Adding haptic feedback to your app with CoreHaptics

One of iOS 13's more subtle, yet amazingly fun and powerful frameworks is CoreHaptics. With this framework, you can add tactile feedback to your app. When implemented correctly, this kind of feedback will delight and amaze your users. It will make your app feel alive like it's physically responsive to the touch input from your user. If you want to see what haptic feedback feels like in a real app, check out the CocoaHub app. It uses haptic feedback when you switch tabs in the app and in my opinion, this feels really nice.

In this blog post you will learn about the following:

  • Understanding the different types of haptic feedback
  • Adding haptic feedback to your app
  • Using AHAP files to define your haptics

By the end of this post, you should be able to add beautiful, elegant haptic experiences to your app that will feel natural and exciting to your users. Because when implemented correctly, haptics can really take any touch to the next level.

Understanding the different types of haptic feedback

Ever since the iPhone 6s, we have been able to add haptics to apps. Haptic feedback is perceived as tiny little nudges or taps that you get from your phone when something happens. It's almost like a very short vibration, except it seems to have a little bit more force to it. The same type of technology that's found in the iPhone 6s and up can be found in the Apple watch and in Apple's trackpads. If you have a MacBook (pro) and you click the trackpad, nothing actually clicks. That's the taptic engine giving you haptic feedback.

The haptics that exist on iOS 12, and in your MacBook's trackpad are fairly limited in what you can with them. With iOS 13 Apple has made a huge change to the haptic capabilities we have. Instead of very simple haptics that can't express much more than that clicking feeling, we can almost make the user feel sound through haptics. This is all enabled through the CoreHaptics framework.

In this framework, we have two key types of haptic feedback, continuous and transient. Transient haptic feedback is like a tap, or a nudge and is typically used when you tap a button, when you need the user's attention or if you want to enrich the visual experience of two objects bumping into each other with some nice haptic feedback. For this kind of haptic feedback, you can configure its intensity to specify how intense the tap should be, and its sharpness. A sharp haptic will feel more urgent to the user, while a less sharp haptic will feel very calm. If this sounds abstract, or strange, I recommend that you come back to this section after playing with haptics a bit. You'll realize that sharp and intense are actually very clever ways to describe what certain haptic feedback feels like.

Continuous haptic feedback is more like a vibration pattern. It still has that typical haptic click feeling, except there are many of them in rapid succession. For a continuous event, you can specify exactly the same parameters that are available for transient feedback, except you can also set the event's duration to determine how long the haptic feedback should keep going for.

It's also possible to define how certain characteristics of a haptic event should change over time. For example, to create an increasing and then decreasing continuous haptic, you would define a single haptic event and apply dynamic parameters or a parameter curve to have the haptic fade in and out over time. I will show an example of both dynamic parameters and parameter curves in the next section.

In addition to tactile feedback, CoreHaptics also has special audio haptic event types. Audio haptic can either be a custom audio file that is played or a generated waveform that is configured similar to a haptic event, except it's played as audio rather than a haptic pulse. You can configure parameters like the audio's pitch, volume and decay to create some very interesting audio effects to accompany your haptic experiences.

Adding haptics to your app

All haptic feedback by CoreHaptics is played through an instance of CHHapticEngine. You can create an instance of the haptic engine as follows:

let engine = try CHHapticEngine()

Note that you should define the haptic engine somewhere where the instance will stay around for as long as you need it. You should avoid creating an engine every time you want to play haptic feedback. Instead, make it a property on the object that will end up managing your haptics implementation so it stays around for a longer time.

You should also set the engine's stoppedHandler and its resetHandler. The stoppedHandler is called in case the haptic engine is stopped due to external factors or if it's finished playing your haptic events. The resetHandler is called if something went wrong and the engine has to be reset. In the reset handler, you will typically want immediately start the engine again.

Before you can play haptic feedback, you must start the haptic engine by calling its start method:

do {
  try engine.start()
} catch {
  // failed to start the engine
}

Once the engine is ready to play haptics, we can define our haptic pattern, ask the haptic engine to create an instance of CHHapticPatternPlayer and then play the haptics. Let's create a simple transient haptic event:

let event = CHHapticEvent(eventType: .hapticTransient, parameters: [
  CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
  CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
], relativeTime: 0)

do {
  let pattern = try CHHapticPattern(events: [event], parameters: [])
  let player = try engine.makePlayer(with: pattern)
  try player?.start(atTime: CHHapticTimeImmediate)
} catch {
  // something went wrong
}

The code above defines a haptic event. Haptic events are the building blocks that can be used to define a haptic pattern. In this case, we have a pattern that only contains a single haptic event. It is possible to, for example, play a transient haptic event first, then play a continuous haptic for a second, and then play another transient haptic event. You can use the relativeTime parameter on the CHHapticEvent initializer to specify an offset for each event. So if you want to play several haptic taps in succession, you would use the relativeTime to make sure each haptic starts when needed rather than playing the entire pattern all at once.

Once the pattern is defined, we can create an instance of CHHapticPattern. The parameters property in the pattern's initializer is an array of CHHapticDynamicParameter items that define how the haptic pattern's intensity, sharpness, and other characteristics should be manipulated over time. I'll show you an example shortly.

Once the pattern is created, we can ask the haptic engine for a player, and we can then play our haptic pattern on the device. You can specify a delay for the player's start time if needed. We use CHHapticTimeImmediate in this example, but you can specify any TimeInterval (or Double) you want. Fairly straightforward, right? In the following examples, I will only show the steps to create the CHHapticPattern. All steps involving obtaining and starting a player are identical for all haptic patterns.

Let's see an example of building a haptic pattern as described earlier where we have a transient event, then a continuous one, and then another transient one:

let events = [
  CHHapticEvent(eventType: .hapticTransient, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
  ], relativeTime: 0),
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.7),
  ], relativeTime: 0.1, duration: 0.5),
  CHHapticEvent(eventType: .hapticTransient, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
  ], relativeTime: 0.7)
]

let pattern = try CHHapticPattern(events: events, parameters: [])

Note that the second haptic event, the continuous one, defines its duration while the transient events don't. This is because a transient haptic event is a single event, that doesn't have a duration. If you try running this pattern on a device, you will feel a short tap, then a sharp buzz, followed by another short tap. Cool, right!

Let's see how we can use parameters to create an interesting continuous haptic pattern.

let events = [
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
  ], relativeTime: 0.1, duration: 3)
]

let parameters = [
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 0.3, relativeTime: 0),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 1, relativeTime: 1),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 0.5, relativeTime: 2),
  CHHapticDynamicParameter(parameterID: .hapticIntensityControl, value: 1, relativeTime: 2.5)]

let pattern = try CHHapticPattern(events: events, parameters: parameters)

Note that the value property of a dynamic parameter is used as a multiplier for the value specified on the parameter it manipulates. So in this case setting a value of 1 on the dynamic pattern equals an intensity of 0.8. A value of 0.5 equals an intensity of 0.4, etc.

If you play this you will notice that the changes for the dynamic parameters are very abrupt. After one second we jump to a higher intensity. One second later, the pattern jumps to a lower intensity and so forth. If you want to apply a more streamlined transition, we can use CHHapticParameterCurve instead of CHHapticDynamicParameter. Let's see how:

let events = [
  CHHapticEvent(eventType: .hapticContinuous, parameters: [
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1),
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
  ], relativeTime: 0.1, duration: 3)
]

let dynamicParameters = [
  CHHapticParameterCurve(parameterID: .hapticIntensityControl,
                         controlPoints: [.init(relativeTime: 0, value: 0),
                                         .init(relativeTime: 1, value: 1),
                                         .init(relativeTime: 2, value: 0.5),
                                         .init(relativeTime: 3, value: 1)],
                         relativeTime: 0)]

let pattern = try CHHapticPattern(events: events, parameterCurves: dynamicParameters)

Everything looks very similar except we define an array of CHHapticParameterCurve instead of CHHapticDynamicParameter. Note that there is a controlPoints argument on the CHHapticParameterCurve initializer. This is where we specify how we want our pattern to change over time. We create instances of CHHapticParameterCurve.ControlPoint to define how the values change over time. Note that I used .init as a shorthand because the real type name is a bit long to type. If you run this pattern on a device, you should notice that the animations are much smoother.

Before we take a look at using AHAP files for haptic, let's also quickly define a custom audio haptic:

let events = [
CHHapticEvent(eventType: .audioContinuous, parameters: [
  CHHapticEventParameter(parameterID: .audioPitch, value: 0.3),
  CHHapticEventParameter(parameterID: .audioVolume, value: 0.6),
  CHHapticEventParameter(parameterID: .decayTime, value: 0.1),
  CHHapticEventParameter(parameterID: .sustained, value: 0)
], relativeTime: 0)
]

let pattern = try CHHapticPattern(events: events, parameters: [])

The process of creating an audio haptic is very similar to creating a tactile haptic. Try running this on your device and you should hear a short beep-like sound. Combining this with a sharp haptic with medium intensity makes for a very interesting effect. Try playing around with combining audio and tactile haptic to create feedback that delights your users.

Using AHAP files to define your haptics

One last thing I want to cover in this already fairly long Quick Tip is using AHAP files to define your haptic in files. Let's look at a very brief example of what the contents of an AHAP file look like:

{
  "Version": 1.0,
  "Metadata": {
    "Project" : "Sample",
    "Created" : "12-11-2019",
    "Description" : "Quick haptic sample"
  },
  "Pattern": [
    { "Event":
      {
        "Time": 0.0,
        "EventType": "HapticTransient",
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 1 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.2 }]
      }
    }
  ]
}

You might notice that the pattern itself looks very similar to the patterns we defined in code. The big difference is that we now have a JSON-like file called an AHAP file that we can edit and update outside of our app. We could even fetch these AHAP files from a remote server to dynamically update the haptics in an app. Let's see how you can play an AHAP file in your app:

