Tracking the location of an iOS device involves Global Positioning System (GPS), cell ID location, and WiFi positioning service (WPS). By using three different services, Apple’s Core Location framework can pinpoint the location of an iOS device with varying degrees of accuracy.
Fortunately, Core Location hides the details of using these various technologies. Instead, Core Location lets you simply specify the degree of accuracy you wish, such as finding the location of an iOS device within 10 or 200 meters while also detecting any changes in the location of an iOS device. By tracking locations within a specified degree of accuracy and the distance an iOS device must travel before detecting movement, Core Location makes it easy for any app to identify the location of any iOS device.
The first step to using Core Location is to import the Core Location framework into an app like this:
import CoreLocation
After importing the Core Location framework, the next step is to access the location manager with any arbitrary name such as locationManager
like this:
let locationManager = CLLocationManager()
A class needs to conform to the CLLocationManagerDelegate
protocol, which you can do in one of two ways. First, you can simply add this to the class line like this:
class ViewController: UIViewController, CLLocationManagerDelegate {
Then you can declare that this class is the CLLocationManagerDelegate
inside the viewDidLoad
method:
override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self }
The other way to conform to the CLLocationManagerDelegate
protocol is to use an extension at the end of the class ViewController file like this:
extension ViewController: CLLocationManagerDelegate { }
Then you can declare that this class is the CLLocationManagerDelegate
inside the viewDidLoad
method:
override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self as? CLLocationManagerDelegate }
When using Core Location, you need to define the amount of accuracy you want. Remember, the greater the accuracy, the more power the iOS device will require so it’s best to choose the level of accuracy your app absolutely needs. If you just need to identify the user’s geographical location such as a city, then you don’t need specific accuracy. However, if your app needs to know the iOS device’s precise location to locate the user such as for a ride-sharing service that needs to know where to pick up a passenger, then you’ll need greater precision.
You can define a specific level of accuracy in meters such as 150 meters. However, Core Location provides several constants you can use that define varying degrees of accuracy:
kCLLocationAccuracyBestForNavigation
. The highest possible accuracy used for navigation appskCLLocationAccuracyNearestTenMeters
. Accurate to within 10 meterskCLLocationAccuracyHundredMeters
. Accurate to within 100 meterskCLLocationAccuracyKilometer
. Accurate to the nearest kilometerkCLLocationAccuracyThreeKilometers
. Accurate to the nearest 3 kilometersTo define accuracy, you need to set the desiredAccuracy
property to a value or to one of the preceding constants like this:
locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters
In addition to defining the accuracy you want, you can also define a distance filter that specifies how far the iOS device needs to move to detect movement. The default value is stored in a constant called kCLDistanceFilterNone
, which tells an app to be notified of all movement.
However, if you define a specific value in meters, you can modify this distance filter such as only detecting movement when an iOS device travels 100 meters such as
locationManager.distanceFilter = 100
Requesting a Location
Core Location gives you two ways to request the location of an iOS device. The first method requests the location once. This can be useful for apps that don’t need constant updating to track movement. To request location once, use the requestLocation
method like this:
locationManager.requestLocation()
Because the requestLocation
method only checks for a location once, it requires far less power than the second method, which requests locations continuously.
To track locations continuously, you need to use the startUpdatingLocation
and stopUpdatingLocation
methods like this:
locationManager.startUpdatingLocation() locationManager.stopUpdatingLocation()
Core Location also offers two Boolean values you can modify as follows:
pausesLocationUpdatesAutomatically
. Allows an app to temporarily pause updating a locationallowsBackgroundLocationUpdates
. Defines whether an app can continue receiving location updates even when the app is suspendedRetrieving Location Data
When Core Location retrieves the location of an iOS device, it provides several different types of values:
coordinate.latitude
and coordinate.longitude
. Returns the latitude and longitude of a location.horizontalAccuracy
. Returns a distance of how accurate Core Location believes the defined location might be, measured in meters.altitude
. Returns the distance above or below sea level, measured in meters.verticalAccuracy
. Returns a distance of how accurate Core Location believes the altitude might be, measured in meters.floor
. Returns the floor of a building where the iOS device is located.timestamp
. Returns the time the location was retrieved.func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { if (error as NSError).code == CLError.locationUnknown.rawValue { return } print("didFailWithError \(error.localizedDescription)") } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let newLocation = locations.last! print("didUpdateLocations \(newLocation)") }
Some of the possible Core Location errors:
CLError.locationUnknown
— the location is currently unknown, but Core Location will keep trying.CLError.denied
— the user denied the app permission to use location services.CLError.network
— there was a network-related error.Requesting Authorization
Apps often need to request permission to access many hardware features of an iOS device. By forcing an app to request permission, Apple wants to make sure users authorize an app’s access to features such as the camera, the microphone, and the device’s location. Requesting authorization provides privacy for users and allows them to know exactly when an app might need to request access to specific hardware features.
Any app that uses Core Location must request authorization to track an iOS device’s location. Core Location provides two ways to request authorization:
requestWhenInUseAuthorization()
. Uses location services only when your app is running.requestAlwaysAuthorization()
. Uses location services all the time.In most cases, you’ll only want to use location services while your app is running. Besides using one of the preceding methods, an app also needs to modify its Info.plist
file and add the Privacy – Location When In Use Usage Description key. In addition, you’ll need to add descriptive text explaining why your app needs to access location services.
let authStatus = CLLocationManager.authorizationStatus() if authStatus == .notDetermined { locationManager.requestWhenInUseAuthorization() return }
This checks the current authorization status. If it is .notDetermined
— meaning that this app has not asked for permission yet — then the app will request "When In Use" authorization. That allows the app to get location updates while it is open and the user is interacting with it.
if authStatus == .denied || authStatus == .restricted { showLocationServicesDeniedAlert() return }
Adding a Map
While you could display location data as text, you’ll more likely want to display a location visually on a map. To do this, you need to use a Map Kit View, which displays a map on the screen. Then you’ll need to import the MapKit framework such as
import MapKit
Finally, you’ll need to make the Map Kit View display the current location. To do this, you just need to set the showsUserLocation
property to true
such as
@IBOutlet var mapView: MKMapView! mapView.showsUserLocation = true
To see how to identify the location of an iOS device and display it on a map, follow these steps:
class ViewController
line in the ViewController.swift file.mapView
, and click the Connect button. Xcode creates the following IBOutlet:@IBOutlet var mapView: MKMapView!
class ViewController
line in the ViewController.swift file.myTextView
, and click the Connect button. Xcode creates the following IBOutlet:@IBOutlet var myTextView: UITextView!
import CoreLocation import MapKit
Add the following under the IBOutlets:
let locationManager = CLLocationManager()
Edit the class ViewController line as follows:
class ViewController: UIViewController, CLLocationManagerDelegate {
This makes the ViewController.swift file the CLLocationManagerDelegate
. That means we need to define the ViewController.swift file as the delegate later.
Edit the viewDidLoad
method as follows:
override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() mapView.showsUserLocation = true }
This code makes the ViewController.swift file the CLLocationManager
delegate. Then it defines the accuracy to 10 meters. The next line requests authorization to use location services, which means we’ll need to edit the Info.plist file later.
The startUpdatingLocation()
method retrieves location data, while the showsUserLocation
property is set to true
to allow the Map Kit View to display the location. The entire ViewController. swift file should look like this:
import UIKit import CoreLocation import MapKit class ViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet var myTextView: UITextView! @IBOutlet var mapView: MKMapView! let locationManager = CLLocationManager() override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() mapView.showsUserLocation = true } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let newLocation = locations.last { let latitudeString = "\(newLocation.coordinate.latitude)" let longitudeString = "\(newLocation.coordinate.longitude)" myTextView.text = "Latitude: " + latitudeString + "\ nLongitude: " + longitudeString } } }
Zooming in a Location
Although Core Location can find coordinates to our current location (or a simulated location such as Apple’s headquarters), the app currently displays the location on a large map. While the user could pinch to zoom in, ideally the app should display a closer view of our location automatically.
To do this, not only do we need to know a location, but we also need to define a region to show around that location. Defining a region around a location involves defining the following:
latitudeDelta
. Measures north-to-south distance (measured in degrees) to display.longitudeDelta
. Measures east-to-west distance (measured in degrees) to display.To see how to zoom in on a location, follow these steps:
Edit the class ViewController line as follows:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
The MKMapViewDelegate
gives us access to a mapView
function that will let us zoom in to the defined location. After defining a MKMapViewDelegate
, the next step is to make sure the map knows that the ViewController.swift file is the delegate.
Edit the viewDidLoad
method by adding the mapView.delegate = self
line at the end as follows:
override func viewDidLoad() { super.viewDidLoad() locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() mapView.showsUserLocation = true mapView.delegate = self }
Add the following mapView
function:
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) { let zoomArea = MKCoordinateRegion(center: self.mapView.userLocation.coordinate, span: MKCoordinateSpan (latitudeDelta: 0.05, longitudeDelta: 0.05)) self.mapView.setRegion(zoomArea, animated: true) }
Adding Annotations
An annotation allows the user to identify a location and place a cartoon pin on a map along with descriptive text. An annotation needs a location, which we can define by wherever the user presses on the map for an extended period of time, known as a long press.
Once we know where the user pressed on the map, we can display the annotation by adding it to the map along with any additional text. In addition, we’ll store the annotations in an array and include a button to clear the annotations from the map.
Add the following under the IBOutlets
to define an array to hold all annotations added to the map:
var myAnnotations = [CLLocation]()
Edit the viewDidLoad
method to recognize a long press gesture and add it to the map view as follows:
override func viewDidLoad() { super.viewDidLoad() locationManager.delegate = self locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() mapView.showsUserLocation = true mapView.delegate = self let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(addPin(longGesture:))) mapView.addGestureRecognizer(longGesture) }
This long press gesture defines a function called addPin
to respond to a long press, which means we now need to create that addPin
function.
Add the following function under the viewDidLoad
method:
@objc func addPin(longGesture: UIGestureRecognizer) { let touchPoint = longGesture.location(in: mapView) let touchLocation = mapView.convert(touchPoint, toCoordinateFrom: mapView) let location = CLLocation(latitude: touchLocation.latitude, longitude: touchLocation.longitude) let myAnnotation = MKPointAnnotation() myAnnotation.coordinate = touchLocation myAnnotation.title = "\(touchLocation.latitude) \ (touchLocation.longitude)" myAnnotations.append(location) self.mapView.addAnnotation(myAnnotation) }
Click the Library icon and drag and drop a button on the user interface such as between the map view and the text view.
Double-click the button, type Clear Pins, and press Enter.
Edit this clearPins
IBAction method as follows:
@IBAction func clearPins(_ sender: UIButton) { mapView.removeAnnotations(mapView.annotations) myAnnotations.removeAll() }
Stopping location updates
If obtaining a location appears to be impossible for wherever the user currently is on the globe, then you need to tell the location manager to stop. To conserve battery power, the app should power down the iPhone’s radios as soon as it doesn’t need them anymore.
func startLocationManager() { if CLLocationManager.locationServicesEnabled() { locationManager.delegate = self locationManager.desiredAccuracy = .kCLLocationAccuracyNearestTenMeters locationManager.startUpdatingLocation() updatingLocation = true } } func stopLocationManager() { if updatingLocation { locationManager.stopUpdatingLocation() locationManager.delegate = nil updatingLocation = false } }
Reverse geocoding
Using a process known as reverse geocoding, you can turn a set of coordinates into a human-readable address. You’ll use the CLGeocoder
object to turn the location data into a human-readable address and then display that address on screen.
let geocoder = CLGeocoder() geocoder.reverseGeocodeLocation(newLocation, completionHandler: { placemarks, error in if let error = error { print("Reverse Geocoding error: \ (error.localizedDescription)") return } if let places = placemarks { print("Found places: \(places)") } })