Using CoreLocation to locate your users

The final feature that needs to be added to the login screen of the ArtApp application is figuring out where a user is currently located. Devices such as the iPhone contain a GPS chip that can determine a user's location in an extremely accurate fashion. Incorporating this into your apps can greatly improve the user experience. However, implementing location services poorly can and will frustrate your users and could even drain their battery.

Before we add location services to ArtApp, we should explore the CoreLocation framework to see what features it has and how we can efficiently implement it into ArtApp and other applications. Some of the features and examples you will be familiarized with are as follows:

  • Obtaining a user's location
  • Geofencing
  • Tracking a user while they're moving

Let's dive right in with the basics.

Obtaining a user's location

The documentation for CoreLocation states the following in its subheading.

Determine the current latitude and longitude of a device. Configure and schedule the delivery of location-related events.

In other words, figuring out a user's location is one of the two core features in the CoreLocation framework. For now, we will focus on determining the user's current latitude and longitude.

Before we are able to access our user's location, we will need to ask permission. Similar to how permission is asked for camera and motion access, you are required to specify in Info.plist that you want to query your user's location and also the reason. The key that you will need to add to the Info.plist is NSLocationWhenInUseUsageDescription . The description should be short and concise as usual, for example, Provides information about nearby artwork. If you add this key and value pair, you are allowed to access the users' location while they're using the app. If you want to have access to the user's location while your app is in the background you will need to add the Privacy - Location Always Usage Description to your app.

Once you add the appropriate key to your plist file, you can use the CoreLocation framework in your app. Just as with the camera, adding the correct key to the plist file alone isn't enough. The user needs to manually allow your app to access their location. The steps to ask permission for location are similar to the camera but not quite the same.

When you ask permission to access the camera, a callback is used to communicate the result of this authorization to your application. For location services, a delegate method is called. This means that we will need to set a delegate on an instance of CLLocationManager that conforms to CLLocationManagerDelegate in order to be notified when the authorization status changes.

The CLLocationManager class is the main point that your app will use as a gateway into the location services of the user's device. The location manager is not only used for location updates but also to determine heading, scheduling events, and more. We'll get to that later; for now, we will use the location manager to simply figure out the user's location.

To get started, import CoreLocation in ViewController.swift and make the ViewController class conform to CLLocationManagerDelegate by adding it to the class declaration. We will also need to add a property to ViewController that holds on to our location manager, as follows:

import CoreLocation 
 
class ViewController: UIViewController, CLLocationManagerDelegate{ 
  let locationManager: CLLocationManager = CLLocationManager() 

Next, add the following method to the class and make sure that you call it in viewDidLoad:

func setupLocationUpdates() { 
    locationManager.delegate = self 
     
    let authStatus = CLLocationManager.authorizationStatus() 
    switch authStatus { 
    case .notDetermined: 
        locationManager.requestWhenInUseAuthorization() 
    case .authorizedWhenInUse: 
        startLocationTracking() 
    default: 
        break 
    } 
} 

This method adds the view controller as the delegate for the location manager and verifies its authorization status. If the status is undetermined, we ask for permission, and if permission is already given, we immediately call startLocationTracking. This is a method we'll implement soon.

Whenever the permission status regarding the location changes, the location manager will inform its delegate about it. We will need to implement the following method to be notified about authorization changes:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: 
  CLAuthorizationStatus) { 
    if status == .authorizedWhenInUse { 
        startLocationTracking() 
    } 
} 

In this method, we check the authorization status, and if we're authorized, we call the startLocationTracking method. The implementation for this method is really short since we just want to tell the location manager to start sending us updates regarding the user's location. Before your app starts to listen for location updates, you will need to make sure that location services are enabled, as shown in the following code snippet; you must do this because the user can disable location services entirely in the settings app:

func startLocationTracking() { 
    if CLLocationManager.locationServicesEnabled() { 
        locationManager.startUpdatingLocation() 
    } 
} 

Now that the location manager activated the GPS chip and started to process the user's location, we just need to implement a delegate method that can be called whenever the location is updated. The method we need to implement is locationManager(_:didUpdateLocations:).

The location manager supplies itself and an array of CLLocation instances to locationManager(_:didUpdateLocations:). The locations array contains at least one CLLocation instance but it could also contain multiple locations. This occurs if the location manager receives location updates faster than it can supply them to its delegate. All location updates are added in the order in which they occurred. If you're just interested in the most recent location update, this means that you should use the last location in the array.

For our app, we will use the following implementation for locationManager(_:didUpdateLocations:):

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: 
  [CLLocation]) { 
    guard let location = locations.last 
        else { return } 
     
    print(location) 
    manager.stopUpdatingLocation() 
} 

This implementation is perfect for ArtApp. We listen for location updates and pick the last one from the locations array. Then, we use the location and after that we stop listening for location updates. We do this for two reasons. The first is good manners; our app shouldn't listen for location updates longer than it has to. We just want to know where the user is at that moment in time and we don't need continuous updates.