guard let path = Bundle.main.path(forResource: "myAhapFile", ofType: "ahap") else {
  return
}

try engine.playPattern(from: URL(fileURLWithPath: path))

All that we need to do to play an AHAP file is load it from the bundle, and pass it to the haptic engine. It will then read the file, create the player and play the pattern specified in your file.

In Summary

First, my apologies for making yet another Quick Tip that's way longer than I intended. There are just so many cool things you can do with haptics and I wanted to make sure that you have all the tools needed to get started with haptics in your own apps. If you need some more inspiration, be sure to check out this WWDC video from 2019 it has many beautiful examples of how haptics improve the user experience.

Also, make sure to check out the documentation for the CoreHaptics framework. There is a lot of good information available, and even a couple of sample apps that will help you to explore haptics and hopefully inspire you to integrate awesome haptic experiences in your app.

If you have questions, feedback or suggestions, don't hesitate to reach out on Twitter.

Expand your learning with my books

Practical Swift Concurrency header image

Learn everything you need to know about Swift Concurrency and how you can use it in your projects with Practical Swift Concurrency. It contains:

  • Eleven chapters worth of content.
  • Sample projects that use the code shown in the chapters.
  • Free updates for future iOS versions.

The book is available as a digital download for just $39.99!

Learn more

Storage options on iOS compared

Developers often need to store data on behalf of their users. Sometimes it’s a small amount simple data, other times it’s a lot of complex data. Maybe it’s highly sensitive data or maybe it’s less sensitive or even publicly available data. Choosing where to store this data is often not trivial, especially if you might be unaware of the options that are available to you, or the security considerations you need to keep in mind.

In this week’s blog post I will show you several storage options that are available to you as an iOS developer, and I'll explain the pros and cons of every storage method.

In this post, you will learn about the following storage mechanisms:

  • UserDefaults
  • Files on disk
  • The Keychain
  • Databases (like SwiftData, Core Data and SQLite)

Disclaimer:
Please note that I am not a security or encryption expert. For this reason, I will only cover every storage mechanism in its “normal”, basic usage. I won’t cover how you can best encrypt data to increase security for any of the listed storage mechanisms. In general, you should at least consider any data that you store on a device accessible by attackers that have access to a physical device.

With that disclaimer out of the way, let’s start exploring storage options on iOS, shall we?

Storing data in UserDefaults

Possibly the most well-known, easiest to use storage on iOS is the user defaults store. You typically use this storage for simple user preferences or to store simple values that help improve the user’s experience. In my blog post on optimizing your app’s App Store reviews, user defaults are used to track several metrics like the number of launches, when the user first launched the app and more to figure out the best moment to show the user a review prompt.

What should be stored in UserDefaults?

You can store a fairly large amount of data in user defaults but it’s not encouraged to store large blobs of data, images and other large data structures in user defaults. On tvOS, the user defaults store is even limited to a maximum 1MB of data and Apple recommends not exceeding 512KB. This amount of storage is a lot of you only store simple data like booleans, numbers or strings because they're small and very well-suited for key-value storage. Anything larger will fill up your store rapidly.

UserDefaults and security

You should never store any sensitive data in user defaults. This storage is not encrypted at all so if an app other than your own obtains access to your user defaults store, your user’s data is compromised.

For example, simple preferences that can’t be used to identify your users are okay to store in user defaults. However, a user’s email address, password and other, similar data, should be stored somewhere more secure.

Reading and writing with UserDefaults

If you wish to use the user defaults store, you do so through the UserDefaults class. You can use the default user store that is always available to your app, or you can create your own user defaults store with its own unique identifier. I always recommend the latter since it makes it somewhat easier to swap your user defaults store out for a different one and it allows you to separate your own user defaults from those that can be accessed by external dependencies that you might include in your app.

Reading from user defaults is really fast because your app's preferences are loaded into memory when your app launches. This means that you don't have to access the underlying disk storage at all to retrieve values using the UserDefaults class.

You can create and use your own user defaults store as follows:

let myDefaults = UserDefaults(suiteName: "com.donnywals.myapp")
myDefaults?.set(true, forKey: "com.donnywals.myapp.user-is-awesome")
let isUserAwesome = myDefaults?.bool(forKey: "com.donnywals.myapp.user-is-awesome")

Note that you might want to use Swift 5’s powerful property wrappers or a wrapper around UserDefaults to make accessing your user defaults a little bit cleaner since it’s easy to make mistakes while typing your user defaults keys. Especially if they’re quite long or complex like they are in the example that you just read.

Storing files on disk

Sometimes you need more than the limited storage user defaults gives you. If this is the case, you might want to look into storing files on the user’s file system. The file system typically has a large amount of storage available, but it can be relatively slow to read large amounts of data from. As with everything, this is a tradeoff and only by measuring and testing will you know whether you need something quicker than disk access, like for instance an SQLite store or in-memory storage.

What should be stored on disk

Typically, you will use disk storage for larger amounts of data. For example images, videos or large JSON files are suited to be stored on disk. These kinds of files are known as binary data and they have no properties that you might want to query in a database for example. Documents that the user creates like text files or PDFs are also typically stored on disk if possible. It's not uncommon for developers to make data structures that they want to store on disk conform to Codable so they can easily convert their objects to Data, which can then be written to a file on the file system. Using disk storage to store your Codable objects is especially nice if you want to create a cache of responses that you receive from the network, or if you just want to make sure certain objects persist across app launches.

Disk storage and security

Without any custom encryption, disk storage is very insecure. Apple encrypts devices when they are locked but as soon as the device is unlocked and your app is active, all bets are off. You should avoid storing sensitive data like usernames, passwords, and more in a file that you write to disk at all costs.

How to write files on disk?

Most commonly, you will write files to the documents directory, or if the data is used as a cache and can easily be recreated, the caches directory. The following example shows how to write data to the documents directory:

do {
  let fileManager = FileManager.default
  let docs = try fileManager.url(for: .documentDirectory,
                                 in: .userDomainMask,
                                 appropriateFor: nil, create: false)
  let path = docs.appendingPathComponent("myFile.txt")
  let data = "Hello, world!".data(using: .utf8)!
  fileManager.createFile(atPath: path.absoluteString, contents: data, attributes: nil)
} catch {
  // handle error
}

The preceding code snippet creates a file called myFile.txt in the documents directory with Hello, world! as its contents. You can use any kind of Data as the value of contents. For example, you might want to encode a struct into JSON using a JSONEncoder or grab some image data instead.

Storing data in the iOS Keychain

The third storage mechanism to consider is the keychain. This is the place where iOS stores all kinds of sensitive user data like usernames, passwords, certificates and more. If you have secure data that you need to store somewhere, the keychain should be your place to go.

What should be stored in the Keychain?

The keychain is intended to store your user’s sensitive, secret data. I personally use the keychain to store things like OAuth tokens that authenticate the user against a web service. Apple themselves use the keychain for much more than just tokens, they even store entire security certificates in the keychain.

The keychain and security

On iOS, the keychain is probably the best-secured place for you to store data. However, like everything on a device, the keychain can be cracked and you should keep that in mind. Even though Apple is so confident in the keychain that they use it to store username and password combinations, I’m a little bit too paranoid for that. If I can avoid storing passwords, I will. I’d much rather store a token that has no special meaning to the user than storing (and potentially losing) something as sensitive as their username and password.

How to interact with the Keychain?

The Keychain’s APIs are unfortunately very non-trivial to navigate and use. The good news is that there are wrappers available on CocoaPods, Carthage and Swift Package Manager that make using the Keychain much, much easier. I personally like KeychainAccess by kishikawakatsumi a lot.

Persisting data in a database

The last storage option I’ll cover in this comparison is using a database. Databases are very good at efficiently storing large amounts of structured data. And they’re not just good at storing data, they’re also very good at retrieving and filtering data. So whenever you have a larger set of data that you want to filter, store or expand upon later, a database that's accessed using plain SQLite, or a database wrapper/manager like Realm or CoreData might be ideal for you. I’m not going to compare the performance and usage of every one of these options since in the end, they’re all good at the same thing that all databases are good at.

What should be stored in a database on iOS?

If you have large objects of structured data like a user’s to-do list or metadata for documents the user creates in your app, data from your user’s weekly running exercises you’re probably going to need a database. If any of these objects have large binary blobs attached to them, like images, you might want to consider mixing disk storage and database storage by writing the large blob of data to disk and storing the path to your data in the database.

Databases and security

Databases on iOS are stored on the device, often in the documents directory. This means that you should consider any data that’s written to your databases compromised unless you’ve applied your own layer of encryption. As always, be very careful with what you store and avoid storing any sensitive data like usernames, passwords, and access token. The Keychain is a better fit for these kinds of data.

Reading and writing to a database

Depending on the database of your choice you will use SQL queries, predicates or something else to access and filter the data in your database. Keep in mind that databases often aren’t thread-safe so you need to be careful when reading and writing data from different threads.

In summary

In this blog post, you’ve learned a lot about different storage options on iOS. You learned that the Keychain is the most secure place to store user secrets. User defaults are great for simple key-value pairs that represent user preferences. File storage is great for storing large blobs of data and lastly, databases should be used to store structured data.

Security-wise, it’s good to reiterate that any data stored on the device should be considered compromised, it’s really just a matter of how complicated it is to extract and decrypt data. Always ask yourself whether you really need to store something that’s user-sensitive.

I hope this blog post will help you to make informed decisions about data storage. If you have questions, doubts, feedback or comments don’t hesitate to reach out to me on Twitter.

Thanks to Ahmed Khalaf for suggesting that I add notes about the max storage for user defaults and that it's loaded into memory when your app launched. And to Vadim Bulavin for pointing out that I omitted that User Defaults storage is only limited to 1MB on tvOS.

Updating your apps with silent push notifications

A lot of apps rely on data from a remote source to display their content. Sometimes the content on the remote source changes regularly, sometimes it changes only sporadically. In all cases, your users will want to see the most up to date version of the information that you have to offer them, and in most cases, you will do your best to make sure that your users always have up to date information.

