To me, thinking of a solution that successfully decouples code is one of the most satisfying aspects of software development. I had that satisfaction recently as I am building a new iOS app that uses CoreLocation. In this post, I will share a little bit about how that app uses CoreLocation, my problem of trying to keep the UIViewController free off a CoreLocation import and my solution using delegates. In addition to that, I have also created a little  Github repo to showcase sample code for the solution, read on to get the link to it.

Background

Ever since I have started working with iOS apps, my goal has been to keep the UIViewController code free of any imports other than UIKit and Foundation. I could successfully accomplish that for the most part, except when it came to working with CoreLocation. For some reason, every solution I came up with, I had to include an “import CoreLocation” in the UIViewController which was against what I wanted.

Motivation

First the personal reason,

In case anyone reading this is thinking, why do I want to achieve this (i.e. decouple code)? The main reason, well I am just lazy, writing the same code again and again is a little boring. Also being the only programmer in my startup means, I have to do all the coding so I just want to avoid re-writing the same code where possible.

Now for an emotionless, more technically sound explanation with examples

Say, if we have a code base (or app) where we can get the user’s location, physical address etc with a simple function call to a LocationHelper class. Then we can do that in in any ViewController or any other class, we don’t care how the location class gets the location as long as we know we will get the location. This means that we can use Apple, Google, Bing Maps or change our method of acquiring the solution without affecting our ViewController code. Lastly, this also means that we can use our LocationHelper class in any app that needs that functionality by importing the LocationHelper code.

Solution

To get the location in iOS, you need to start updating location from a CLLocationManager class after which you get the location coordinates in CLLocationManagerDelegate.didUpdateLocations method. My problem was, even if I wrote a LocationHelper class with a method that calls CLLocationManager.startUpdatingLocation() how do I get the location information from the  CLLocationManagerDelegate without importing a CoreLocation class.

All this time, there was an obvious solution that I simply didn’t see i.e. Delegates.

Delegate pattern

One way of thinking about Delegate pattern is to think of it as an alternative to inheritance. It’s when you delegate the responsibility of achieving something to someone else i.e. for an object to communicate back to it’s parent object. hmm maybe I am not clear? I know what this is, but I can’t think of a “layman’s terms” English explanation for it right now.

The delegate pattern is heavily used in iOS and knowingly or unknowingly every iOS developer has to have used it. For example, UITableViewDelgate, UITextViewDelegate, CLLocationManagerDelegate etc etc. This is why I cannot believe I did not think of this solution sooner! Actually, I can believe that, I had been so occupied with all things Product Management at my startup, that I was unable to just sit-down and give this careful thought.

Delegates with Protocol

A Protocol in Swift is what an interface is in Java i.e. a blue print for methods and properties of a class. In Swift we can also use them to implement Delegate pattern e.g. let’s have a look at the protocol for our solution

protocol LocationUpdatesDelegate {
    func locationUpdated(lat: Double, lon: Double)
}

So when the location is updated, it notifies all the classes that adopt that protocol with the latest location. Let’s see how we use the Protocol, first in the LocationHelper class

var locationManager: CLLocationManager? = nil
var locationUpdatesDelegate: LocationUpdatesDelegate?

override init() {
    super.init()

    locationManager = CLLocationManager()
    locationManager!.requestWhenInUseAuthorization()
    locationManager!.delegate = self
    locationManager!.startUpdatingLocation()
}
//MARK: Location manager delegate methods
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    for location in locations {
        let lat = location.coordinate.latitude
        let lon = location.coordinate.longitude
        locationUpdatesDelegate?.locationUpdated(lat: lat, lon: lon)
    }
}

In the CLLocationManagerDelgate method as soon as we get the location, we call the locationUpdated method of our delegate by passing in the latest coordinates. Next, let’s look at the UIViewController  that adopts that Protocol

import UIKit
class ViewController: UIViewController, LocationUpdatesDelegate {
    var locationHelper: LocationHelper? = nil
    override func viewDidLoad() {
        super.viewDidLoad()
        locationHelper = LocationHelper()
        locationHelper?.locationUpdatesDelegate = self
        // Do any additional setup after loading the view.
    }
    func locationUpdated(lat: Double, lon: Double) {}
}

As you can see our ViewController conforms to our LocationUpdatesDelegate and implements it’s locationUpdated method. Notice, how the lat and lon parameters are of type Double, and not CLLocationCoordinate2D? This is just to keep our UIViewController free of any CoreLocation import. See, UIKit is the only import in our UIViewController class. What happens in the locationUpdated method is explained below.

Like the blog? Subscribe for updates

The physical address

Okay, one last thing, there’s some simple code in that Github repo, that I should explain. Once we get the latitude (lat) and longitude (lon) of the user location, how do we get the physical address of the user? I have defined a struct just for convenience in the LocationHelper called Address

struct Address {
    //setting them as optional because
    //sometimes the GeoCoder cannot find
    //these from a placemark
    var name: String? = nil
    var postCode: String? = nil
    var locality: String? = nil
    var city: String? = nil
    var country: String? = nil
    var state: String? = nil //could be state or province

    func toString() -> String {
        if let n = name,
            let cty = city,
            let ctry = country {
            return "\(n), \(cty) (\(ctry))"
        }
        return "\(name ?? ""), \(locality ?? ""), \(city ?? ""), \(state ?? ""),  \(postCode ?? "") \(country ?? "")"
    }
}

We use Address , as a return type in the getCoordinateAddress completion handler.

func getCoordinateAddress(lat: Double, lon: Double, completion: @escaping (_ address: Address?) -> ()) {
    let location = CLLocation(latitude: lat, longitude: lon)
    CLGeocoder().reverseGeocodeLocation(location) { (placemarks, error) in
        if error != nil {
            return
        }
        if let placesFound = placemarks {
            for place in placesFound {
                var address = Address()
                address.city = place.locality
                address.country = place.country
                address.postCode = place.postalCode
                address.name = place.name
                address.state = place.administrativeArea
                completion(address)
            }
        }
    }
}

A delegate could potentially be used for this too but for this one, we don’t need to. I mean the completion handler works just fine. Lastly, here’s how we use that in our UIViewController

class ViewController: UIViewController, LocationUpdatesDelegate {        
    @IBOutlet var locationLbl: UILabel!
    var address: Address? = nil {
        willSet {
            locationLbl.text = newValue?.toString()
        }
    }
    func locationUpdated(lat: Double, lon: Double) {
        locationHelper?.getCoordinateAddress(lat: lat, lon: lon, completion: { (reverseGeocodedAddress) in
            if reverseGeocodedAddress != nil {
                self.address = reverseGeocodedAddress
            }
        })
    }
}

The above code is self explanatory, as we just use one of Swift’s property observer to se the text for a UILabel.

That’s a wrap! You can find all the code for this post on this Github repository here. Have a look at the LocationHelper class, you could jus re-use it or use as a starting point in your iOS app. Give that Github repo a star if you find it useful.

As usual, if you find any of my posts useful support us by  buying or even trying one of our products and leave us a review on the app store.

My Day To-Do Lite - Task list
My Day To-Do Lite - Task list
Snap! I was there
Snap! I was there
Developer: Bhuman Soni
Price: $0.99
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
  • Snap! I was there Screenshot
Simple 'N' Easy Task List
Simple 'N' Easy Task List
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
  • Simple 'N' Easy Task List Screenshot
Captain's Personal Log
Captain's Personal Log
Developer: Bhuman Soni
Price: $4.99
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
  • Captain's Personal Log Screenshot
Categories: iOSSwiftUI

Leave a Reply

Your email address will not be published. Required fields are marked *