Property Wrappers and User Defaults

I love magical new features that help us abstract away boiler-plate code and let us focus on the unique features of our apps. Property wrappers is such a feature, and seriously – it just feels like magic!

mervyn-chan-RFXxBTHze_M-unsplash

Let’s take a look at an example to see how property wrappers work.

User defaults: Level 1

A reminder, the simplest way user defaults would work is to first set up some sort of a key:

enum Keys { //enum to prevent instantiation
  static let beenHereBefore = "Been Here Before"
}

You could then use this to store or retrieve the user default:

//Retrieve user default:
if !UserDefaults.standard.bool(forKey: Keys.beenHereBefore) {
  //Onboarding could go here for example
}
//Store user default:
UserDefaults.standard.set(true, forKey: Keys.beenHereBefore)

Great! But wouldn’t it be nicer if we could just set and retrieve the property without worrying about the underlying user defaults implementation?

User defaults: Level 2

Instead, let’s abstract away the User defaults code into a GlobalSettings type:

//Retrieve user default:
if !GlobalSettings.beenHereBefore {
  //Onboarding could go here for example
}
//Store user default:
GlobalSettings.beenHereBefore = true

Much more readable, right?

But of course, we now need to set up the GlobalSettings type:

enum GlobalSettings { //enum to prevent instantiation
    enum Keys {
        static let beenHereBefore = "Been Here Before"
    }
    static var beenHereBefore:Bool {
        get {
            return UserDefaults.standard.bool(forKey: Keys.beenHereBefore)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: Keys.beenHereBefore)
        }
    }
}

Now that’s fine for one user default, but what if we have several? We would have to duplicate this code defining a key and setting and retrieving the UserDefaults for each property.
Could we abstract this code even further? Well, here’s where we can see the magic of property wrappers!

Screenshot 2019-09-02 21.01.33

User defaults: Boss Level! (using Property Wrappers)

A property wrapper is a special attribute you can create and apply to a property that automatically runs a bunch of code behind the scenes for the property.

Let’s move the User Defaults get and set code to a property wrapper.
*Disclaimer: the code for Persist is based on sample code in the Swift Evolution proposal for Property Wrappers, proposal 258.

Let’s give the property wrapper the name Persist:

@propertyWrapper
struct Persist<T> {
  let key: String
  let defaultValue: T

  var wrappedValue: T {
    get {
      return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
    }
    set {
      UserDefaults.standard.set(newValue, forKey: key)
    }
  }
}

To define the property wrapper we follow three essential steps:

1. Create a type (in this case, a struct). The name of our type becomes the name of the property wrapper.
2. Prefix the type with the @propertyWrapper attribute.
3. Include a ​wrappedValue property. In this case this a generic property wrapper, so the wrappedValue property is defined as a generic. To perform an action when this property is retrieved or stored, it is defined as a computed property with both a getter and a setter.

In this case, as UserDefaults needs a key to store and retrieve a property, a key property has been defined in the Persist struct.

As the property wrapper is defined as generic, it uses UserDefault‘s generic object method to retrieve a user default. As this method returns an optional, the Persist struct also defines a defaultValue property that will be returned if the value returned by the object method is nil.

Of course, as Persist is a struct, it doesn’t need an initializer specifically defined to intialize these two properties, as a memberwise initializer will be automatically generated.

Now, we can adjust our GlobalSettings type to use the property wrapper:

enum GlobalSettings {
  @Persist(key: "BeenHereBefore", defaultValue: false)
  static var beenHereBefore: Bool
}

Holy moly. You can see how much shorter the declaration of the beenHereBefore property is, now that all of the UserDefaults code has been abstracted to our custom property wrapper Persist.

All we did to add our custom property wrapper was to prefix a variable with an at symbol (@) followed by the name of the wrapper (Persist), followed by any initialization required.
Like magic, the property wrapper code we wrote will now execute for this variable!

We could easily add a bunch of user defaults to our GlobalSettings type, with minimal additional lines of code.

enum GlobalSettings {
  @Persist(key: "BeenHereBefore", defaultValue: false)
  static var beenHereBefore: Bool
  @Persist(key: "TopScore", defaultValue: 0)
  static var topScore: Int
  @Persist(key: "UserName", defaultValue: "Anon")
  static var userName: String
}