There are many ways to make sure your users always see the latest data from your server. For example, you might be using web sockets if your data updates with very high frequency. If your data updates less frequently, or if you want to be able to fetch new data on-demand when your app is running in the background, you might want to use silent push notifications, which is exactly what this week’s Quick Tip is all about.

In a nutshell, with silent push notifications, your server can notify your app that it has new data available. Your app will be woken up if it’s in the background and a special AppDelegate method is called allowing you to retrieve and store this new data. Let’s go into a little bit more detail, shall we?

Configuring your app for silent push notifications

To receive silent push notifications you need to add the Push Notifications and Background Modes capabilities to your project. Make sure to check the Remote Notifications checkbox under the Background Modes header as shown in the following screenshot:

Screenshot of background modes enabled

Next, you need to register your application for remote notifications. Place the following code in your AppDelegate’s application(_:didFinishLaunchingWithOptions:) method:

UIApplication.shared.registerForRemoteNotifications()

Note that calling registerForRemoteNotifications() does not trigger the notification permissions popup. It only registers the current device on Apple’s notification servers. Next, implement application(_:didRegisterForRemoteNotificationsWithDeviceToken:) like you would for normal push notifications. Send the device token to a web server, or extract the device token from the data object for debugging using the following code:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  let token = deviceToken.reduce("") { $0 + String(format: "%02.2hhx", $1) }
  print("registered for notifications", token)
}

The last step is to implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:). This method is called when your application receives a remote notification. You will perform your background updates in this method. The following code is a very simple example of fetching data from a server and calling the fetchCompletionHandler:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

  URLSession.shared.dataTask(with: URL(string: "https://donnywals.com")!) { data, response, error in
    print(data)
    completionHandler(.newData)
  }.resume()
}

In your own applications, you will likely make a request to a more relevant URL and store the data that you fetch from that URL in UserDefaults, Core Data, a file on disk or somewhere else.

Note the following line of code completionHandler(.newData). When your app is launched in the background, you need to make sure that you call your completionHandler in a timely manner to avoid getting cut off or penalized if your task takes too long, or never completes. You can expect to have roughly 30 seconds of background execution time when fetching data in response to a silent push notification. If your task failed, call the handler with a .failed argument. If you attempted to fetch data but didn’t receive any, call the handler with .noData so iOS can prioritize future work for your app appropriately.

Now that you know how to prepare your app for silent push notifications, let’s have a brief look at what you need to do on the server to send a silent push notification.

Configuring your server for silent push notifications

Your server-side configuration is very similar to how you’d set it up for normal notifications. You can use an external service like Firebase Cloud Messaging if you want.

What’s most important is that you include the following in your push notification payload:

  • content_available: 1 in your notification payload
  • apns-push-type = background in your notification header
  • apns-priority = 5 in your notification header

I won’t go into the details of how you can build your notification exactly as this will vary per push provider or if you’re doing it manually.

Including content_available: 1 in your payload tells iOS that this is a notification that you sent because there is new content on the server. Without this, your application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method will never be called.

The apns-push-type = background header is only required on watchOS but Apple recommends that you include it in your silent pushes anyway. And lastly, the apns-priority = 5 header tells iOS that it should launch your app in the background when this push is sent. If you don’t include the priority header, your app will not be launched so make sure to include this in your notification payload.

If everything is configured correctly, you now have a powerful tool at your fingertips to make sure your application always has the most up to date data available for your users!

In summary

In this Quick Tip, you saw how you can configure both your app and remote notifications to support silent push notifications or on-demand background refresh. Note that you should not try to abuse this feature. Apple trusts that you will push updates responsibly and in moderation. Abuse of silent push might be penalized, for example, Apple will heavily throttle the number of times it will wake your app up in response to silent push notifications. Apple themselves recommend that you don’t send more than two or three silent pushes per hour.

Setting up silent push notifications isn’t a terribly complex process but it provides your app with the superpower of always being up to date! And again, with great power comes great responsibility…

If you have any questions, comments or feedback for me you can always reach out to me on Twitter

Real time data exchange using web sockets in iOS 13

Apps send and receive data all the time. Some apps mostly read data from the network, others are more focussed on sending data to servers. Depending on your needs, you might need to be able to send data to a server as quickly as possible, or maybe you need to receive new data as soon as it’s available from a server. Every app has different needs and several mechanisms exist to streamline network communication.

In this post week’s blog post, I will focus on one specific use of networking in apps. We’ll look at using web sockets to receive data as soon as it becomes available on the server. We’ll explore some of the available alternatives and how you can use web sockets with URLSession in iOS 13.

Are you excited? I know I am! Let’s get started then.

Receiving updates from a web server

The simplest and possibly most well-known way to receive updates from a server is by making a request to the server with URLSession. While it’s well-known, it’s also quite possibly the least efficient way to get real-time updates. The server only sends your app new data when it asks for new data. This means that your app should either go to the server many times to get data as soon as possible, which means that a lot of requests might come back without new data. Or alternatively, you might make fewer requests which means that it takes longer for the new data to reach your apps. This process of retrieving new data from a server is called polling.

Another way to retrieve new data from a server with URLSession is by implementing silent push notifications in your app. Your server can send a push notification that includes the content-available key in its apns payload to trigger a special AppDelegate method in your app. You can then request new data from the server and know that you will always receive new data. This is really efficient if your app limits the number of push notifications it sends by grouping updates together. If your server processes a lot of changing data, this might mean that your app receives lots of silent push notifications, which results in lots of requests to your server. Batching the notifications that are sent to your app will limit the number of requests made, but it will also introduce a delay between data becoming available on the server and your app knowing about it.

An alternative to polling and silent push notifications is long-polling. When you implement long-polling in an app, you make a request to a web server and leave the connection open for an extended period of time. Much longer than you normally would when making a request for new data. The server won’t respond to your application’s request until it has new data. Once new data is received, you can make a new long-polling request to receive the next bit of data.

Retrieving data through long-polling has the advantage of receiving new data as soon as it’s available. A potential downside of this approach is that your server will have to maintain many open connections. A properly configured server shouldn’t have too much trouble doing this though, so for receiving data long-polling is actually a pretty decent solution. A real downside of long polling is that you can’t send data over the active connection. This means that you’ll need to send a new request every time you want to send data from your app to the server. And again, in a rapidly changing environment, this could mean that your server will receive lots of incoming requests.

What if there was a way to use a single connection that can be kept active and is used to send and receive data at the same time? Well, you’re in luck. That’s exactly what a web socket does! Let’s see how you can use web sockets in your iOS 13+ apps using URLSession.

Using web sockets to send and receive data

In order to understand what we’re looking at in this blog post, let’s take a quick step back and learn about web sockets from a very high level. Once you have a rough overview of how web sockets work, we’ll look at using them in an app.

Understanding web sockets

Web sockets can be considered an open connection between a client and a server. They can send each other messages, typically these messages are short to make sure they’re sent and received as quickly as possible. So while it’s possible to send a complicated JSON payload with several items over a web socket connection, it’s more likely that you’re going to send each item in your payload individually.

It might seem a little bit counter-intuitive to send and receive many smaller payloads rather than a single bigger payload. After all, when you make requests to a server in a normal setting, you want to make sure you don’t make too many requests and favor a single larger request over several small requests, depending on your exact requirements, of course.

When you’re dealing with web sockets, you don’t send requests. You’re sending messages. And since you’re already connected to the server, you don’t incur any extra overhead when you send many small messages to the server. And since your ultimate goal when using a web socket is often communication with as little latency as possible, it makes sense to send any messages you have for the server as soon as possible in a package that is as small as possible so it can be sent over the network quickly.

The same is true for server-to-app communication. A server that supports web sockets will often prefer to send your app small messages with data as soon as the data becomes available rather than batching up several messages and sending them all at once.

So when we implement web sockets in an app, we need to be prepared to:

  • Send messages as soon as we can and keep them as small as possible.
  • Receive many messages in rapid succession.

Based on these two points, you should be able to decide whether web sockets are a good fit for the task you’re trying to accomplish. For example, uploading a large high-resolution image is not typically a good task for a web socket. The image data can be quite large and the upload might take a while. You’re probably better off using a regular data task for this.

If your app has to sync a large amount of data all at once, you might also want to consider using a separate request to your server to make sure your socket remains available for the quick and short messaging it was designed to handle. Of course, the final decision depends on your use case and trial and error might be key to finding the optimal balance between using web sockets and using regular URL requests in your app.

Now that you know a bit more about web sockets, let’s find out how they work in an iOS application, shall we?

Using web sockets in your app

Using web sockets in your app is essentially a three-step process:

  1. Connecting to a web socket
  2. Send messages over a web socket connection
  3. Receiving incoming messages

Let’s go over each step individually to implement a very simple connection that will send and receive simple messages as either JSON data or a string.

Connecting to a web socket

Establishing a connection to a web socket is done similar to how you make a regular request to a server. You make use of URLSession, you need a URL and you create a task that you must resume. When you make a regular request, the task you need is usually a dataTask. For web sockets, you need a webSocketTask. It’s also important that you keep a reference to the web socket task around so you can send messages using that same task at a later moment. The following code shows a simple example of this:

var socketConnection:  URLSessionWebSocketTask?

func connectToSocket() {
  let url = URL(string: "ws://127.0.0.1:9001")!
  socketConnection = URLSession.shared.webSocketTask(with: url)
  socketConnection?.resume()
}

Note that the URL that’s used for the socket connection is prefixed with ws://, this is the protocol that’s used to connect to a web socket. Similar to how http:// and https:// are protocols to connect to a web server when making a regular request.

After obtaining the web socket task, it must be resumed to actually connect to the web socket server.

Next up, sending a message over the web socket connection.

Sending message over a web socket connection

