Walkthrough - Background Location in Xamarin.iOS
In this example, we are going to build an iOS Location application that prints information about our current location: latitude, longitude, and other parameters to the screen. This application will demonstrate how to properly perform location updates while the application is either Active or Backgrounded.
This walkthrough explains some key backgrounding concepts, including registering an app as a background-necessary application, suspending UI updates when the app is backgrounded, and working with the WillEnterBackground
and WillEnterForeground
AppDelegate
methods.
Application set up
First, create a new iOS > App > Single View Application (C#). Call it Location and ensure that both iPad and iPhone have been selected.
A location application qualifies as a background-necessary application in iOS. Register the application as a Location application by editing the Info.plist file for the project.
Under Solution Explorer, double click on the Info.plist file to open it, and scroll to the bottom of the list. Place a check by both the Enable Background Modes and the Location Updates checkboxes.
In Visual Studio for Mac, it will look like something like this:
In Visual Studio, Info.plist needs to be updated manually by adding the following key/value pair:
<key>UIBackgroundModes</key> <array> <string>location</string> </array>
Now that the application is registered, it can get location data from the device. In iOS, the
CLLocationManager
class is used to access location information, and can raise events that provide location updates.In the code, create a new class called
LocationManager
that provides a single place for various screens and code to subscribe to location updates. In theLocationManager
class, make an instance of theCLLocationManager
calledLocMgr
:public class LocationManager { protected CLLocationManager locMgr; public LocationManager () { this.locMgr = new CLLocationManager(); this.locMgr.PausesLocationUpdatesAutomatically = false; // iOS 8 has additional permissions requirements if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) { locMgr.RequestAlwaysAuthorization (); // works in background //locMgr.RequestWhenInUseAuthorization (); // only in foreground } if (UIDevice.CurrentDevice.CheckSystemVersion (9, 0)) { locMgr.AllowsBackgroundLocationUpdates = true; } } public CLLocationManager LocMgr { get { return this.locMgr; } } }
The code above sets a number of properties and permissions on the CLLocationManager class:
PausesLocationUpdatesAutomatically
– This is a Boolean that can be set depending on whether the system is allowed to pause location updates. On some device it defaults totrue
, which can cause the device to stop getting background location updates after about 15 minutes.RequestAlwaysAuthorization
- You should pass this method to give the app user the option to allow the location to be accessed in the background.RequestWhenInUseAuthorization
can also be passed if you wish to give the user the option to allow the location to be accessed only when the app is in the foreground.AllowsBackgroundLocationUpdates
– This is a Boolean property, introduced in iOS 9 that can be set to allow an app to receive location updates when suspended.
Important
iOS 8 (and greater) also requires an entry in the Info.plist file to show the user as part of the authorization request.
Add Info.plist keys for the permission types the app requires –
NSLocationAlwaysUsageDescription
,NSLocationWhenInUseUsageDescription
, and/orNSLocationAlwaysAndWhenInUseUsageDescription
– with a string that will be displayed to the user in the alert that requests location data access.iOS 9 requires that when using
AllowsBackgroundLocationUpdates
the Info.plist includes the keyUIBackgroundModes
with the valuelocation
. If you have completed step 2 of this walkthrough, this should already been in your Info.plist file.Inside the
LocationManager
class, create a method calledStartLocationUpdates
with the following code. This code shows the how to start receiving location updates from theCLLocationManager
:if (CLLocationManager.LocationServicesEnabled) { //set the desired accuracy, in meters LocMgr.DesiredAccuracy = 1; LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) => { // fire our custom Location Updated event LocationUpdated (this, new LocationUpdatedEventArgs (e.Locations [e.Locations.Length - 1])); }; LocMgr.StartUpdatingLocation(); }
There are several important things happening in this method. First, we perform a check to see if the application has access to location data on the device. We verify this by calling
LocationServicesEnabled
on theCLLocationManager
. This method will return false if the user has denied the application access to location information.Next, tell the location manager how often to update.
CLLocationManager
provides many options for filtering and configuring location data, including the frequency of updates. In this example, set theDesiredAccuracy
to update whenever the location changes by a meter. For more information on configuring location update frequency and other preferences, refer to the CLLocationManager Class Reference in the Apple documentation.Finally, call
StartUpdatingLocation
on theCLLocationManager
instance. This tells the location manager to get an initial fix on the current location, and to start sending updates
So far, the location manager has been created, configured with the kinds of data we want to receive, and has determined the initial location. Now the code needs to render the location data to the user interface. We can do this with a custom event that takes a CLLocation
as an argument:
// event for the location changing
public event EventHandler<LocationUpdatedEventArgs>LocationUpdated = delegate { };
The next step is to subscribe to location updates from the CLLocationManager
, and raise the custom LocationUpdated
event when new location data becomes available, passing in the location as an argument. To do this, create a new class LocationUpdateEventArgs.cs. This code is accessible within the main application and returns the device location when the event is raised:
public class LocationUpdatedEventArgs : EventArgs
{
CLLocation location;
public LocationUpdatedEventArgs(CLLocation location)
{
this.location = location;
}
public CLLocation Location
{
get { return location; }
}
}
User Interface
Use the Xcode Interface Builder to build the screen that will display location information. Double-click on the Main.storyboard file to begin.
On the storyboard, drag several labels onto the screen to act as placeholders for the location information. In this example, there are labels for latitude, longitude, altitude, course, and speed.
For more information, see Designing user interfaces with Xcode.
In the Solution Pad, double-click the
ViewController.cs
file and edit it to create a new instance of the LocationManager and callStartLocationUpdates
on it. Change the code to look like the following:#region Computed Properties public static bool UserInterfaceIdiomIsPhone { get { return UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone; } } public static LocationManager Manager { get; set;} #endregion #region Constructors public ViewController (IntPtr handle) : base (handle) { // As soon as the app is done launching, begin generating location updates in the location manager Manager = new LocationManager(); Manager.StartLocationUpdates(); } #endregion
This will start the location updates on application start-up, although no data will be displayed.
Now that the location updates are received, update the screen with the location information. The following method gets the location from our
LocationUpdated
event and shows it in the UI:#region Public Methods public void HandleLocationChanged (object sender, LocationUpdatedEventArgs e) { // Handle foreground updates CLLocation location = e.Location; LblAltitude.Text = location.Altitude + " meters"; LblLongitude.Text = location.Coordinate.Longitude.ToString (); LblLatitude.Text = location.Coordinate.Latitude.ToString (); LblCourse.Text = location.Course.ToString (); LblSpeed.Text = location.Speed.ToString (); Console.WriteLine ("foreground updated"); } #endregion
We still need to subscribe to the LocationUpdated
event in our AppDelegate, and call the new method to update the UI. Add the following code in ViewDidLoad,
right after the StartLocationUpdates
call:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// It is better to handle this with notifications, so that the UI updates
// resume when the application re-enters the foreground!
Manager.LocationUpdated += HandleLocationChanged;
}
Now, when the application is run, it should look something like this:
Handling Active and Background states
The application is outputting location updates while it is in the foreground and active. To demonstrate what happens when the app enters the background, override the
AppDelegate
methods that track application state changes so that the application writes to the console when it transitions between the foreground and the background:public override void DidEnterBackground (UIApplication application) { Console.WriteLine ("App entering background state."); } public override void WillEnterForeground (UIApplication application) { Console.WriteLine ("App will enter foreground"); }
Add the following code in the
LocationManager
to continuously print updated location data to the application output, to verify the location information is still available in the background:public class LocationManager { public LocationManager () { ... LocationUpdated += PrintLocation; } ... //This will keep going in the background and the foreground public void PrintLocation (object sender, LocationUpdatedEventArgs e) { CLLocation location = e.Location; Console.WriteLine ("Altitude: " + location.Altitude + " meters"); Console.WriteLine ("Longitude: " + location.Coordinate.Longitude); Console.WriteLine ("Latitude: " + location.Coordinate.Latitude); Console.WriteLine ("Course: " + location.Course); Console.WriteLine ("Speed: " + location.Speed); } }
There is one remaining issue with the code: attempting to update the UI when the app is backgrounded will cause iOS will terminate it. When the app goes into the background, the code needs to unsubscribe from location updates and stop updating the UI.
iOS provides us notifications when the app is about to transition to a different application states. In this case, we can subscribe to the
ObserveDidEnterBackground
Notification.The following code snippet shows how to use a notification to let the view know when to halt UI updates. This will go in
ViewDidLoad
:UIApplication.Notifications.ObserveDidEnterBackground ((sender, args) => { Manager.LocationUpdated -= HandleLocationChanged; });
When the app is running, the output will look something like this:
The application prints location updates to the screen when operating in the foreground, and continues to print data to the application output window while operating in the background.
Only one outstanding issue remains: the screen starts UI updates when the app is first loaded, but it has no way of knowing when the app has re-entered the foreground. If the backgrounded application is brought back into the foreground, UI updates won’t resume.
To fix this, nest a call to start UI updates inside another Notification, which will fire when the application enters the Active state:
UIApplication.Notifications.ObserveDidBecomeActive ((sender, args) => {
Manager.LocationUpdated += HandleLocationChanged;
});
Now the UI will begin updating when the application is first started, and resume updating any time the app comes back into the foreground.
In this walkthrough, we built a well-behaved, background-aware iOS application that prints location data to both the screen and the application output window.