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.