Depending on your app and the kind of data you wish to send over the web socket, you might want to send strings or plain data objects. Both are available on the Message enum that is defined on URLSessionWebSocketTask as a case with an associated value. Let’s look at sending a string first:

func sendStringMessage() {
  let message = URLSessionWebSocketTask.Message.string("Hello!")

  socketConnection?.send(message) { error in
    if let error = error {
      // handle the error
      print(error)
    }
  }
}

The preceding code creates a message using the string version of URLSessionWebSocketTask.Message. The existing socket connection is then used to send the message. The send(_:) method on URLSessionWebSocketTask takes a closure that is called when the message is sent. The error is an optional argument for the closure that will be nil if the message was sent successfully.

Let’s see how sending data over the socket is different from sending a string:

func sendDataMessage() {
  do {
    let encoder = JSONEncoder()
    let data = try encoder.encode(anEncodableObject)
    let message = URLSessionWebSocketTask.Message.data(data)

    socketConnection?.send(message) { error in
      if let error = error {
        // handle the error
        print(error)
      }
    }
  } catch {
    // handle the error
    print(error)
  }
}

The preceding code uses a JSONEncoder to encode an Encodable object into a Data object and then it creates a message using the data case of URLSessionWebSocketTask.Message. The rest of the code is identical to the version you saw earlier. The message is sent using the existing socket connection and the completion closure is called once the message has been delivered or if an error occurred.

This is pretty straightforward, right? Nothing fancy has to be done to send messages. We just need to wrap our message in a URLSessionWebSocketTask.Message and send it using an existing web socket connection.

Let’s see if receiving messages is just as nice as sending messages is.

Receiving incoming messages

Any time our server has new data that we’re interested in, we want to receive this new data over the web socket connection. To do this, you must provide the web socket task with a closure that it can call whenever incoming data is received.

The web socket connection will call your receive closure with a Result object. The Result’s success type is URLSessionWebSocketTask.Message and the Failure type is Error.

This means that we can either get a string or data depending on the contents of the message. The following code shows how you can set up the receive closure, and how you can distinguish between the two different types of messages you can receive.

func setReceiveHandler() {
  socketConnection?.receive { result in
    defer { self.setReceiveHandler() }

    do {
      let message = try result.get()
      switch message {
      case let .string(string):
        print(string)
      case let .data(data):
        print(data)
      @unknown default:
        print("unkown message received")
      }
    } catch {
      // handle the error
      print(error)
    }
  }
}

Similar to sending messages, receiving them isn’t terribly complex. Since the receive closure can receive either a string or data, we must make sure that the web socket server only sends us responses we expect. It’s important to coordinate with your server team, or yourself, that you can handle unexpected messages and that you only send strings or data in a pre-defined format.

Note the defer statement at the start of the receive handler. It calls self.setReceiveHandler() to reset the receive handler on the socket connection to allow it to receive the next message. Currently, the receive handler you set on a socket connection is only called once, rather than every time a message is received. By using a defer statement, you make sure that self.setReceiveHandler is always called before exiting the scope of the receive handler, which makes sure that you always receive the next message from your socket connection.

If you want to use a JSONDecoder to decode that data you’ve received from the server, you need to either attempt to decode several different types of objects until an attempt succeeds, or you need to make sure that you can only receive a single type of data over a certain web socket connection.

Personally, I would recommend to always make sure that you send a single type of message over a socket connection. This allows you to write robust code that is easy to reason about.

For example, if you’re building a chat application, you’ll want to make sure that your web socket only sends and receives instances of a ChatMessage that conforms to Codable. You might even want to have separate sockets for each active chat a user has. Or if you’re building a stocks application, your web sockets will probably only receive StockQuote objects that contain a stock name, timestamp, and a current price.

If you make sure that your messages are well-defined, URLSession will make sure that you have a convenient way of using your web socket.

In Summary

Web sockets are an amazing technology that, as you now know, allow you to communicate with a server in real-time, using a single active connection that can be used to both send and receive messages. You know that the big advantage here is that there is no overhead of making requests to a server every time you want to either send or receive data.

You also saw how you can use URLSession’s web socket task to connect to a web socket, send messages and receive them by using very straightforward and convenient APIs. Lastly, you learned that you must pre-define the messages you want to send and receive over your web socket to make sure all components involved understand what’s going on.

I personally really like web sockets, especially because you can use them to build experiences that feel like magic. I once used web sockets to build an app that could be controlled through a companion website by sending messages over a web socket, it was really cool to see the app reflect the things I was doing on the website.

If you have any questions about this blog post, have feedback or anything else, don’t hesitate to reach out to me on Twitter!

Thanks to Kristaps Grinbergs for pointing out that receive handlers are always called once 🙌🏼.

Announcing: Advent of Swift

December has always been a month of sharing for me. Sharing food and experiences with family, celebrating the end of what has (hopefully) been a wonderful year, reflecting and setting goals for the new year. And this year, I have decided to share something really special with the iOS community.

In December I will publish a new blog post every day until Christmas eve. That's 24 blog posts in total. And since I want this whole endeavor to be about the community itself, I need your input. Send me messages on Twitter to let me know what you want to learn and I will do my best to cover your topic in December during Advent of Swift.

Of course, I won't be able to cover every question you send me. But rest assured, I won't ignore any of your suggestions. If your post doesn't make the cut for Advent of Swift, I'm definitely keeping it around on my list of ideas for future blog posts and will do my best to cover your suggestion in 2020.

Let's make Advent of Swift 2019 a great success together! 🙌🏼

Note
Make sure you don't miss any posts by subscribing to my newsletter using the form below this post.

Configuring projects with xcconfig

Sometimes you want to be able to install two versions of your app side by side, for example, a development version and a release version that show up as individual apps by giving them different bundle identifiers. And maybe they should also use different versions of your REST API depending on the type of build you're using. In this week's Quick Tip I will show you how you can manage this using xcconfig files.

Creating and using your xcconfig file

To create an xcconfig file, choose File -> New -> File... in your project. In the new file dialog, scroll down until you see the Configuration Settings File in the Other section.

New File Dialog

After choosing an appropriate name (like debug.xcconfig for example) you can open your xcconfig file in Xcode and add new configuration rules to it. An xcconfig file should contain key and value pairs in the following format:

PRODUCT_BUNDLE_IDENTIFIER = com.xcconfig.sample
PRODUCT_NAME = Config Example

You can add configurations for pretty much anything you want. A good way to find out what key should be used for specific purposes you can open your xcodeproj file in a text editor and look for the settings you want.

To make Xcode apply your xcconfig file, go to your app's project settings, look for the build type you want your configuration to apply to, and select your configuration as shown in the following screenshot:

Selected build configuration

After doing this, Xcode might not apply your configuration for all keys you've added in your xcconfig. If this is the case, go to your projects Build Settings and enable the Levels view. The fields that are marked in green are what Xcode will use while building your app. If Xcode uses the wrong field, you can select it and clear it so Xcode will look for the next best fit. The following screenshot shows this:

Xcode Levels view

The number of options you can configure like this are numerous and the Levels view is extremely helpful in figuring out what values Xcode uses for certain configuration keys. Next up, using your xcconfig file to configure your app's runtime.

Using xcconfig and your Info.plist to configure your app

If you want your app to use different settings for different builds, like for instance use a development endpoint of your REST API, you can use your Info.plist and xcconfig file to set this up. All you need to do is add a new key to your config, use a config key as the value and read it in your app. For example, you might have the following key in your xcconfig:

API_ROOT = dev.myapp.com

You can use this config value by adding a key to your Info.plist and using $(API_ROOT) as the value. You can then proceed to read this key from your Info.plist just like you would any other key. Nifty, right?

In Summary

In this week's Quick Tip you learned how you can use an xcconfig file to drive build time configurations of your app. At compile time, all values that are in your xcconfig are applied to your project. This allows you to specify a special bundle identifier, product name, development team and even code signing strategy that will be applied to any build configuration that you want.

You can even use the xcconfig file to add different values to your Info.plist file so you can configure certain runtime features for your app. All in all, a very powerful feature that I personally use in many projects. If you have any questions, compliments or feedback, don't hesitate to find and message me on Twitter

Building flexible components with generics and protocols

Recently I wanted to build a generic data source layer. This data source would be able to return pretty much anything from a local cache, or if the local cache doesn't contain the requested object, it would fetch the object from a server and then cache the result locally before returning it to me. To achieve this, I figured that I would write a generic local cache, a generic remote cache and a wrapper that would combine both caches, allowing me to transparently retrieve objects without having to worry about where the object came from.

It didn't take long before I saw the first compiler warnings and remembered that generics can be extremely hard to bend to your will. Especially if your generic object uses other generic objects to transparently do generic things.

In this blog post, I will show you my approach to tackling complicated problems like this, and how I use pseudo code to design and implement a fluent API that works exactly as I wanted. In this blog post we'll go over the following topics:

  • Designing an API without getting lost in the details
  • Knowing how simple generics are used in Swift, and how you can use them in your code.
  • Understanding you can have protocols with generic requirements, also known as associated types.
  • Combining generics with protocols that have associated types

Are you ready to enter the brain-melting world of generics? Good, let's go!

Designing an API without getting lost in the details

I promised you generics. Instead, I'm going to show you how to design an API that uses generics first. This is to establish a goal, something we can work towards throughout this blogpost. Generics are complicated enough as they are and I don't want you to get confused to the point where you're not sure what we were building again.

In the introduction of this post, I mentioned that I wanted to build a generic data store that cached data locally, and would use a remote data store as a back up in case the required data didn't exist locally.

A good way to get started building something like this is to write down some pseudo-code that demonstrates how you would like to use the API or component you're building. Here's what I wrote for the caching layer:

let localDataStore = UserDataStore()
let remoteDataStore = UserApi()
let dataStore = CacheBackedDataStore(localDataStore, remoteDataStore)

dataStore.fetch(userID) { result in 
  // handle result
}

