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:
[sourcecode language=”delphi”]
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;
[/sourcecode]
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:
[sourcecode language=”delphi”]
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;[/sourcecode]
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.
Hello,
very interesting post.
In this days I’m going crazy in order to listen the Beacon with application in background.
Reading the documentation, I used UIBackgroundModes with Location and/or bluetooth-central mode …but nothing.
I searched in internet but seems that this background mode not works.
I need only that when I listen a beacon I open the app …
Have you experience with this scenario ?
thanks for suggestions.
kind regards
Antonello Carlomagno
[…] NOTE: This article is about allowing your iOS app to run when it goes into the background (i.e. another app becomes active) for a short period (up to 3 minutes on iOS 9, at least). It does not relate to having the UIBackgroundModes option, an example of which is here. […]
Hi have you filed a bug on this by chance?
Not as yet. I have a bit of a backlog; thanks for the reminder 🙂
How are you, my name is Elcio , São Paulo, Brazil , tried to reproduce this script, bad this oroccrendo error in line with the script ” TCLLocationManager = class ( TOCGenericImport & lt; CLLocationManagerClass , CLLocationManager & gt; ) end ; ” I could not identify, can you help me another question , I’m trying to make my application running in the background on iOS 9.3.5 and can not in any way , I tried several examples found here bad without success, I will try to perform the functions using the location , really appreciate your help , and grateful greetings
Sorry; slight formatting error. It’s fixed now. As for the background problem, you’ll have to provide more information on what you want your code to do in the background
Very grateful actually the application uses a timer and is from time to time sending via socket information of latitude and longitude , the current time and battery, already used the Feth and nothing would have any idea , grateful
You cannot use a timer in the background.
The best option would be to set the LocationChange value on the location sensor to lctLarge when your app is about to go into the background, using the WillBecomeInactive event. You’ll also need to use the techniques described here:
http://blog.delphiworlds.com/2016/07/region-monitoring-background-ios/
In order for the application to receive location updates in the background
Very grateful for helping me, I’m trying to play on my iOS changes in system.ios.sensors file and running lacationtest program, bad no success, maybe I’m putting the implementations in the wrong way, you could help me by sending the file system. ios.sensors with the proposed implementations, I thank you so much patience, hugs.
I cannot send you the complete unit, as it would violate copyright.
What do you mean by “no success”? Some things to check:
1. You made a copy of the System.iOS.Sensors unit, and put it in the project path (in the same folder as the project itself should do)
2. You made the changes to that unit outlined in the article
3. You added the custom property of MFLocationRequestAlwaysPermission to the Version Info in the project options (as per the demo project) and set it to: true.
4. Your application handles the WillBecomeInactive event, and sets the LocationChange value on the sensor to lctLarge. It should also handle WillBecomeForeground if you want the LocationChange set to lctSmall when the application is active. There is an example of how to handle these events, here:
http://community.embarcadero.com/blogs/entry/mobile-app-lifecycle-events-handling-in-delphi-xe5-40067
The final 2 steps are very important, as location changes in the background will be notified only if LocationChange is lctLarge, and only if the app has the “always” permission (meaning it has permission when in the foreground, and when in the background).
Thankful , I understand the problem you encounter is only the function below manager.location emits error in the word location, this occurs when you insert the codes described , put the routines below the implamantation . Grateful
TiOSLocationDelegate.locationManager procedure ( manager: CLLocationManager ; didUpdateToLocation , fromLocation : CLLocation ) ;
begin
DoStateChanged ( TSensorState.Ready ) ;
try
DoCLLocationChanged ( manager.location ) ;
DoLocationChanged ( ConvCLLocation ( fromLocation )
ConvCLLocation ( didUpdateToLocation ) ) ;
finally
DoStateChanged ( TSensorState.NoData ) ;
end ;
end ;
Hello,
I do not know if it can help, with berlin I have changed only
function TiOSLocationSensor.DoStart: Boolean;
… adding just these two lines
FLocater.requestAlwaysAuthorization
FLocater.setAllowsBackgroundLocationUpdates(True);
Now the app remains always in the background listening.
Antonello
Very grateful Antonello, worked perfectly, I will use the onlocationchange event to send information via socket, had any experience ?, the application worked perfectly in iOS8 after upgrade everything stopped, I have to redo the routines again, big hug
Elcio
Gentlemen, put the device to test and Locationstest application worked perfectly in the background, the problem that after 20 minutes it stops updating the positioning information, someone has been there.
If the app is in the background, and they do not change location significantly, there will be no updates after a while. Apparently, Apple don’t think that allowing your app to notify your server with an “I’m still here” message is OK.
Regarding your query about iOS 8: yes, the location services changed in iOS 9 so that it needs that call
setAllowsBackgroundLocationUpdates(True)
In order to work in the background, even though the UIBackgroundModes already has location in it.
ok, but If I want to create a app that inform me when I near a place..
for example: I have 2 regions monitored, 1 for home and 1 for office.
When I am near I receive a local push message…. it’s possible ?
otherwise what serve the background mode if is not possible to use it ?
This article has a complete demo that does exactly that:
http://blog.delphiworlds.com/2016/07/region-monitoring-background-ios/
is there any way to wake up the place after hibernation?
If the app is still running in the background (using the code discussed, above), and the user changes location significantly, it will “wake up”. I don’t think there’s any way of making it “wake up” without the user changing location.
Como vai, pode me ajudar, estou tentando escrever a primeira janela de sua orientação, más não sei em que lugar no arquivo system.ios.sensors devo colocar as funções, apenas alterei a função DoSstart incluindo os comandos, FLocater.requestAlwaysAuthorization;
FLocater.setAllowsBackgroundLocationUpdates(True); o aparelho até funcionou em background, más depois de 1 hora ele para, em movimento, quando está de repouso, após 20 minutos ele também para e não volta após estar em movimento, pode me ajudar, muito grato
How are you, help me, I’m trying to write the first window of his guidance, bad do not know where in the file system.ios.sensors I put the functions, just changed the DoSstart function including commands, FLocater.requestAlwaysAuthorization;
FLocater.setAllowsBackgroundLocationUpdates (True); the device to work in the background, bad after 1 hour for it moving when resting, after 20 minutes he also not return after being on the go, you can help me, very grateful
Can you show your code for DoStart?
Also, remember that the LocationChange value for the TLocationSensor *must* be lctLarge when the application is in the background, otherwise it will not receive location updates
Hello, I’m rather putting lctLarge in background mode, below DoStart code, thanks.
function TiOSLocationSensor.DoStart: Boolean;
var
I: Integer;
begin
// TOSVersion.Check if (8) and (FLocater nil) then
//FLocater.requestWhenInUseAuthorization;
FLocater.requestAlwaysAuthorization;
FLocater.setAllowsBackgroundLocationUpdates (True);
// Check authorization
if Authorized = TAuthorizationType.atUnauthorized Then
SensorError (SLocationServiceUnauthorized);
// Check if location sensor is enabled
Then if not FLocater.locationServicesEnabled
SensorError (SLocationServiceDisabled);
// Start location updates
if (locationChange = TLocationChangeType.lctLarge) and CanUseSignifChangeNotifs Then
FLocater.startMonitoringSignificantLocationChanges
else
FLocater.startUpdatingLocation;
// Start heading updates
Then if CanUseHeading
begin
FLocater.startUpdatingHeading;
end;
// Start monitoring regions
Then if CanMonitorRegions
for I: = 0 to Regions.Count – 1
FLocater.startMonitoringForRegion (ConvLocationRegion (Regions [I]));
Result: = FLocater.locationServicesEnabled;
Then if Result
Result: = = Authorized TAuthorizationType.atAuthorized;
end;
Here again, the translator changed some lines
function TiOSLocationSensor.DoStart: Boolean;
var
I: Integer;
begin
// TOSVersion.Check if (8) and (FLocater nil) then
//FLocater.requestWhenInUseAuthorization;
FLocater.requestAlwaysAuthorization;
FLocater.setAllowsBackgroundLocationUpdates (True);
// Check authorization
if Authorized = TAuthorizationType.atUnauthorized Then
SensorError (SLocationServiceUnauthorized);
// Check if location sensor is enabled
Then if not FLocater.locationServicesEnabled
SensorError (SLocationServiceDisabled);
// Start location updates
if (locationChange = TLocationChangeType.lctLarge) and CanUseSignifChangeNotifs Then
FLocater.startMonitoringSignificantLocationChanges
else
FLocater.startUpdatingLocation;
// Start heading updates
if CanUseHeading Then
begin
FLocater.startUpdatingHeading;
end;
// Start monitoring regions
if CanMonitorRegions Then
for I: = 0 to Regions.Count – 1
FLocater.startMonitoringForRegion (ConvLocationRegion (Regions [I]));
Result: = FLocater.locationServicesEnabled;
Then if Result
Result: = = Authorized TAuthorizationType.atAuthorized;
end;
Aside from the obvious errors (probably caused by your translator), that looks OK. You might need to provide a small test project, so I could debug it and make sure everything is being set properly.
I want to revisit the test project from my other article anyway, and trim it down to something very straightforward, but still achieve the result you’re looking for, however that may be some time from now.
Perfect , I’m waiting for the test project , any news please let me know, I’ve tried everything as far as my knowledge goes , the android had no problem , the problem appeared after upgrading from version 8 to version 9.3.5 IOS .
Thank you
Thanks for this sample works great. Just a little note, if you want to change from lctLarge and lctSmall during App background and foreground changes you need to stop and start the sensor.
Hello Devid!
Thank you for your research!
I spent a very long time looking for a solution and, it seemed, found him…
But i can not use it 🙁
When i put your code (type //Reintroducing the entire CLLocationManager and CLLocationManagerDelegate in order…) after the uses part of implementation section, i get a lot of errors. For example:
https://cloud.mail.ru/public/2Ze2/QnQhvMMNa
Maybe I’m already very tired (I really spent many days searching and trying a solution). Or maybe I’m just stupid 🙁
Could you help me? Please 🙂
I’m ready to share the source file System.iOS.Sensors.pas
I’m sure it will not take long-time for you.
And you can can save from the madness of one programmer :))))))))))))
Could you send your modified unit using the contact page of my website? I’ll have a look at it.
http://blog.delphiworlds.com/#contact
Hi,
after upgrading my Delphi to the RIO version, I have a problem with TLocationSensor. The onLocationChanged event is triggered in Windows and Android. IOS does not trigger this event. Do you have any suggestions how to fix it?
Please refer to this report: https://quality.embarcadero.com/browse/RSP-21834
As per the description in the report, you need to comment line 151 (i.e. the MethodName attribute)
Hey Dave. Having login issues with this link despite resetting my password. Can you describe the fix here?
Make a copy of System.iOS.Sensors (in the source\rtl\common folder) and put it in your project folder.
Comment out line 151 in the copy of System.iOS.Sensors.
Hello. What was your fix here?
Hi Dave,
Realy Great job it works !!!
Now I have only one more problem with TMSFMXWebGMaps on Android.
Maybe you know how to run the methods: TMSFMXWebGMaps.MapPanTo and TMSFMXWebGMaps.MapOptions.ZoomMap.
I am obliged for your help.
Regards
Marcin
Please refer to:
https://www.tmssoftware.com/site/forum/forum_posts.asp?TID=11172&title=api-level-26
I’m dubious about whether it is a problem they’re unable to resolve, however I do not know the detail. If I have time, I’ll look into it. It may be helpful if the messages (if any) from logcat were provided.
HI,
Many developers will be grateful 🙂
Regards,
Marcin
Dave thank you! That worked!
Just for other users:
Running Delphi 10.3 Enterprise/ iPhone 6 on iOS 12 / Using 11.2 SDK/ XCode 9.2.
[…] I have written two articles on monitoring location changes: one for iOS, and one for Android. This article and the related demo brings everything up-to-date and merges […]
I am trying to do something similar in RadStudio 10.4 which has now fixed this problem by letting you set the background modes in Project Options. So this question is sort of related bit I am asking here because I don’t know where else to get help. Any help would be greatly appreciated.
I am trying to display the user location on a multi-device form that has a TMapView object and TLocationSensor object on it. The code is below. I was a little unsure of whether or not to Synchronise the OnLocationChange event hence there is code for thread checking. Apparently for iOS the callback occur in the main execution thread so the synchronization isn’t required, is that correct ? All I want to do is diplay the user location on the map (ie the little blue glowing circle) but as soon as it updates the map an exception occurs in TMapKitDelegate.mapViewViewForAnnotation -> TMapKitMapMarker.AnnotationView
This problem is driving me nuts, hopefully someone can see anything silly I am doing wrong although I don’t have the problem in Embarcadero RadStudio 10.3
unit FMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Maps, {FMX.Maps.iOS,}
System.Sensors, System.Sensors.Components;
type
TfMainForm = class(TForm)
tmvMapView: TMapView;
tlocLocationSensor: TLocationSensor;
procedure tlocLocationSensorLocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
mCurrentLocation: TLocationCoord2D;
procedure ProcessLocationChange;
public
{ Public declarations }
end;
var
fMainForm: TfMainForm;
implementation
{$R *.fmx}
procedure TfMainForm.tlocLocationSensorLocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D);
var mainThread: TThreadID;
currentThread: TThreadID;
begin
mCurrentLocation.Latitude := -37.78001; //NewLocation.Latitude;
mCurrentLocation.Longitude := 144.98890; //NewLocation.Longitude;
mainThread := System.MainThreadID;
currentThread := TThread.Current.ThreadID;
// Check to see if we need to do a synchronize or not. Apparently for iOS
// the location sensor callback runs in the main thread.
//
if mainThread currentThread then
TThread.Synchronize(TThread.Current, ProcessLocationChange)
else
ProcessLocationChange;
end;
procedure TfMainForm.FormCreate(Sender: TObject);
begin
tlocLocationSensor.Active := True;
end;
procedure TfMainForm.ProcessLocationChange;
var mapCenter: TMapCoordinate;
begin
// Center the map view around our current position and set the zoom to 18
mapCenter := TMapCoordinate.Create(mCurrentLocation.Latitude, mCurrentLocation.Longitude);
tmvMapView.Location := mapCenter;
tmvMapView.Zoom := 18;
end;
end.
What is the actual issue you’re having? Are you aware of this report?:
https://quality.embarcadero.com/browse/RSP-29859
Hi Dave. Thanks for your response. I know how to make the Location sensor work in the background. All that needs to be done is to set the allow background flags in the project options -> Version Info -> UI Background modes, although it seems to work, it doesn’t seem to work perfectly [you need to switch between foreground and background mode, I use both the location and the audio]. The problem is using the TMapView. I should be able to show the user location on the map, live as a blue glowing dot. In the example code given, if you build it in 10.3, the blue dot shows. If you use exactly the same code in 10.4 you get the exception mentioned. The property to set the UserLocation is part of the TMapView obkect LayerOptions.UserLocation. So when I set this True, in 10.3 everything is fine. In 10.4 you have a problem. I need to use 10.4 because it allows me to use the UI Background modes project option rather than using 10.3 and the approach shown in the original article [ie a modified system file]
Hi Thomas, I think this report may be related to your issue: https://quality.embarcadero.com/browse/RSP-30471
BTW Dave, I had a look at that link you shared and yes, that solves the other problem I mentioned in my last reply where I said the TLocationSensor wasn’t working perfectly.
Thanks again for your help. Yes that is exactly the work around I need but not the solution I want. I will use the work around for now and wait for the fix from Embarcadero