As all the code dealing with persisting data is now in our Persist property wrapper, if we wanted to make adjustments to how this works, we would only have to make this change in one place. Let’s say we change our mind and decide to use iCloud’s NSUbiquitousKeyStore container to persist our data, instead of UserDefaults. Making this change would be straight-forward:

@propertyWrapper
struct Persist {
  let key: String
  let defaultValue: T

  var wrappedValue: T {
    get {
        return NSUbiquitousKeyValueStore.default.object(forKey: key) as? T ?? defaultValue
    }
    set {
        NSUbiquitousKeyValueStore.default.set(newValue, forKey: key)
    }
  }
}

As I mentioned, you can read more about property wrappers in Swift Evolution proposal 258. You can also see more about it in WWDC video Modern Swift API Design. Just be cautious, back then wrappedValue was simply known as value. (it was renamed 8 days after the talk – ooh that would be frustrating!)

Enjoy playing with property wrappers, let me know what magical property wrappers you create!

Tagged with: ,
Posted in Swift

SF Symbols in iOS 13

I was playing with the amazing SwiftUI with Apple’s tutorials (stay tuned, I hope to write something on SwiftUI soon) when I noticed something interesting:

Screen Shot 2019-06-06 at 8.55.02 pm.png

Where does this “star.fill” come from, I wondered… There’s no “star.fill” in the asset catalog?! It turns out the clue was in the initializer parameter “systemName”.

So from iOS 13, UIImage can now be initialized with systemName, which generates a “system symbol image”.  What are these “system symbol images”? Well, Apple has provided a program called “SF Symbols” that lets you browse all 1,500+ of them! You can download the program at their Apple Design Resources page.

Screen Shot 2019-06-07 at 9.16.15 am.png

Here are just some of the SF Symbols!

And it couldn’t be easier to use:

Screen Shot 2019-06-06 at 11.33.39 pm.png

There is a WWDC session on SF symbols well worth checking out here. You’ll also find Apple’s guidelines for using SF Symbols in the Human Interface Guidelines here. You’ll also find guidance there for building your own SF Symbol, using the SF Symbols program.

In the SF Symbols program you’ll notice you can play with the weight and scale of the image. You’ll find an example of these in the Human Interface Guidelines:

sf-symbols-scales-weights_2x.png

You can adjust the weight and scale of the image, along with its pointSize, using a config property:

Screen Shot 2019-06-06 at 11.44.22 pm.png

It should be noted that pointSize refers to a typographical point size – the same point size a font uses – rather than points on the screen.

Why have both pointSize and scale, you might ask. Well, the scale property allows you to adjust the scale of a symbol in relation to the text around it without having to adjust the pointSize. Perhaps you might be lining up an image with a label, adding an image to a button, or even inserting an SF symbol in a string! It should be mentioned that inserting an SF symbol into a string isn’t working yet for the current beta of Xcode 11, but it sounds very promising, adjusting the style of the symbol to the text surrounding it!

Having point size available is great, but we generally try to avoid specifying a point size with text — instead we prefer to use text styles, such as Body, Headline, etc where possible to take advantage of dynamic type. This is possible with SF symbols too — instead of specifying the point size, you can create the config property using a text style, just as you might with a label or text field:

Screen Shot 2019-06-07 at 1.18.28 pm.png

By the way, so far we’ve been looking at how to add SF symbols in code. We also saw at the start of this post that you can also include SF symbols in SwiftUI views.You might be interested to know you can also add them in the storyboard:

Screen Shot 2019-06-07 at 12.08.55 pm.png

Of course, as we saw at the start of this post, you can also include SF symbols in SwiftUI views.

Well, that’s the basics – there’s a lot more to look at – such as alignment, automatic configuration based on size class, making your app with SF symbols compatible with devices with < iOS 13, and building your own SF symbols. Again, I recommend you check out the WWDC session on SF symbols here.

In conclusion I think this is a really welcome addition to Xcode/iOS – apps will have a more consistent look across iOS, and I think SF symbols should make life much easier for us – less time for example, taken up sourcing icons and finding (or designing) a variety of sizes and weights. What do you think?

Tagged with: , , , ,
Posted in Swift

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