This is pretty straightforward, right? You can see that I want to create two stores and a wrapping store. The wrapping store is the one that's used to retrieve information and it uses a callback to inform the caller about the results. Simple and straightforward, just how I like it. Keep in mind that whatever we design has to work with more than user objects. We also want to be able to store other information in this structure, for example, documents that belong to the user.

Let's dive a bit deeper and write a pseudo-implementation for CacheBackedDataStore:

class CacheBackedDataStore {
  let localStore: LocalStore
  let remoteStore: RemoteStore

  func fetch(_ identifier: IdentifierType, completion: @escaping Result<T, Error>) {
    localStore.fetchObject(identifier) { result in 
      if let result = try? result.get() {
         completion(.success(result))
      } else {
        remoteStore.fetchObject(identifier) { result in 
          if let result = try? result.get() {
            completion(.success(result))
          } else {
            // extract error and forward to the completion handler
          }
        }
      }
    }
  }
}

You might notice the type T on the result here. This type T is where our generic adventure begins. It's the start of a rabbit hole where we're going to turn everything into objects that could be anything. At this point, we have enough "design" to get started with setting up some of our building blocks. To do this, we're going to have a look at generics in Swift.

Adding simple generics to your code

In the pseudo-code design that I showed you in the previous section, I used the type T. Whenever we write code with generics in Swift, we typically use T to flag a type as generic. A generic type can be pretty much anything as long as it satisfies the constraints that are specified for it. If we don't specify any constraints, T can be anything you want. An example of a generic type that can be anything you want is Array. Let's look at two identical ways to define an empty array in Swift:

let words1 = [String]()
let words2 = Array<String>()

Notice that the second way uses the type name Array followed by <String>. This informs the compiler that we're defining an array where the type of element is String. Now let's try to imagine what the type definition for Array might look like:

struct Array<T> {
  // implementation code
}

This code declares a struct of type Array that contains some type T that is generic; it could be anything we want, as long as we specify it when creating an instance or when we use it as a type. In the earlier example, let words2 = Array<String> we defined T to be of type String. Let's look at one more basic example before we move on:

struct SpecializedPrinter<T> {
  func print(_ object: T) {
    print(object)
  }
}

This code declares a SpecializedPrinter that's generic over T and it has a function called print, that takes an object of type T and prints it to the console. If you paste the above into a Playground, you can use this SpecializedPrinter struct as follows:

let printer = SpecializedPrinter<String>()
printer.print("Hello!") // this is fine
printer.print(10) // this is not okay since T for this printer is String, not Int

Now that you know a bit about generics, I think we can write the first bit of code for the CacheBackedDataSource object:

struct CacheBackedDataSource<T> {
  func find(_ objectID: String, completion: @escaping (Result<T?, Error>) -> Void) {

  }
}

We're not doing much here, but it's an important milestone in your journey to mastering generics in Swift. You have written a data source that claims to cache any type (T) and will do an asynchronous lookup for an item based on a string identifier. The find(_:completion:) function will call the completion block with a Result object that contains an optional instance of T, or an Error object.

In the pseudo-code from earlier in this post, there were two properties:

let localStore: LocalStore
let remoteStore: RemoteStore

Since the caching layer should be as generic and flexible as possible, let's define LocalStore and RemoteStore as protocols. This will give us tons of flexibility, allowing any object to act as the local or remote store as long as they implement the appropriate functionality:

protocol LocalStore {

}

protocol RemoteStore {

}

And in these protocols, we will define methods to fetch the object we need, and in the local store, we'll define a method that persists an object.

protocol LocalStore {
  func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void)
  func persist(_ object: T)
}

protocol RemoteStore {
  func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void)
}

Unfortunately, this doesn't work. Our protocols don't know what T is since they're not generic. So how do we make these protocols generic? That's the topic of the next section.

Adding generics to protocols

While we can define a generic parameter on a struct by adding it to the type declaration between <>, just like we did for struct CacheBackedDataSource<T>, this is not allowed for protocols. If you want to have a protocol with a generic parameter, you need to declare the generic type as an associatedtype on the protocol. An associatedtype does not have to be implemented as a generic on objects that implement the protocol. I will demonstrate this shortly. For now, let's fix the local and remote store protocols so you can see associatedtype in action:

protocol LocalStore {
  associatedtype StoredObject

  func find(_ objectID: String, completion: @escaping (Result<StoredObject, Error>) -> Void)
  func persist(_ object: StoredObject)
}

protocol RemoteStore {
  associatedtype TargetObject

  func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void)
}

Notice how we're not using a short name like T here. This is because the associated type does not necessarily have to be generic, and we want the purpose of this type to be communicated a bit better than we typically do when you're defining a generic parameter on a struct. Let's create two structs that we conform to LocalStore and RemoteStore to see how associatedtype works in the context of objects that conform to our protocols.

struct ArrayBackedUserStore: LocalStore {
  func find(_ objectID: String, completion: @escaping (Result<User, Error>) -> Void) {

  }

  func persist(_ object: User) {

  }
}

struct RemoteUserStore: RemoteStore {
  func find(_ objectID: String, completion: @escaping (Result<User, Error>) -> Void) {

  }
}

All that's needed to implement the protocol's associatedtype in this example is to use the same type in all places where the protocol uses its associated type. An alternative that's a bit more verbose would be to define a typealias inside of a conforming object and use the protocols associatedtype where we currently use the User object. An example of this would look like this:

struct RemoteUserStore: RemoteStore {
  typealias TargetObject = User

  func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void) {

  }
}

I prefer the former way where we use User in place of TargetObject, it's just easier to read in my opinion.

Since we're dealing with data that comes from a remote server in RemoteUserStore, it would be quite convenient to constraint the value of TargetObject to only allow Decodable types to be used in place of TargetObject. We can do this as follows:

protocol RemoteStore {
  associatedtype TargetObject: Decodable

  func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void)
}

If we try to use User in place of TargetObject and User isn't Decodable, we're shown the following error by the compiler:

candidate would match and infer 'TargetObject' = 'User' if 'User' conformed to 'Decodable'
func find(_ objectID: String, completion: @escaping (Result<User, Error>) -> Void) {

We now have the following code prepared for the CacheBackedDataSource and the local and remote store protocols:

struct CacheBackedDataSource<T> {
  func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void) {

  }
}

protocol LocalStore {
  associatedtype StoredObject

  func find(_ objectID: String, completion: @escaping (Result<StoredObject, Error>) -> Void)
  func persist(_ object: StoredObject)
}

protocol RemoteStore {
  associatedtype TargetObject: Decodable

  func find(_ objectID: String, completion: @escaping (Result<TargetObject, Error>) -> Void)
}

Let's add some properties for the local and remote store to the CacheBackedDataStore:

struct CacheBackedDataSource<T> {
  let localStore: LocalStore
  let remoteStore: RemoteStore

  func find(_ objectID: String, completion: @escaping (Result<T, Error>) -> Void) {

  }
}

Unfortunately, this won't compile. The following errors are thrown by the Swift compiler:

error: protocol 'LocalStore' can only be used as a generic constraint because it has Self or associated type requirements
  let localStore: LocalStore
                  ^

error: protocol 'RemoteStore' can only be used as a generic constraint because it has Self or associated type requirements
  let remoteStore: RemoteStore
                   ^

Let's see how we can fix this error in the next session.

Using a protocol with associated type requirements as a generic constraint

Before we look deeper into the compiler errors we're currently stuck with I want to quickly recap what we've got prepared so far. Because even though the code doesn't compile, it's quite impressive already. We have a generic data source that can retrieve any object T.

We also have a protocol for a local store that caches any type we want, the adopter of the protocol can decide what type is cached exactly. All that matters is that the object that implements the protocol has a find method that performs a lookup based on an identifier and invokes a callback with a Result object. It also has a persist method that is expected to store objects that have the same type as the type object that the local store can fetch.

Lastly, we have a protocol for a remote store that fetches any kind of object, as long as it conforms to Decodable. Similar to how local store works, the implementer of the RemoteStore can decide what the type of the TargetObject will be.

This is really powerful stuff and if the above is a bit confusing to you, that's okay. It's not simple or straightforward even though the code looks fairly simple. Try following along with the code we've written so far, re-read what you've learned and maybe take a short break to let it sink in. I'm sure it will eventually.

In order to use the local and remote store protocols as types on the CacheBackedDataSource, we need to add generic parameters to the CacheBackedDataSource, and constrain these parameters so they have to implement our protocols. Replace your current implementation of CacheBackedDataSource with the following:

struct CacheBackedDataSource<Local: LocalStore, Remote: RemoteStore> {
  private let localStore: Local
  private let remoteStore: Remote

  func find(_ objectID: String, completion: @escaping (Result<Local.StoredObject, Error>) -> Void) {

  }
}

The declaration of CacheBackedDataSource now has two generic parameters, Local and Remote. Each has to conform to its respective protocol. This means that the localStore and remoteStore should not be of type LocalStore and RemoteStore. Instead, they should be of type Local and Remote. Note that Result<T, Error> has been replaced with Result<Local.StoredObject, Error>. The find method now uses whatever type of object the LocalStore stores as the type for its Result. This is really powerful because the underlying store now dictates the type of objects returned by the data source object.

There's still one problem though. Nothing prevents us from locally storing something that's completely incompatible with the remote store. Luckily we can apply constraints to the generic parameters of our struct. Update the declaration of CacheBackedDataSource as follows:

struct CacheBackedDataSource<Local: LocalStore, Remote: RemoteStore> where Local.StoredObject == Remote.TargetObject

We can now only create CacheBackedDataSource objects that use the same type of object for the local and remote stores. Before I show you how to create an instance of CacheBackedDataSource, let's implement the find method first:

func find(_ objectID: String, completion: @escaping (Result<Local.StoredObject, Error>) -> Void) {
  localStore.find(objectID) { result in
    do {
      let object = try result.get()
      completion(.success(object))
    } catch {
      self.remoteStore.find(objectID) { result in
        do {
          let object = try result.get()
          self.localStore.persist(object)
          completion(.success(object))
        } catch {
          completion(.failure(error))
        }
      }
    }
  }
}

The find method works by calling find on the local store. If the requested object is found, then the callback is invoked and the result is passed back to the caller. If an error occurred, for example, because the object wasn't found, the remote store is used. If the remote store finds the requested object, it's persisted in the local store and the result is passed back to the caller. If the object wasn't found or an error occurred in the remote store, we invoke the completion closure with the received error.

Note that this setup is extremely flexible. The implementation of CacheBackedDataSource doesn't care what it's caching. It only knows how to use a local store with a fallback to a remote store. Pretty awesome, right? Let's wrap this up by creating an instance of the CacheBackedDataSource:

let localUserStore = ArrayBackedUserStore()
let remoteUserStore = RemoteUserStore()
let cache = CacheBackedDataSource(localStore: localUserStore, remoteStore: remoteUserStore)
cache.find("someObjectId") { (result: Result<User, Error>) in

}

All you need to do is create instances of your stores, and supply them to the cache. You can then call find on your cache and the compiler is able to understand that the result object that's passed to the completion closure for find is a Result<User, Error>.

Take a look at the pseudo-code I showed you at the beginning of this post. It's very close to what we ended up implementing, and it's just as powerful as we imagined! If you've been following along, try to create some CacheBackedDataSource objects for other types. It should be fairly straightforward.

In summary

You have learned so much in this blog post. I wouldn't be surprised if you have to read it one or two more times to make complete sense out of all these generics and type constraints. And we haven't even covered all of it! Generics are an unbelievably powerful and complex feature of the Swift language but I hope that I have been able to help you make some sense of them. Overall, you now know that adding <T> to an object's declaration adds a generic parameter, which means that anytime you use T inside of that object, it's whatever the user of that object decided it to be.

You also learned that you can add associatedtype to a protocol to have it support generic types. And to top it off, you learned how you can use a protocol that has an associated type as a constraint for an object's generic parameter for maximum flexibility. If your brain hurts a bit after reading all this then again, don't worry. This stuff is hard, confusing, weird and complex. And if you have any questions, comments or need somebody to talk to because you feel lost now, don't hesitate to reach out on Twitter!

Add iOS 12 support to a new Xcode 11 Project

When you create a new project in Xcode 11, you automatically get the new SceneDelegate for free. This is great if you want to build an app that's for iOS 13 and newer but as soon as you change your deployment target to an iOS version that's lower than iOS 13, your app will have trouble compiling. In this Quick Tip, I will show you how to update your project in order to make it compile for iOS 12 and below. You will first learn how to use the SceneDelegate for iOS 13 and up, and use the AppDelegate as a fallback for older versions of iOS. After that, I will show you how to opt-out of using the SceneDelegate completely if you're absolutely sure that you're not interested in any of its benefits for iOS 13.

Making the existing template work for iOS 12

Since the UIWindowSceneDelegate is only available in iOS 13 and up, we'll need to exclude the entire SceneDelegate object if the app is compiled for iOS 12 or below. To do this, add an @available annotation to the SceneDelegate class as shown in the following code snippet:

@available(iOS 13, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

This ensures that the SceneDelegate is not included in iOS 12 builds. Next, go to the AppDelegate and add the same @available annotation to application(_:configurationForConnecting:options) and application(_:didDiscardSceneSessions) as shown in the following code snippet:

@available(iOS 13, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  // ...
}

@available(iOS 13, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
  // ...
}

Lastly, since applications that use storyboards must a window property defined on the AppDelegate for iOS 12 and below, add the following property to the AppDelegate:

var window: UIWindow?

This will allow iOS to instantiate the Main.storyboard and use it to create and populate the window object on iOS 12. For iOS 13 and above the window will remain unset because the SceneDelegate will set its own window on those versions of iOS.

Your app should be able to run on iOS 12 and 13 now. One last thing you might want to add to application(_:didFinishLaunchingWithOptions:) is the following:

if #available(iOS 13, *) {
  // do nothing / do things that should only be done for iOS 13
} else {
  // do iOS 12 specific window setup
}

This will allow you to do some version dependent set up in your AppDelegate.

Keep in mind that now that you have the AppDelegate for iOS 12 and the SceneDelegate for iOS 13 you have different entry points in your code for different iOS versions. This means that you might have to do some special setup or apply duplicated logic in the AppDelegate or SceneDelegate depending on what you need to achieve. If you don't want to do this, you can also opt-out of the SceneDelegate completely. Let's see how.

Opting out of the SceneDelegate completely

To opt-out of using the SceneDelegate in your project, you should take the following steps:

  1. Begin by deleting the SceneDelegate.swift file from your project.
  2. Next, open AppDelegate.swift and remove all scene related methods from that file.
  3. Lastly, you'll need to open your Info.plist and remove the Application Scene Manifest key from that file.

Three simple steps and you're able to work on your project in the same way you used to do in Xcode 10. Very nice.

In summary

In this Quick Tip, you've learned how to add a couple of clever @available and if #available statements to your code to make sure your projects work on iOS 12 and below. While this isn't a lot of work, I think it's still somewhat inconvenient that we have to do this to make Xcode 11 work for in a very reasonable scenario. Supporting older iOS versions is something a lot of developers have to do so it would have been nice to see Apple accommodate this when creating a new project in Xcode. I would recommend against using the approach of opting out of the SceneDelegate entirely because it has some awesome advantages that I describe in my posts Understanding the iOS 13 Scene Delegate and Adding support for multiple windows to your iPadOS app.

When you deploy your app to iOS 12 and use the SceneDelegate for iOS 13, you will run into some come duplication between the AppDelegate for iOS 12 and the SceneDelegate for iOS 13. I chose to omit suggestions to make this manageable because I really wanted to keep this post short. I'm sure you can come up with some way to encapsulate the duplicated logic in some kind of helper object that you can use to configure your app in either the AppDelegate or the SceneDelegate. I might write something about this in the future. Make sure to follow me on Twitter. Don't hesitate to reach out to me if you have any questions or suggestions for me!

[weak self] usage in Swift explained

We all want to write good, beautiful and stable code. This includes preventing memory leaks, which we can, using [weak self] when writing a closure that needs access to self. But what's the real reason for needing this weak capture? And do we need it all the time? In this week's Quick Tip, I want to help you find an answer to this question so you can make more informed decisions about your capture lists in the future.

This post contains the following topics:

  • Understanding what a capture list is
  • Understanding different kinds of captures
  • Knowing when to rely on implicit or strong captures
  • Deciding whether you need a weak or an unowned self capture

Enjoy!

Understanding what a capture list is

When you write a closure, it will implicitly capture all properties referenced inside of the closure. Let's look at a short code sample to illustrate this:

func multiply(by multiplier: Int) -> ((Int) -> Int) {
  return { (input: Int) -> Int in
    return input * multiplier
  }
}

var multiplier = 2
let multiplyTwo = multiply(by: multiplier) // you can now call multiplyTwo as if it's a function
multiplier = 4
let multiplyFour = multiply(by: multiplier) // you can now call multiplyFour as if it's a function

The preceding code sample shows a function that takes a multiplier and returns a closure that takes a different input and multiplies it with the multiplier that was passed to multiply(by:). The closure that is returned from this function captures the multiplier and uses it as the multiplier for the input you give it. In practice, this means that calling multiplyTwo(2) will return 4 and multiplyFour(2) returns 8. The multiplier variable that is defined does not affect the closure held by multiplyTwo or multiplyFour because of this capture behavior.

I know that this can be quite confusing when you've just started learning about closures, but bear with me as we go through some more examples.

Take a look at the following example:

var name = "Donny"
var appendToName = { (string: String) -> String in
  return name.appending(string)
}

let one = appendToName("Wals")
name = "D"
let two = appendToName("Wals")

What would you expect the values of one and two to be? Remember that I just explained how closures capture properties that are used inside of a closure.

If you expect one and two to both be "DonnyWals" I don't blame you, it seems to make sense! But unfortunately, this isn't correct. The value for one is "DonnyWals" and two is "DWals". How this closure is different from the closure you saw before is that everything, from the closure to the property it references is in the same context. The closure can read the current value of name because it's on the same level. We can, however, explicitly capture name using a capture list as follows:

var name = "Donny"
var appendToCapturedName = { [name] (string: String) -> String in
  return name.appending(string)
}

let one = appendToCapturedName("Wals")
name = "D"
let two = appendToCapturedName("Wals")

When you run this code in a Playground, both one and two will equal "DonnyWals" because you explicitly told the closure to capture the current value of name by putting it in a capture list. This can be called an explicit or strong capture. You can capture more than one property in a capture list by comma separating them: [property, anotherProperty].

You just learned about strong and implicit capturing of properties. Let's look at other kinds of captures.

Understanding different kinds of captures

When you strongly capture a property, the closure will own this property. For value types like structs and enums, this means that the closure copies the current value of an object over to its own area in memory where it owns the object. When you do the same for a reference type, like a class, the closure will maintain a strong pointer reference to the object. To understand the implications of this, you need to know one thing about reference types: they are never deallocated as long as at least one other object holds a reference to them. When an object holds an unintended reference to a reference type, it could be considered a memory leak. If you're not sure what this means, don't worry, it should be a little bit clearer after the next code sample. The following code demonstrates the memory leak that I described earlier:

class MyClass {}
var instance: MyClass? = MyClass()

var aClosure = { [instance] in
  print(instance)
}

aClosure() // MyClass
instance = nil
aClosure() // MyClass

The second time we call aClosure we still print the same instance of MyClass because aClosure holds a strong reference to the instance. Sometimes this is exactly what we want, but usually, we don't want our closures to keep objects alive after they've been deinitialized. An example that comes to mind is a closure that might capture a view controller while it's waiting for a network request to finish. If the view controller is dismissed before the network request is finished, we want the view controller to be removed from memory, or deallocated. If the network request's completion closure has a strong reference to the view controller, the closure keeps the view controller alive because it still holds a reference to the view controller.

