Skip to main content

Where is my iPhone?

·3 mins

A Tutorial on CoreLocation #

This post is a tutorial on how to retrieve the geographical location of an iPhone or iPad by using Apples CoreLocation framework.

I first walk through all the steps necessary to setup a listener for changes of the devices location.

At the end of this post I will wrap everything into a handy Combine Publisher.

Feel free to buy me a coffee if you liked this post.

The LocationManager #

The main class that does all the work is CLLocationManager. Let’s instantiate one.

private let locationManager = CLLocationManager()

A delegate is needed that will receive the location updates.

self.locationManager.delegate = self

The delegate needs to implement the following function. I just made a simple example of printing the received longitude and latitude.

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }
    print("Longitude: \(location.coordinate.longitude)")
    print("Latitude: \(location.coordinate.latitude)")
}

Then we set the desired accuracy. There are plenty of options of what to set and I still need to figure out of how they behave.

self.locationManager.desiredAccuracy = kCLLocationAccuracyBest

The accuracy also relates to when the app is supposed or allowed to track the location.

In my case for example, I want to track the location even when the app is not actively in use.

The app has to request for this. Therefore we have to make the following call.

self.locationManager.requestAlwaysAuthorization()

We also need to set some keys in the Info.plist. These are required, without them to tracking will be available at all.

Configuration

Then, when the user starts the app, she will be prompted to confirm this request.

Request Authorization

Back to our code, there is one more step to make this all work.

self.locationManager.startUpdatingLocation()

Yup, that’s all. We are done.

Wrapping it Up #

As promised in the beginning, I have put the complete code into a class that implements a Combine-Publisher.

In case you are not sure what I am talking about here, please see the Resources-section at the end of the document for some further reading.

import Combine
import CoreLocation
import Foundation

class LocationPublisher: NSObject {

    typealias Output = (longitude: Double, latitude: Double)
    typealias Failure = Never

    private let wrapped = PassthroughSubject<(Output), Failure>()

    private let locationManager = CLLocationManager()

    override init() {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestAlwaysAuthorization()
        self.locationManager.startUpdatingLocation()
    }
}

extension LocationPublisher: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        wrapped.send((longitude: location.coordinate.longitude, latitude: location.coordinate.latitude))
    }
}

extension LocationPublisher: Publisher {
    func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
        wrapped.subscribe(subscriber)
    }
}

Whenever the location changes, the publisher will emit the tuple of longitude and latitude.

We only need to subscribe to these changes in order to process them.

This can be done, by calling sink and passing a function to it.

let locationPublisher = LocationPublisher()
var cancellables = [AnyCancellable]()
locationPublisher.sink(receiveValue: doSomething).store(in: &cancellables)

func doSomething(location: (longitude: Double, latitude: Double)) {
    print("Longitude: \(longitude)")
    print("Latitude: \(latitude)")
}

Done for today! I hoped you liked this post and find it useful.

Feel free to buy me a coffee if you liked this post..

Resources #