A recent post in the Embarcadero Delphi forums asked about why the location sensor stopped working in the background when changing to iOS 9. Since I have an interest in this area, I’ve done a bit of investigating as to how to make this work, and this post is the result.
The location sensor stopped working because of a change in iOS 9 that requires a property to be set on CLLocationManager, in addition to having already include location in UIBackgroundModes in the project options. The property in question is allowsBackgroundLocationUpdates, however this property is not declared CLLocationManager in the iOSapi.CoreLocation unit, and so of course it is not surfaced as an option in TLocationSensor. A secondary issue relates to being able to set the “always” authorization instead of “when in use”.
In order to fix most issues such as this, I prefer to modify only the implementation sections of the units provided with Delphi. This is because I need only include those units when recompiling, rather than all of the units that are affected by a change if one is made in the interface section. This practice made this issue a challenge because I needed to change the interface provided in one unit (iOSapi.CoreLocation) that is used by another (System.iOS.Sensors). The answer was to completely redeclare CLLocationManager in System.iOS.Sensors in the implementation section, and add the new methods for the property there. Since the delegate is also declared in iOSapi.CoreLocation, I needed to redeclare that, too.
Here’s snippets (for brevity, and to avoid copyright issues) of the changes, which appear just after the uses clause in the implementation section of System.iOS.Sensors:
type // Reintroducing the entire CLLocationManager and CLLocationManagerDelegate in order to add allowsBackgroundLocationUpdates property CLLocationManager = interface(NSObject) ['{B162A514-4334-48B8-B31A-32926B758340}'] // New GUID function activityType : CLActivityType; cdecl; procedure allowDeferredLocationUpdatesUntilTraveled(distance: CLLocationDistance; timeout: NSTimeInterval); cdecl; function allowsBackgroundLocationUpdates: Boolean; cdecl; // New method for allowsBackgroundLocationUpdates property function delegate: Pointer; cdecl; // snip procedure setActivityType(activityType: CLActivityType); cdecl; procedure setAllowsBackgroundLocationUpdates(allowsBackgroundLocationUpdates: Boolean); cdecl; // New method for allowsBackgroundLocationUpdates property procedure setDelegate(delegate: Pointer); cdecl; // snip end; TCLLocationManager = class(TOCGenericImport<CLLocationManagerClass, CLLocationManager>) end; CLLocationManagerDelegate = interface(IObjectiveC) ['{113A227F-AD2D-4983-83C3-C5158F394257}'] // New GUID procedure locationManager(manager: CLLocationManager; didFailWithError: NSError); overload; cdecl; // snip [MethodName('locationManager:didFinishDeferredUpdatesWithError:')] procedure locationManagerDidFinishDeferredUpdatesWithError(manager: CLLocationManager; error: NSError); cdecl; end;
The next task was to work out a way of being able to optionally set the “always” authorization, and to optionally set the allowsBackgroundLocationUpdates property when the sensor is active. To achieve this, I modified TiOSLocationSensor.DoStart and used conditional defines so that the correct code is included at compile time:
function TiOSLocationSensor.DoStart: Boolean; var I: Integer; begin if TOSVersion.Check(8) and (FLocater <> nil) then begin // *** Added this conditional define {$IF Defined(REQUESTALWAYS)} FLocater.requestAlwaysAuthorization {$ELSE} FLocater.requestWhenInUseAuthorization; {$ENDIF} end; // *** Added this if block: if TOSVersion.Check(9) and (FLocater <> nil) then begin {$IF Defined(BACKGROUNDUPDATES) and Defined(CPUARM64)} // for some reason, this function crashes in 32-bit FLocater.setAllowsBackgroundLocationUpdates(True); {$ENDIF} end; // check authorization if Authorized = TAuthorizationType.atUnauthorized then SensorError(SLocationServiceUnauthorized); // snip end;
NOTE: Where the comment “//snip” appears, portions of the code have merely been left out in that section.
To test this all out, I’ve uploaded a test project:

You will still need to copy System.iOS.Sensors from your source folders, to somewhere in your project path, and make the changes described above. If you’re creating your own project, also ensure you have the conditional defines set, as well as location in the UIBackgroundModes option in the Version Information section of the project options.
The post Making the Location Sensor Work in the Background on iOS 9 appeared first on Delphi Worlds.