So how would we make this strong capture a not so strong capture? Well, how about we make it weak instead?

Tip: You can run the code below in a Swift Playground to see the result.

class MyClass {}

var myInstance: MyClass? = MyClass()

var aClosure = { [weak myInstance] in
  print(myInstance)
}

aClosure() // MyClass
myInstance = nil
aClosure() // nil

Because the closure only holds a weak reference to the instance of MyClass, the system doesn't count our closure's reference to instance which means that as soon as the playground releases its reference to our instance of MyClass, it can be deallocated. The downside here is that this might lead to subtle bugs where you don't immediately notice that instance was deallocated before your closure was called. If you want to assert that instance is still around when the closure is called, and want to crash your app if it's not you can use an unowned reference:

Tip: You can run the code below in a Swift Playground to see the result.

class MyClass {}
var instance: MyClass? = MyClass()

var aClosure = { [unowned instance] in
  print(instance)
}

aClosure() // MyClass
instance = nil
aClosure() // crash

The impact on memory for an unowned capture is pretty much the same as weak. It's very similar to safely unwrapping an optional value with ? or doing so forcefully with ! which crashes your app if the value to unwrap is nil. In practice, you'll find that unowned is almost never what you're looking for.

Now that you have some understanding of what weak and unowned are, and how you can implicitly or strongly capture a value, let's have a look at when you should use these different capture methods.

Knowing when to use weak, unowned, strong or implicit capture

As mentioned at the start of this post, a lot of developers use [weak self] in their closures to prevent memory leaks. But are memory leaks really that common when working with closures? That depends on what they're used for exactly. In this section, we'll explore different kinds of captures in closures and what their implications are. We'll first look at when you might want to use an implicit capture. Then we'll look at strong capture and last we'll look at weak and unowned captures.

When to use implicit capture

Implicit capture is often used when you're dealing with closures that capture self, where self is a value type, like a struct. Since structs don't have pointers that reference them, closures won't accidentally keep a struct alive for longer than it should. In fact, trying to use weak or unowned on a non-class type isn't allowed in Swift.

You can use implicit capture on reference types as long as you're certain that the closure you're calling won't be retained by the object that will receive your closure. A good example of this is performing work on a DispatchQueue:

class MyClass {
  func dispatchSomething() {
    DispatchQueue.global().async {
      // it's okay to implicitly capture self here
    }
  }
}

Since the closure passed to async isn't retained, you can safely capture it without a capture list. You know it's not retained because the closure is executed shortly after calling async, and it's only called once. If your closure is retained, for example when it's used as an event handler for an object, you should make sure to capture self weakly to avoid keeping a reference to self that you don't want. Keep in mind that you're often relying on implementation details when you're doing this so even though you don't have to use weak self here, it might be a good idea to limit your usage of implicit captures to code you own and control.

The easiest way to know whether a closure that you pass to a function is retained, is to check whether it's marked as @escaping. Closures that are not marked with @escaping do not leave the scope of the function that you pass it too which means that it can't be retained for longer than the function's scope. If you want to learn more about @escaping, take a look at this post.

When to use an explicit strong capture

Strongly, or explicitly capturing references isn't done very often. It's most useful if you want to allow partial deallocation of objects. For example, if you perform a network request and want to store its result in a Core Data store without doing anything else, the object that initiated the request doesn't have to stick around; you only need the data store. An example of this might look as follows:

struct SomeDataSource {
  var storage: MyDataStore
  let networking: NetworkingLayer
  // more properties

  func refreshData() {
    networking.refresh { [storage] data in
      storage.persist(data)
    }
  }
}

Since the closure only requires the storage property, there's no need to capture self here since that would keep the entire SomeDataSource object around. Keep in mind though that the storage property is captured at the time the closure is created as I showed you earlier in this post. So in this case that means that the value of storage is captured when we call refreshData. If MyDataStore is a struct, that means that it's copied at capture-time in the closure and any changes that are made to storage after the closure is created are not visible inside of the closure.

If MyDataStore is a reference type, you would be able to see changes made to the instance that's captured in the closure, but if you change storage by assigning a new storage to it after storage is captured, you will have captured the old storage instead of the new one.

When to use weak and unowned captures

Weak capture is by far the most common capture and it's usually a good default choice. It should typically be used when you don't want an object to stick around until the closure is performed, or if the closure is retained for an unknown amount of time which is often the case for network requests. Keep in mind that a closure with a weak capture will treat the captured property as an optional. So if you have a [weak self] in your capture list, you'll likely want to unwrap self in your closure body using guard let self else { return } to make sure that self still exists by the time the closure is executed. Using weak or unowned is the only way to make 100% sure your reference type doesn't hang around in memory for longer than necessary. This makes it a good option if you're not sure which type of capture if most appropriate for your current use case.

When it comes to deciding between weak and unowned my personal opinion is to go with weak. There are little to no performance gains when using unowned (not to the point where it will matter in most apps anyway) and if any of your assumptions become incorrect later on, your unowned capture would crash you app which isn't great.

I always think of unowned as a force unwrap. It's probably fine in some cases, but you're essentially putting a little landmine in your code that in my experience is likely to go off when you least expect it.

In Summary

And that's another Quick Tip that became much larger than I initially intended. Closures and capture lists have all kinds of subtle implications that are important to keep in mind while programming and I hope this post has given you some insights into why you sometimes need a [weak self] for reference types but can reference self freely in value types (remember, it's all about the reference count). You saw that you can even capture specific properties of an object if you don't need to capture the entire self. You can even apply weak or unowned to individual properties of an object to prevent your closures from keeping individual objects around.

Now that you know pretty much everything there is to know about closures and capture lists, go ahead and look through your codebase to see if there are any improvements you can make based on the information you just read. As always, thanks for reading and don't hesitate to reach out on Twitter if you have any feedback or questions.

Special thanks to Bas Broek for proofreading this article!

Adding support for multiple windows to your iPadOS app

Now that Apple has split iPadOS into a separate OS, and launched Catalyst to enable developers to compile their iPad apps for the Mac, there’s a whole new multi-window paradigm we must understand and cater for. Up until this year, we only had to worry about a single window for our iOS applications. This meant that we never had to worry about the user being at two places in our app at the same time. For instance, what would happen if a user has two windows of an app open and both are on the edit profile page?

In this blog post, I will introduce the concept of iPad apps with more than one window to you. Last week I explained that multi-window support is enabled through UIScene, UISceneSession and the SceneDelegate. If you’re not sure what these objects are and what they do, I can highly recommend that you go ahead and read last week’s post first.

This week’s blog post covers the following topics:

  • Understanding how a user creates a new scene for your app.
  • Defining the scenes that are supported by your app.
  • Opening, closing, and refreshing scenes.
  • Adding advanced behavior to open new scenes.

Understanding how a user creates a new scene for your app

Before we dive into the details of defining your scenes and implementing code to work with your scenes, let's go over all the ways a user will be able to create scenes for your app once you enable the "Supports Multiple Windows" checkbox in your project settings. We'll not just look at what's made available out of the box, but I also want to show you several of the behaviors your users will likely expect once your application supports multiple windows. After that, we'll have a look at how you can implement multi-window support for your apps.

Ready to get started?

Default ways to open a new scene

In iPadOS, there are two standard paths to opening a new scene. First, the user can drag an app's icon upwards from the dock to create a new scene for the dragged app if an active scene is already on screen. If the app isn't currently visible on the screen, iPadOS will grab an active scene and move that over to where you dropped the app icon. Users can use this method to make your app appear as a flyover or side-by-side with another app.

The second way is to go to the Exposè, select your app to show all windows for your app, and use the plus symbol in the top right corner of the screen to launch a new scene. This will create a new full-screen scene for your app using your app's default scene. The user can then rearrange windows, show them side by side or change one into a flyover using the same gestures and patterns that exist in iOS 12. The only difference is that in iOS 12 every app only had a single scene and in iPadOS 13 an app can have multiple scenes.

In addition to these default methods of opening a scene, there is a special kind of interaction that users will likely come to expect in any app that supports multiple scenes. This interaction is the drag and drop interaction.

Drag and drop interactions your users will likely expect

Users love patterns, and so does Apple. So a good place to look for interactions that your user is likely to expect from your app is the default apps Apple ships with iPadOS. If you examine some of Apple's multi-scene applications, you can see that a lot of them support drag and drop to create a new scene. In Safari, you can pick up a tab and drag it to the side of the screen to open that tab in a new scene. In Contacts, you can grab a contact from the contact list and you can drop it on the side of the screen to show the dragged contact in a new scene. And lastly, in the Mail app, a user can grab the email compose modal and drag it to the side of the screen to open the composer in a new scene.

All of these interactions feel very natural and if your app has similar interaction patterns, it's seriously worth considering implementing drag and drop in a similar way to Apple's implementation to make sure your users feel right at home in your app.

Now that you know about some of the ways your users will expect to open scenes, let's see how you can add support for more than a single type of scene in your app.

Defining the scenes that are supported by your app

If you started your project in Xcode 11 and you've checked the "Supports multiple windows" checkbox, you've done the first couple of steps to support multiple scenes. Your app can now have more than one active scene, and iOS will always use your SceneDelegate to set up your scene. Before we continue, go ahead and grab this blog post's sample project, open the .xcodeproj in the Starter folder and examine the project. It's a very simple cat image viewer application. I know, super cute. If you don't want to follow along, here's a screenshot of the app.

Screenshot of the end result

Okay, back to business. Cats are cool but it would be even cooler if you could see the detail page for the cats side by side, for even more cuteness. When a detail page is tapped, we'll go ahead and open a new scene to display the detail page in. Note that this is probably not what you'd want to do in a real application. Normally a simple tap should just push a detail view in the existing scene, and you'd open a new scene if a user drags the cat picture to the side of the screen. We'll implement this drag and drop behavior in the last section of this blog post. For now I just really want to get you up and running with multiple scene support.