The second reason is to conserve battery life. If our app keeps listening for location updates all the time, the user's battery will empty faster than it needs to. As a developer, it's your job to make sure that your app does not needlessly drain the user's battery.

If you take a look at the printed output from the preceding method, you should see something similar to the following line:

<+52.30202835,+4.69597776> +/- 5.00m (speed 0.00 mps / course -1.00) @ 30-08-16 21:52:55 Midden-Europese zomertijd 

This output is a summary of some of the information that's available in the CLLocation class. Every instance has the GPS coordinates, accuracy, the speed, the direction the user is traveling at, and a time stamp.

This is plenty of information for our app since the latitude and longitude are the only things needed to look up nearby pieces of Augmented Reality art. If ArtApp was a real application, we could send the GPS coordinates to the backend, and it could respond with the number of available art pieces. However, we also want to have a readable location to display to the user, such as Amsterdam or New York.

The act of translating coordinates to a location or vice versa is called geocoding. To convert GPS coordinates to a human-friendly location, you will make use of the CLGeocoder class. This class has a single purpose, geocoding locations. If you convert from coordinates to a human-readable location, you call the reverseGeocode(_:completionHandler:) method. If you already have an address and have the coordinates for it, you call one of the address geocoding methods that are available.

If you're using the CLGeocoder class, it's important that your user has access to the Internet since CLGeocoder uses the network to geocode your requests. This is why you provide a callback to the geocoding methods; they don't return a value immediately. It's also important to be aware that geocoding is rate limited. Your app cannot make an infinite amount of geocoding requests, so it's important that you attempt to cache results locally to prevent unnecessary geocoding lookups.

A simple example of using the CLGeocoder is shown in the following code snippet:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:
   [CLLocation]) { 
    guard let location = locations.last 
        else { return } 
     
    let geocoder = CLGeocoder() 
    geocoder.reverseGeocodeLocation(location, completionHandler: { 
      placemarks, _ in 
        guard let placemark = placemarks?.first 
            else { return } 
         
        print(placemark) 
    }) 
     
    locationManager.stopUpdatingLocation() 
} 

This example uses the last location received from the location manager and reverse geocodes it into a CLPlacemark instance. The completion handler for geocoding receives an array of possible CLPlacemarks that match the request and an NSError. Both are optional and only have a non-nil value if there is a real value available.

We'll get to displaying the address in a human friendly form when we finish our login screen for ArtApp. Let's take a look at geofencing first.

Providing location based updates with geofencing

In addition to just listening for location updates, we can monitor the user's location in regards to a certain area. This is called geofencing, and it allows us to notify the user whenever they enter or exit an area. We can use CoreLocation to define a region that is of interest to our user. For instance, iOS itself uses geofencing to provide location-based reminders to people.

Monitoring a region is done by configuring a CLLocationManager instance to monitor changes to either a CLCircularRegion or a CLBeaconRegion. If you're monitoring GPS coordinates, you will need a circular region. If you're monitoring a user's proximity to an iBeacon, you will use the beacon region.

Once you configure the area you will monitor, you use the location manager's startMonitoring(for:) method to begin monitoring exit and enter events for your region. The location manager will call its delegate whenever one of these events occur.

If your app is in the background or not running, iOS will continue monitoring enter and exit events for your region. If one of these events occur, your app is either woken up or launched in order to handle the region event. The application(_:didFinishLaunchingWithOptions:) method in your app delegate will contain a key named UIApplicationLaunchOptionsLocationKey to indicate that your app was launched due to a location-related event. It's your responsibility to create a location manager object and assign it a delegate that can handle the region event.

After you create your location manager and set its delegate, the delegate method is automatically called. If you have other regions set up, they will be available in the location manager you created. Whenever you create a location manager object, it will have access to all of the monitored regions. The monitored regions will persist between app launches so your app doesn't have to keep track of the regions it wants to monitor.

Let's take a look at an example of monitoring a region. We'll build upon the example shown in the previous segment so all of the location permissions are already taken care of:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: 
  [CLLocation]) { 
    guard let location = locations.last 
        else { return } 
     
    let region = CLCircularRegion(center: location.coordinate, radius: 100, 
      identifier: "HomeArea") 
    if !locationManager.monitoredRegions.contains(region) { 
        locationManager.startMonitoring(for: region) 
    } 
} 
 
func locationManager(_ manager: CLLocationManager, didEnterRegion region: 
  CLRegion) { 
    print("user entered region with identifier: (region.identifier)") 
    manager.stopMonitoring(for: region) 
} 

The preceding snippet uses the locationManager(_:didUpdateLocations:) method to obtain the user's current location and sets up a region around this location. If the location manager is not already monitoring this location, we begin to monitor it.

The locationManager(_:didEnterRegion:) method is implemented so it will be called whenever the users enter this location again. Also, when they do, we stop monitoring the location because we don't need to monitor it any longer. If you try running the app, then quit it, and run it again, you could inspect the monitoredRegions property for your location manager and note that the region is still there. Pretty convenient, right? Let's take a look at what we can do with users' location data while they are moving.

