Prerequisites!
This article assumes that you are familiar with the basics of writing components for Delphi. It also assumes that you have downloaded and played with my Accelerometer demo for iOS.
Where are we now?
As you can see in the current demo, the code has a whole bunch of $IFDEFs in it. All of the accelerometer stuff is on the main form. What if you wanted to take it to a different demo? You could just copy and paste it I suppose, but a much better approach would be to encapsulate the accelerometer code in a component. The component will do nothing on Windows, and it will work the way it's supposed to on iOS.
For instance, the main form contains this:
procedure TForm1.FormCreate(Sender: TObject);
begin
UIAccelerometer.sharedAccelerometer.setUpdateInterval(1.0 / kAccelerometerFrequency);
AccDelegatVar := AccDelegate.alloc;
UIAccelerometer.sharedAccelerometer.setDelegate(AccDelegatVar);
end;
We shouldn't have to deal with setup code like this in project after project!
Writing the component
Here is the code for the accelerometer in its entirety. Notice that all of the $IFDEF stuff moves into this single unit. It won't pollute your project anymore. Add this to a package, install into the IDE and you're off and running, or should I say accelerating! :)
unit Accelerometer;
interface
uses
SysUtils, Classes, FMX_Types
, iPhoneAll
;
type
TAccelerateEvent = procedure(x,y : Double) of object;
TiOSAccelerometer = class(TFmxObject)
private
FOnAccelerate: TAccelerateEvent;
protected
public
constructor Create(AOwner: TComponent); override;
published
property OnAccelerate: TAccelerateEvent read FOnAccelerate write FOnAccelerate;
end;
const
kAccelerometerFrequency = 100.0;
type
UIAcceleration = objcclass external (NSObject)
public
function timestamp: NSTimeInterval; message 'timestamp';
function x: UIAccelerationValue; message 'x';
function y: UIAccelerationValue; message 'y';
function z: UIAccelerationValue; message 'z';
end;
type
AccDelegate = objcclass(NSObject)
procedure accelerometer_didAccelerate(accelerometer: UIAccelerometer; acceleration: UIAcceleration); message 'accelerometer:didAccelerate:';
end;
var
AccDelegatVar: AccDelegate;
var
MyAccelerometer: TiOSAccelerometer;
procedure Register;
implementation
procedure AccDelegate.accelerometer_didAccelerate(accelerometer: UIAccelerometer; acceleration: UIAcceleration);
begin
if Assigned(MyAccelerometer) then
if Assigned(MyAccelerometer.FOnAccelerate) then
MyAccelerometer.FOnAccelerate(acceleration.x,acceleration.y);
end;
constructor TiOSAccelerometer.Create(AOwner: TComponent);
begin
inherited;
UIAccelerometer.sharedAccelerometer.setUpdateInterval(1.0 / kAccelerometerFrequency);
AccDelegatVar := AccDelegate.alloc;
UIAccelerometer.sharedAccelerometer.setDelegate(AccDelegatVar);
end;
procedure Register;
begin
RegisterComponents('iOS', [TiOSAccelerometer]);
end;
end.
Let's break it down and go through each piece. First we declare an event type. When the accelerometer gives us acceleration data we want the X and Y components of it.
type
TAccelerateEvent = procedure(x,y : Double) of object;
Now, the class itself. Nothing strange here, simply a field to hold the event handler, a published property so it's assignable in the IDE. And a constructor.
type
TiOSAccelerometer = class(TFmxObject)
private
FOnAccelerate: TAccelerateEvent;
public
constructor Create(AOwner: TComponent); override;
published
property OnAccelerate: TAccelerateEvent read FOnAccelerate write FOnAccelerate;
end;
This code is specifically here due to an issue with the current parser for the UIAcceleration callback type.
type
UIAcceleration = objcclass external (NSObject)
public
function timestamp: NSTimeInterval; message 'timestamp';
function x: UIAccelerationValue; message 'x';
function y: UIAccelerationValue; message 'y';
function z: UIAccelerationValue; message 'z';
end;
Next up, our delegate. This is the object that contains a method that is getting called when there's new acceleration data.
type
AccDelegate = objcclass(NSObject)
procedure accelerometer_didAccelerate(accelerometer: UIAccelerometer; acceleration: UIAcceleration); message 'accelerometer:didAccelerate:';
end;
We need an instance of it:
var
AccDelegatVar: AccDelegate;
We need a global reference to refer to the current instance of TiOSAccelerometer. This could probably be solved by a singleton, but I'm choosing this way for now. More later.
var
MyAccelerometer: TiOSAccelerometer;
The implementation of the accelerometer delegate looks as follows. If the global reference is assigned, and it has an assigned event handler, then call it with the accelerometer data.
procedure AccDelegate.accelerometer_didAccelerate(accelerometer: UIAccelerometer; acceleration: UIAcceleration);
begin
if Assigned(MyAccelerometer) then
if Assigned(MyAccelerometer.FOnAccelerate) then
MyAccelerometer.FOnAccelerate(acceleration.x,acceleration.y);
end;
The constructor takes care of the setup. We set the update interval, create an instance of the delegate and assign it properly.
constructor TiOSAccelerometer.Create(AOwner: TComponent);
begin
inherited;
UIAccelerometer.sharedAccelerometer.setUpdateInterval(1.0 / kAccelerometerFrequency);
AccDelegatVar := AccDelegate.alloc;
UIAccelerometer.sharedAccelerometer.setDelegate(AccDelegatVar);
end;
Register the component on the iOS tab in the IDE!
procedure Register;
begin
RegisterComponents('iOS', [TiOSAccelerometer]);
end;
Writing an app that uses the component
We can now take the original demo project and rip out all the $IFDEF'd code. Below I'll simply cover the pieces that change in our new demo project that's using the component instead.
We drop a TiOSAccelerometer component on our form. This will add the Accelerometer unit to our uses clause:
interface
uses
..., Accelerometer;
We double-click on the component, and we get an event handler added for OnAccelerate:
type
TForm1 = class(TForm)
...
procedure iOSAccelerometer1Accelerate(x, y: Double);
...
end;
The Form.Create method becomes super simple. We do need to assign the active TiOSAccelerometer component to the MyAccelerometer global reference. It will now be able to get those callbacks with the acceleration data.
procedure TForm1.FormCreate(Sender: TObject);
begin
ResetBallPosition;
MyAccelerometer := iOSAccelerometer1;
end;
Finally, the OnAccelerate event in our case. Couldn't get any neater!
procedure TForm1.iOSAccelerometer1Accelerate(x, y: Double);
begin
MoveBall(x,y);
end;
That's it. Enjoy!
Download
Contact
Please feel free to email me with feedback to aohlsson at embarcadero dot com.
Connect with Us