If you examine the sample project, you'll find that it contains a secondary SceneDelegate object called CatSceneDelegate. This specific SceneDelegate looks very similar to the default SceneDelegate that Xcode generates except instead of the app's main view, the CatSceneDelegate uses the cat detail page.

To make sure that your app is aware of this new scene delegate, you must add a new entry to the Application Scene Manifest's Scene Configuration array.

A scene configuration is nothing more than a string identifier and a reference to a scene delegate that should be used when your application is asked to create a scene. The default scene configuration is called Default Configuration and uses the $(PRODUCT_MODULE_NAME).SceneDelegate object to set up its scene and display it to the user.

Tip:
You must always use the $(PRODUCT_MODULE_NAME). prefix before your scene delegate's class name to make sure iOS knows where to look for your scene delegate while running your app.

To add our cat detail scene configuration, click on the plus icon next to the Application Session Role keyword. Xcode will create a new configuration entry for you. Remove the Storyboard Name and Class Name fields. Set $(PRODUCT_MODULE_NAME).CatSceneDelegate as the Delegate Class Name and Cat Detail as the Configuration Name. Make sure to rearrange the configurations by dragging them so that the Default Configuration is above the Cat Detail configuration. Your configuration should like the following screenshot:

Scene configuration example

Run the app, it should work as normal because we're not launching any scenes with our newly created scene configuration yet. Let's go ahead and do that next!

Opening, closing, and refreshing scenes

In a typical application, you will want to control when your app opens a new scene or when it closes one. But how do you open or close a scene, and why would you want to refresh a scene? In this section, I will answer these questions. Let's start by opening a new scene and implementing logic to close it. We will then look at some advanced logic to determine whether a new scene should be opened, or if we can reuse an existing scene. Lastly, we will look at scene refreshing.

To open a new scene, you pass a request to your UIApplication object. The UIApplication will then check if a new scene session needs to be created, or if an existing scene session can be used. In the sample code, I've added a method called didTapCat(_:). Let's add some code to this method to open the cat detail page in a new scene:

let activity = NSUserActivity(activityType: "com.donnywals.viewCat")
activity.userInfo = ["tapped_cat_name": tappedCat(forRecognizer: sender)]
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)

The preceding code isn't terribly complex, and it's all we need for a very simple detail page. We create an NSUserActivity that contains all information needed to determine that we want to view a cat detail page, and what cat we want to view the detail page for. After configuring the NSUserActivity, we call requestSceneSessionActivation(_:userActivity:options:errorHandler:) on UIApplication.shared to initiate the request to launch a new scene. When we call this method, application(_:configurationForConnecting:options:) is called on your AppDelegate. You must return an appropriate UISceneConfiguration from this method that matches a configuration that's in your Info.plist. Update this method in this method in AppDelegate.swift so its body looks as follows:

if let activity = options.userActivities.first, activity.activityType == "com.donnywals.viewCat" {
  return UISceneConfiguration(name: "Cat Detail", sessionRole: connectingSceneSession.role)
}

return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)

We check whether we have a user activity that matches the type we expect. If one exists, we create and return an instance of our Cat Detail configuration. If we don't have the expected user activity, we return the Default Configuration. We're almost ready to see the app in action. Let's just have a quick look at the CatDetailSceneDelegate.swift that's already created for you. The following line are especially interesting:

let detail: CatDetailViewController
if let activity = connectionOptions.userActivities.first,
  let catName = activity.userInfo?["tapped_cat_name"] as? String {
  detail = CatDetailViewController(catName: catName)
} else {
  detail = CatDetailViewController(catName: "default")
}

We check whether a user activity was provided to us. If we have one, we extract the relevant values from it. If this fails, or if we don't have a user activity, we create a CatDetailViewController with a default name. You'll see why we need this in a moment. If you run the app now, you'll see that tapping one of the two cats spawns a new scene every time. While this is cool, it would be much better to reuse the same scene session and activate it if one of our cats is tapped.

This can be achieved by looping over the currently active sessions and inspecting the targetContentIdentifier associated with each session's scene. If we find a match, we can request activation of that specific scene rather than asking for a new scene session. Update didTapCat(_:) so it looks as follows:

@objc func didTapCat(_ sender: UITapGestureRecognizer) {
  let activity = NSUserActivity(activityType: "com.donnywals.viewCat")
  activity.targetContentIdentifier = tappedCat(forRecognizer: sender)

  let session = UIApplication.shared.openSessions.first { openSession in
    guard let sessionActivity = openSession.scene?.userActivity,
      let targetContentIdentifier = sessionActivity.targetContentIdentifier  else {

        return false
    }

    return targetContentIdentifier == activity.targetContentIdentifier
  }

  UIApplication.shared.requestSceneSessionActivation(session, userActivity: activity, options: nil, errorHandler: nil)
}

Note:
In one of Apple's WWDC presentations they mention the use of predicates to automatically find the most appropriate scene for a target content identifier. Unfortunately, I haven't been able to get this to work myself. If you have, please do reach out to me so I can update this post.

When you run the app again in Xcode, you will notice that it recreates all the scenes that were active when you quit the app. This is why you added the fallback earlier. When the app is used in normal conditions, this shouldn't happen. But it's good to guard against it for development purposes anyway. Despite Xcode recreating existing detail scenes with the default identifier, tapping the same cat multiple times should now only open one scene for each cat.

When a user wants to close a scene, they can do this from the iPad's Exposé. There is a close button on the cat detail page right now, but it doesn't do anything, Let's write some code to destroy the current scene if a user taps the close button. Add the following code to the close method in CatDetailViewController.swift:

if let session = self.view.window?.windowScene?.session {
  let options = UIWindowSceneDestructionRequestOptions()
  options.windowDismissalAnimation = .commit
  UIApplication.shared.requestSceneSessionDestruction(session, options: options, errorHandler: nil)
}

This code obtains the current session and creates an instance of UIWindowSceneDestructionRequestOptions. We can use this object to configure a nice animation for when the scene is discarded. In this case, we pick a commit style. You can also choose decline and standard depending on the message you want to send to the user.

Now let's look at refreshing a scene. This is something you'll typically want to do to make sure your app's snapshot in the iPad Exposé is up to date and accurately reflects your user interface. For the cat app this doesn't make a lot of sense, but let's assume it would. The following code would go over all currently connected scene sessions and if the session is used to display a relevant user activity, we ask the application to refresh the session:

for session in UIApplication.shared.openSessions {
  if session.scene?.userActivity?.activityType == "some.activity.type" {
    UIApplication.shared.requestSceneSessionRefresh(session)
  }
}

Note that the refresh action might not take place immediately, the system reserves the right to delay the refreshes to an appropriate moment in time.

Now that you've seen how you would create a session and reuse it for the same content, how to destroy a session and how to refresh a session, it's time to implement one last method of creating new scenes; drag and drop.

Adding advanced behavior to open new scenes

We've implemented everything we set out to implement. All we need now is drag and drop support. I won't go into all the fine details for drag and drop in this post. Instead, I will show you how to implement a drag interaction for this specific scenario and how to configure your app so it can use drag and drop to open new scenes. Before your app can support drag and drop to open new scenes, you must register the user activity types your app will handle in your Info.plist. Add a new NSUserActivityTypes key of type Array and add the user activity types you wish to support to this array. You should end up with an entry that's similar to the following screenshot:

Example plist entry

Next, add the following two lines of code to CatsOverviewViewController.swift right after the code that sets up a tap gesture (should be around line 41):

let dragInteraction = UIDragInteraction(delegate: self)
image.addInteraction(dragInteraction)

The preceding code adds drag support to the image view. The next step is to make CatsOverviewViewController.swift conform to UIDragInteractionDelegate so it can provide the appropriate drag item that's used to open a new scene for the app. Add the following extension to CatsOverviewViewController.swift:

extension CatsOverviewViewController: UIDragInteractionDelegate {
  func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
    let selectedCat = cats[interaction.view?.tag ?? 0]

    let userActivity = NSUserActivity(activityType: "com.donnywals.viewCat")
    userActivity.targetContentIdentifier = selectedCat
    userActivity.title = "View Cat"

    let itemProvider = NSItemProvider(object: UIImage(named: selectedCat)!)
    itemProvider.registerObject(userActivity, visibility: .all)

    let dragItem = UIDragItem(itemProvider: itemProvider)
    dragItem.localObject = selectedCat

    return [dragItem]
  }
}

After doing this, run the app and drag one of the two cats to the side of the screen. You should be able to drop the image to create a new floating scene or create a new one that's opened right next to the existing scene. Amazing that we did this with only a few lines of code right?

In summary

Wow! This post turned out much longer than I expected! You have learned a ton about adding support for multiple scenes to your iPad app, and there's still more for you to explore! We haven't looked at supporting URLs or Shortcut Items in this blog post. And we also didn't have time to go over adding a target-content-id to your push notifications to make certain notifications launch in a specific scene. I'm sure you'll be able to figure this out on your own now that you have a solid foundation of scene session knowledge.

Let's recap what you've learned in this post. First, you learned about the ways users are likely to expect to use multiple scenes with apps that support them. Then you saw how you can configure your Info.plist with all the scenes that your app supports. Next, you saw how to launch a new scene when a user taps a button, and how to destroy it when they tap another button. You also saw how you would refresh a scene session if needed. And lastly, we added drag and drop support to allow users to drag elements of your app to the side of the screen to launch a new scene.

This is cool stuff! And I'm sure you're going to build amazing things with this knowledge. And, as always, if you enjoyed this blog post, have feedback, questions or anything else. Don't hesitate to share this post with your friends and reach out to me on Twitter. If you're looking for the sample code for this post, it's right here on Github.