UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Key points

Before you continue to the next milestone, there are two things I’d like to discuss briefly.

First, project 22 introduced Core Location to enable scanning for iBeacons. That’s just one of several things that Core Location does, and I couldn’t possibly continue without at least giving you a taste of the others. For example, Core Location’s functionality includes:

  • Providing co-ordinates for the user’s location at a granularity you specify.
  • Tracking the places the user has visited.
  • Indoor location, even down to what floor a user is on, for locations that have been configured by Apple.
  • Geocoding, which converts co-ordinates to user-friendly names like cities and streets.

Using these things starts with what you have already: modifying the Info.plist to provide a description of how you intend to use location data, then requesting permission. If you intend to use visit tracking you should request the “always” permission because visits are delivered to you in the background.

Once you have permission, try using this to get the user’s location just once, rather than ongoing:

locationManager = CLLocationManager()
manager.delegate = self

    // request the user's coordinates
locationManager.requestLocation()

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.first {
            print("Found user's location: \(location)")
    }
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print("Failed to find user's location: \(error.localizedDescription)")
}

You can also request visit monitoring, like this:

// start monitoring visits
locationManager.startMonitoringVisits()

When the user arrives or departs from a location, you’ll get a callback method if you implement it. The method is the same regardless of whether the user arrived or departed at the location, so you need to check the departureDate property to decide.

Here’s an example to get you started:

func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {
    if visit.departureDate == Date.distantFuture {
        print("User arrived at location \(visit.coordinate) at time \(visit.arrivalDate)")
    } else {
        print("User departed location \(visit.coordinate) at time \(visit.departureDate)")
    }
}

Note: the definition of a “visit” is pretty vague because iOS can’t tell whether the user has walked into a store or is just standing at a bus stop or sitting in traffic.

The second thing I’d like to discuss further is Swift extensions. These are extraordinarily powerful, because you can extend specific types (e.g. Int and String) but also whole protocols of types (e.g. “all collections”.) Protocol extensions allow us to build up functionality extremely quickly, and using it extensively – a technique known as protocol-oriented programming – is common.

We just wrote several extensions on String, which is what we call a concrete data type – a thing you can actually make. We can write extensions for other concrete types like Int, like this:

extension Int {
    var isOdd: Bool {
        return !self.isMultiple(of: 2)
    }

    var isEven: Bool {
        return self.isMultiple(of: 2)
    }
}

However, that will only extend Int – Swift has a variety of different sizes and types of integers to handle very specific situations. For example, ‌Int8 is a very small integer that holds between -128 and 127, for times when you don’t need much data but space is really restricted. Or there’s UInt64, which holds much larger numbers than a regular Int, but those numbers must always be positive.

Making extensions for whole protocols at once adds our functionality to many places, which in the case of integers means we can add isOdd and isEven to Int, Int8, UInt64, and more by extending the BinaryInteger protocol that covers them all:

extension BinaryInteger {
    var isOdd: Bool {
        return !self.isMultiple(of: 2)
    }

    var isEven: Bool {
        return self.isMultiple(of: 2)
    }
}

However, where things get really interesting is if when we want only a subset of a protocol to be extended. For example, Swift has a Collection protocol that covers arrays, dictionaries, sets, and more, and if we wanted to write a method that counted how many odd and even numbers it held we might start by writing something like this:

extension Collection {
    func countOddEven() -> (odd: Int, even: Int) {
        // start with 0 even and odd
        var even = 0
        var odd = 0

        // go over all values
        for val in self {
            if val.isMultiple(of: 2) {
                // this is even; add one to our even count
                even += 1
            } else {
                // this must be odd; add one to our odd count                
                odd += 1
            }
        }

        // send back our counts as a tuple
        return (odd, even)
    }
}

However, that code won’t work. You see, we’re trying to extend all collections, which means we’re asking Swift to make the method available on arrays like this one:

let names = ["Arya", "Bran", "Rickon", "Robb", "Sansa"]

That array contains strings, and we can’t check whether a string is a multiple of 2 – it just doesn’t make sense.

What we mean to say is “add this method to all collections that contain integers, regardless of that integer type.” To make this work, you need to specify a where clause to filter where the extension is applied: we want this extension only for collections where the elements inside that collection conform to the BinaryInteger protocol.

This is actually surprisingly easy to do – just modify the extension to this:

extension Collection where Element: BinaryInteger {

As you’ll learn, these extension constraints are extraordinarily powerful, particularly when you constrain using a protocol rather than specific type. For example, if you extend Array so that your methods only apply to arrays that hold Comparable objects, the methods in that extension gain access to a whole range of built-in methods such as firstIndex(of:), contains(), sort(), and more – because Swift knows the elements must all conform to Comparable.

If you want to try such a constraint yourself – and trust me, you’ll need it for one of the challenges coming up! – write your extensions like this:

extension Array where Element: Comparable {
    func doStuff(with: Element) {
    }
}

Inside the doStuff() method, Swift will ensure that Element automatically means whatever type of element the array holds.

That’s just a teaser of what’s to come once your Swift skills advance a little further, but I hope you’re starting to see why Swift is called a protocol-oriented programming language – you can extend specific types if you want to, but it’s far more efficient – and powerful! – to extend whole groups of them at once.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.