Swift 5 – it’s alive!

its-alive.jpgIt’s been a couple of weeks now since Swift 5 has arrived, with a bunch of features, such as:

  • ABI stability
  • source compatibility
  • isMultiple – a fancy technique for checking if a number is a multiple of another number.
  • Dictionary compactMapValues – finally we can properly compactMap a dictionary!
  • Result type – handle results from aynchronous code in a much cleaner way.
  • Raw strings – again Swift has had some enhancement to handling Strings.
  • And more!

If you want to a refresher on one or more of these features, I go into them in detail in a new Medium post, you can check it out here.

Tagged with:
Posted in Swift

Optionals in Swift explained – with a cat in a box!

If you’ve come to Swift from a language that doesn’t use optionals, you might find the concept a little strange and the syntax unfamiliar at first. Sometimes it helps to learn the unfamiliar with something familiar – in my lightning tour of optionals, I explain optionals with a cat in a box!

So – imagine you have a box:

ch2_08_cat1.png

Inside the box you know you have one of two things – either no cat…

ch2_08_cat3_open.png

…or a cat!

ch2_08_cat4_open.png

What you have just imagined, is a Cat optional!

ch2_08_cat2.png

As you can see, you declare an optional with the data type of its contents (Cat in this example), followed by a question mark:

var cat:Cat?

But now that we have a Cat optional, how do we get at its contents – or put another way – how do we unwrap the box?

Check my lightning tour of Optionals to find out:

Tagged with:
Posted in Swift

Get a continent from longitude-latitude

For a project I’m working on I needed to know if a point (latitude,longitude) was in a continent, and if so, which one!

To do this, first I set up a Continent enum to work with, making it a String (to easily print) and CaseIterable (to iterate through the cases later):

enum Continent:String,CaseIterable {

    case australia
    case northAmerica
    case southAmerica
    case africa
    case europe
    case asia
    case antarctica
}

I found this stackoverflow post, which though it wasn’t written in Swift, gave some very general polygons that vaguely encompassed the seven continents, exactly what I needed:

m2fo5

I first set up a utility method to convert a latitude and longitude array into a GMSMutablePath. (I’m going to solve this using the Google Maps SDK)

I then used the polygons from the StackOverflow post and converted the Latitude and Longitude arrays into paths in static variables in the Continent enum:

Great! Now seeing if a point is inside a polygon is as simple as comparing a CLLocationCoordinate2D property with a GMSMutablePath property, and you can do that with the GMSGeometryContainsLocation function.

I set up a contains function in the enum that checks if a continent contains the point:

func contains(point:CLLocationCoordinate2D)->Bool {
    switch self {
    case .australia:
        return GMSGeometryContainsLocation(point, Continent.australiaPath, true)
    case .northAmerica:
        return GMSGeometryContainsLocation(point, Continent.northAmericaPath, true) ||
            GMSGeometryContainsLocation(point, Continent.northAmerica2Path, true)
    case .southAmerica:
        return GMSGeometryContainsLocation(point, Continent.southAmericaPath, true)
    case .africa:
        return GMSGeometryContainsLocation(point, Continent.africaPath, true)
    case .europe:
        return GMSGeometryContainsLocation(point, Continent.europePath, true)
    case .asia:
        return GMSGeometryContainsLocation(point, Continent.asiaPath, true) || GMSGeometryContainsLocation(point, Continent.asia2Path, true)
    case .antarctica:
        return GMSGeometryContainsLocation(point, Continent.antarcticaPath, true)
    }
}

North America and Asia both have two have polgyons that represent their areas.

So now, to determine which continent contains a point, all we have to do is iterate through the cases of the Continent enum, calling the contains method. I set up a getContinent method to do this:

static func getContinent(at point:CLLocationCoordinate2D)->Continent? {
    for continent in Continent.allCases {
        if continent.contains(point: point) {
            return continent
        }
    }
    return nil
}

To see how this could be used, imagine you’ve set up a map view in a view controller, let’s call it MapViewController, and we’ve set the view controller as the map view’s delegate. You could set up the mapView:didTapAt method to be called when the user taps on the map. We could use the coordinate property passed into the method to determine which continent the user has tapped, simply by passing the coordinate property into the Continent.getContinent method:

extension TestMapViewController:GMSMapViewDelegate {
    func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
        if let continent = Continent.getContinent(at: coordinate) {
            print(continent)
        }
}
Tagged with: ,
Posted in Swift

Getting the keyboard height

Some time ago I answered a question in StackOverflow comparing the keyboard height on the iPhone X and the iPhone 8:

I got these heights by:
1. Adding an observer of the UIResponder.keyboardWillShowNotification notification:

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)

(In fact, if we want all keyboard frame changes, we should probably be observing the keyboardWillChangeFrameNotification notification, I discuss this in my book.)

2. Adding the selector function to be called:

@objc func keyboardWillShow(_ notification: NSNotification) {
}

3. Then, within this function, we need to dig down a crazy chain of downcasts and optional unwrapping from the userInfo dictionary to extract the height out of the notification object:

if let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
      print(keyboardRect.height)
}

Done! We have the keyboard height.

The person I was answering on StackOverflow was trying to display a view directly above the keyboard, and was confused about why it wasn’t displaying in the correct place. (I believe there was some confusion because of the safe area inset on the iPhone X.)

To illustrate that displaying the view was possible, I set up a red rectangle directly above the keyboard. I was recently asked on Twitter how I did this:

Well, pretty straightforward, actually. Now we have the keyboardRect, all I had to do was set up a CGRect just above it, convert that to a CAShapeLayer, give it a strokeColor and lineWidth, and add it to the view’s layer.

      let newRect = CGRect(x: 0, y: view.frame.height - keyboardRect.height - 100, width: keyboardRect.width, height: 100)
      let rectLayer = CAShapeLayer()
      rectLayer.path = CGPath(rect: newRect, transform: nil)
      rectLayer.strokeColor = UIColor.red.cgColor
      rectLayer.lineWidth = 5
      view.layer.addSublayer(rectLayer)
Tagged with:
Posted in Swift

Lightning tour of closures in Swift

lightningTourClosures.png

Check out this video tutorial with a lightning tour of closures in Swift. It covers:

  • Functions as data types
  • Basic closure syntax
  • Trailing closures

 

If you’re interested in more of these sorts of videos, I’ll be posting frequently over on the iOS Development with Swift YouTube channel, you can subscribe here. I’ll be posting about closures next!

You could also join the video course and you’ll have access to all 8 hours of video tutorials, exercises and more!

Tagged with: ,
Posted in Swift

Overloading functions with default parameter values

Here’s a Swifty conundrum for you!

First a quick recap. Here’s a simple function with no parameters. Call it and “Hello World” will print to the console.

Screenshot 2019-01-09 13.27.42.png

Here’s another function with the same name. This time it has a parameter with a default value. We can call it in exactly the same way, and the default value will be printed to the console.

Screenshot 2019-01-09 13.27.25.png

Here’s the conundrum.

What if both functions exist, as overloaded functions?

screenshot 2019-01-09 13.18.55

What would we expect to see in the console?

We have two overloaded functions, one with a parameter with default values and another with no parameters. The call to the function could theoretically call either, but the question is, which does the compiler choose to call?

Need some thinking time?

giphy.gif

So! It turns out that the console will display “Hello Mars”!

The rationale seems to be that the function with fewer parameters is called. I’m not able to find a discussion of the Swift team’s reasoning behind this, but it is consistent with the ‘overload resolution’ decision made by the C# compiler. The philosophy is described here in Microsoft docs:

If two candidates are judged to be equally good, preference goes to a candidate that does not have optional parameters for which arguments were omitted in the call.

(“Optional parameters” in C# parlance are the equivalent of parameters with default values in Swift.)

If you would like a quick recap of overloading functions and default parameter values, check my previous blog post “Lightning tour of functions in Swift“.

Tagged with: , ,
Posted in Swift

Lightning tour of functions in Swift

lightningTourFunctions.png

Check out this video tutorial with a lightning tour of functions in Swift. It covers:

  • Parameter default values
  • Optional function parameters
  • Overloading a function

livevideo-ios-development-with-swift-in-motion (2)

If you’re interested in more of these sorts of videos, I’ll be posting frequently over on the iOS Development with Swift YouTube channel, you can subscribe here. I’ll be posting about closures next!

You could also join the video course and you’ll have access to all 8 hours of video tutorials, exercises and more!

Tagged with: , , , ,
Posted in Swift