Tracking the user's location while they're on the move

If you're tracking user movement, there are a couple of methods that you might be interested in that CLLocationManager calls. As with the other CoreLocation features, CLLocationManager is at the center of movement-related events.

One of the methods is already covered; locationManager(_:didUpdateLocations:). This is the method you're interested in if you want to track the user's location throughout, for example, a run or a bicycle ride. If you're not interested in your user's exact location but you want to be notified if the user changes the direction they're headed in, you can use the locationManager(_:didUpdateHeading:) method.

The locationManager(_:didUpdateHeading:) is called with a CLHeading instance. This instance contains information about the heading of your user in relation to magnetic north but also in relation to true north. To access the heading in relation to true north, you will use the trueHeading property. This property has a value that represents the heading in degrees.

A value of 90 degrees means the user is heading east. A value of 180 means the user is heading south and a value of 270 represents west. The value of trueHeading is a negative value and means that the true heading could not be determined. The following snippet provides an example for listening to heading changes:

func startLocationTracking() { 
    if CLLocationManager.headingAvailable() { 
        locationManager.startUpdatingHeading() 
    } 
} 
 
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: 
  CLHeading) { 
    print("user is now facing (newHeading.trueHeading) degrees away from north") 
} 

In order to be notified about heading updates, we need to inform the location manager to start updating the heading. Also, we should make sure that the heading information is available. If it is, the startUpdatingHeading method is called to actually start updating the heading.

If the users change their heading, the locationManager(_:didUpdateHeading:) method is called and we can obtain the new heading through the trueHeading property. This is all actually pretty straightforward if you consider that all of the delegate methods and location updates are available through the CLLocationManager and CLLocationManagerDelegate classes.

If your app continuously tracks your user's location, you probably shouldn't be doing this. The apps that really benefit from doing this are mostly workout and tracking apps. It's very likely that you're not interested in a user's location all of the time. You probably want to know whether they enter or leave a certain region or whether they have traveled a significant distance. Geofencing has already been discussed and should be used if you're monitoring a user's proximity to a point of interest. Again, the exact location of the user isn't relevant to your application until they are right where you want them to be. It's not only best practice in regards to a user's privacy but it also saves battery life if you use geofencing over continuous tracking.

If you are interested in your user's movement but don't need to know every tiny move they make, you should use the significant location changes API. If you use this method of tracking your users, you will only receive notifications if the user has moved 500 meters or more and only once every 5 minutes at most. Using this API instead of listening for all location changes can greatly improve battery performance.

If you use the significant location changes API and your app is allowed to use the user's location in the background, your app will be woken up or restarted whenever a significant location change event occurs. The steps to handle the location change are similar to the steps you take to handle geofencing. If your app launches due to a significant location change, the UIApplicationLaunchOptionsLocationKey key will be set in the launch options dictionary. It's up to your application to create a location manager and set a delegate to receive the available events.

The delegate method that's called whenever a significant location change occurs is the locationManager(_:didUpdateLocations:) method. This is the same delegate method that's called when you listen for all location changes.

One last mechanism CoreLocation provides to track the movement of the users is the visits service. Visits work similar to significant location changes except they aren't created while a user is moving. A visit occurs when a user is in a single location for a longer period of time. Visits are a lower power alternative for apps that don't need regular updates or don't need to be aware of the user's movements; for example, this API is not suitable to build navigation apps. A great use case for this API is the way Apple predicts travel times based on locations you often visit. If you regularly travel between your home and work, iOS will suggest a good time to leave your house while taking travel times into account. This is possible by monitoring visits and analyzing the times when the visits occur.

If you start listening for visits, you will only be notified about events that occur after you've begun listening. This means that if the user is already visiting a location when you call startMonitoringVisits(), the visit data will be inaccurate because the start time will be a time after you start listening for visits.

Whenever a visit occurs, the location manager calls locationManager(_:didVisit:) on its delegate. This method receives a CLVisit instance that contains information about the visit. Every visit contains an arrival and departure time, coordinates, and a horizontal accuracy value for the coordinates.

If you use this service, events are usually only delivered if your app is in the foreground. However, if you have requested always access for the user's location and your app is in the location background mode, your app will be awoken or relaunched whenever a visit occurs. This is signified by the same UIApplicationLaunchOptionsLocationKey key in the launch options that's used for other location events.

All location services work in a similar way through a CLLocationManager instance. This makes working with CoreLocation a pleasant experience, and it allows you to master the classes and services that are made available, without too much effort. Studying the documentation will often give you answers to any questions you might have regarding location services on iOS. Remember that most APIs require you to check their availability before you use them because not all devices support the same location services.

Now that you're up to date with everything CoreLocation has to offer, let's add the final touches to the ArtApp login screen. We'll push the user's location to the screen and display a fictional number of nearby pieces of art for our user. In reality, you would probably set up a backend call that returns the actual number of nearby art pieces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.232.239