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.

Uploading images and forms to a server using URLSession

One of those tasks that always throws me off balance is building a form that allows users to upload a form with a picture attached to it. I know that it involves configuring my request to be multipart, that I need to attach the picture as data and there’s something involved with setting a content disposition. This is usually about as far as I go until I decide it might be a good time to go to github.com and grab the Carthage URL for Alamofire. If you’re reading this and you’ve implemented POST requests that allow users to upload photos and forms, I’m sure this sounds familiar to you.

In this week’s quick tip I will show you how to implement a multipart form with file upload using only Apple’s built-in URLSession. Ready? On your marks. GO!

Understanding what a multipart request actually looks like

If you’ve ever inspected a multipart request using a tool like Charles or Proxyman you may have found out that the headers of your post requests contained the following key amongst several others:

Content-Type: multipart/form-data; boundary=3A42CBDB-01A2-4DDE-A9EE-425A344ABA13

This header tells us that the content that's being sent to the server is multipart/form-data. This content type is used when you upload a file alongside other fields in a single request. This is very similar to how an HTML form is uploaded to a server for example. This header also specifies a boundary which is a string that's used by the server to detect where lines / values start and end.

The value you saw for the boundary was very likely to be different, but it should look familiar. The body of your post request typically looks a little bit like like the following:

--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="family_name"

Wals
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="name"

Donny
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="file"; filename="somefilename.jpg"
Content-Type: image/png

-a long string of image data-
--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13—

If you've inspected your request and found similar content to the content above, you might have decided that this looks complicated and you’re better off using a library that handles the creation of the header and HTTP body for you. If that’s the case, I completely understand. Especially because the part where I wrote -a long string of image data- can be really long. I used to reach for Alamofire to handle uploads for the longest time. However, once you take the time to dissect the Content-Type header and HTTP body a little bit, you’ll find that it follows a pretty logical pattern that takes a bunch of effort to implement but I wouldn't say it's very hard. It's mostly very tedious.

First, there’s the Content-Type header. It contains information about the type of data you’re sending (multipart/form-data;) and a boundary. This boundary should always have a unique, somewhat random value. In the example above I used a UUID. Since multipart forms are not always sent to the server all at once but rather in chunks, the server needs some way to know when a certain part of the form you’re sending it ends or begins. This is what the boundary value is used for. This must be communicated in the headers since that’s the first thing the receiving server will be able to read.

Next, let’s look at the http body. It starts with the following block of text:

--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="family_name"

Wals

We send two dashes (--) followed by the predefined boundary string (Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13) to inform the server that it’s about to read a new chunk of content. In this case a form field. The server knows that it’s receiving a form field thanks to the first bit of the next line: Content-Disposition: form-data;. It also knows that the form field it’s about to receive is named family-name due to the second part of the Content-Disposition line: name=“family_name”. This is followed by a blank line and the value of the form field we want to send the server.

This pattern is repeated for the other form field in the example body:

--Boundary-3A42CBDB-01A2-4DDE-A9EE-425A344ABA13
Content-Disposition: form-data; name="name"

Donny

The third field in the example is slightly different. It’s Content-Disposition looks like this:

Content-Disposition: form-data; name="file"; filename="somefilename.jpg"
Content-Type: image/png

It has an extra field called filename. This tells the server that it can refer to the uploaded file using that name once the upload succeeded. This last chunk for the file itself also has its own Content-Type field. This tells the server about the uploaded file’s Mime Type. In this example, it’s image/png because we’re uploading an imaginary png image.

After that, you should see another empty line and then a whole lot of cryptic data. That’s the raw image data. And after all of this data, you’ll find the last line of the HTTP body:

--Boundary-E82EE6C1-377D-486C-AFE1-C0CE9A03E9A3--

It’s one last boundary, prefixed and suffixed with --. This tells the server that it has now received all of the HTTP data that we wanted to send it.

Every form field essentially has the same structure:

BOUNDARY
CONTENT TYPE
-- BLANK LINE --
VALUE

This structure is mandatory, we didn't pick it ourselves, and we shouldn't modify it.

Once you understand this structure, the HTTP body of a multipart request should look a lot less daunting, and implementing your own multipart uploader with URLSession shouldn’t sound as scary anymore. Let’s dive right in and implement a multipart URLRequest that can be executed by URLSession!

Preparing a multipart request with an image

In the previous section, we focussed on the contents of a multipart form request in an attempt de demystify its contents. Now it’s time to construct a URLRequest, configure it and build its httpBody so we can send it off to the server with URLSession instead of a third-party solution.

Since I only want to focus on building a multipart for request that contains a file, I won’t be showing you how you can obtain an image that your user can upload.

The first bit of this task is pretty straightforward. We’ll create a URLRequest, make it a POST request and set its Content-Type header:

let boundary = "Boundary-\(UUID().uuidString)"

var request = URLRequest(url: URL(string: "https://some-page-on-a-server")!)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

Next, let’s look at the HTTP body. In the previous section, you saw that every block in a multipart request is constructed similarly. Let’s create a method that will output these chunks of body data so that we're not having to bother with writing the same code over and over again.

func convertFormField(named name: String, value: String, using boundary: String) -> String {
  var fieldString = "--\(boundary)\r\n"
  fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
  fieldString += "\r\n"
  fieldString += "\(value)\r\n"

  return fieldString
}

The code above should pretty much speak for itself. We construct a String that has all the previously discussed elements. Note the \r\n that is added to the string after every line. This is needed to add a new line to the string so we get the output that we want.

While this method is pretty neat for the form fields that contain text, we need a separate method to create the chunk for file data since it works slightly different from the rest. This is mainly because we need to specify the content type for our file, and we have file's Data as the value rather than a String. The following code can be used to create a body chunk for the file:

func convertFileData(fieldName: String, fileName: String, mimeType: String, fileData: Data, using boundary: String) -> Data {
  let data = NSMutableData()

  data.appendString("--\(boundary)\r\n")
  data.appendString("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n")
  data.appendString("Content-Type: \(mimeType)\r\n\r\n")
  data.append(fileData)
  data.appendString("\r\n")

  return data as Data
}

extension NSMutableData {
  func appendString(_ string: String) {
    if let data = string.data(using: .utf8) {
      self.append(data)
    }
  }
}

Instead of a String, we create Data this time. The reason for this is twofold. One is that we already have the file data. Converting this to a String and then back to Data when we add it to the HTTP body is wasteful. The second reason is that the HTTP body itself must be created as Data rather than a String. To make appending text to the Data object, we add an extension on NSMutableData that safely appends the given string as Data. From the structure of the method, you should be able to derive that it matches the HTTP body that was shown earlier.

Let’s put all these pieces together and finish preparing the network request!

let httpBody = NSMutableData()

for (key, value) in formFields {
  httpBody.appendString(convertFormField(named: key, value: value, using: boundary))
}

httpBody.append(convertFileData(fieldName: "image_field",
                                fileName: "imagename.png",
                                mimeType: "image/png",
                                fileData: imageData,
                                using: boundary))

httpBody.appendString("--\(boundary)--")

request.httpBody = httpBody as Data

print(String(data: httpBody as Data, encoding: .utf8)!)

The preceding code shouldn’t be too surprising at this point. You use the methods you wrote earlier to construct the HTTP body. After adding the form fields you add the final boundary with the two trailing dashes and the resulting data is set as the request’s httpBody. Note the print statement at the end of this snippet. Try printing the HTTP body and you’ll see that it matches the format from the beginning of this post perfectly.

Now all that’s left to do is run your request just like you would normally:

do {
  let (data, response) = try await URLSession.shared.data(from: request)
  // use your data
} catch {
  print(error)
}

// Or if you're not using async / await yet
URLSession.shared.dataTask(with: request) { data, response, error in
  // handle the response here
}.resume()

Pretty awesome right? It took a little bit of work, but once you understand what you’re doing it’s suddenly not so bad anymore.

In summary

Even though I called this post a Quick Tip, this turned out to be an information-packed, fairly long write up on making a multipart form request with URLSession. And while there is quite some text involved in explaining everything, I hope you see now that making this kind of request doesn’t require a third-party library. Sure, it takes away the boilerplate for you, but at the same time, you might want to take a step back and ask yourself whether the boilerplate is really so bad that you want to use an external dependency for a single task.

If you want to grab a copy of the finished example, head over to Github. I’ve uploaded a Playground for you to, well, play with. As always, thanks for reading this week’s Quick Tip and any questions, feedback and even compliments are more than welcome. You can reach me on X or Threads.

Understanding the iOS 13 Scene Delegate

When you create a new project in Xcode 11, you might notice something that you haven’t seen before. Instead of only creating an AppDelegate.swift file, a ViewController.swift, a storyboard and some other files, Xcode now creates a new file for you; the SceneDelegate.swift file. If you’ve never seen this file before, it might be quite confusing to understand what it is, and how you are supposed to use this new scene delegate in your app.

By the end of this week's blog post you will know:

  • What the scene delegate is used for.
  • How you can effectively implement your scene delegate.
  • Why the scene delegate is an important part of iOS 13.

Let’s jump right in, shall we?

Examining the new Xcode project template

Whenever you create a new Xcode project, you have the option to choose whether you want to use SwiftUI or Storyboards. Regardless of your choice here, Xcode will generate a new kind of project template for you to build upon. We’ll take a closer look at the SceneDelegate.swift and AppDelegate.swift files in the next section, what’s important for now is that you understand that Xcode has created these files for you.

In addition to these two delegate files, Xcode does something a little bit more subtle. Take a close look at your Info.plist file. You should see a new key called Application Scene Manifest with contents similar to the following image:

Screenshot of the Info.plist file's scene manifest

This scene manifest specifies a name and a delegate class for your scene. Note that these properties belong to an array (Application Session Role), suggesting that you can have multiple configurations in your Info.plist. A much more important key that you may have already spotted in the screenshot above is Enable Multiple Windows. This property is set to NO by default. Setting this property to YES will allow users to open multiple windows of your application on iPadOS (or even on macOS). Being able to run multiple windows of an iOS application side by side is a huge difference from the single window environment we’ve worked with until now, and the ability to have multiple windows is the entire reason our app’s lifecycle is now maintained in two places rather than one.

Let’s take a closer look at the AppDelegate and SceneDelegate to better understand how these two delegates work together to enable support for multiple windows.

Understanding the roles of AppDelegate and SceneDelegate

If you’ve built apps prior to iOS 13, you probably know your AppDelegate as the one place that does pretty much everything related to your application’s launch, foregrounding, backgrounding and then some. In iOS 13, Apple has moved some of the AppDelegate responsibilities to the SceneDelegate. Let’s take a brief look at each of these two files.

AppDelegate’s responsibilities

The AppDelegate is still the main point of entry for an application in iOS 13. Apple calls AppDelegate methods for several application level lifecycle events. In Apple’s default template you’ll find three methods that Apple considers to be important for you to use:

  • func application(_:didFinishLaunchingWithOptions:) -> Bool
  • func application(_:configurationForConnecting:options:) -> UISceneConfiguration
  • func application(_:didDiscardSceneSessions:)

These methods have some commentary in them that actually describes what they do in enough detail to understand what they do. But let’s go over them quickly anyway.

When your application is just launched, func application(_:didFinishLaunchingWithOptions:) -> Bool is called. This method is used to perform application setup. In iOS 12 or earlier, you might have used this method to create and configure a UIWindow object and assigned a UIViewController instance to the window to make it appear.

If your app is using scenes, your AppDelegate is no longer responsible for doing this. Since your application can now have multiple windows, or UISceneSessions active, it doesn’t make much sense to manage a single-window object in the AppDelegate.

The func application(_:configurationForConnecting:options:) -> UISceneConfiguration is called whenever your application is expected to supply a new scene, or window for iOS to display. Note that this method is not called when your app launches initially, it’s only called to obtain and create new scenes. We’ll take a deeper look at creating and managing multiple scenes in a later blog post.

The last method in the AppDelegate template is func application(_:didDiscardSceneSessions:). This method is called whenever a user discards a scene, for example by swiping it away in the multitasking window or if you do so programmatically. If your app isn’t running when the user does this, this method will be called for every discarded scene shortly after func application(_:didFinishLaunchingWithOptions:) -> Bool is called.

In addition to these default methods, your AppDelegate can still be used to open URLs, catch memory warnings, detect when your app will terminate, whether the device’s clock changed significantly, detect when a user has registered for remote notifications and more.

Tip:
It’s important to note that if you’re currently using AppDelegate to manage your app’s status bar appearance, you might have to make some changes in iOS 13. Several status bar related methods have been deprecated in iOS 13.

Now that we have a better picture of what the new responsibilities of your AppDelegate are, let’s have a look at the new SceneDelegate.

SceneDelegate’s responsibilities

When you consider the AppDelegate to be the object that’s responsible for your application’s lifecycle, the SceneDelegate is responsible for what’s shown on the screen; the scenes or windows. Before we continue, let’s establish some scene related vocabulary because not every term means what you might think it means.

When you’re dealing with scenes, what looks like a window to your user is actually called a UIScene which is managed by a UISceneSession. So when we refer to windows, we are really referring to UISceneSession objects. I will try to stick to this terminology as much as possible throughout the course of this blog post.

Now that we’re on the same page, let’s look at the SceneDelegate.swift file that Xcode created when it created our project.

There are several methods in the SceneDelegate.swift file by default:

  • scene(_:willConnectTo:options:)
  • sceneDidDisconnect(_:)
  • sceneDidBecomeActive(_:)
  • sceneWillResignActive(_:)
  • sceneWillEnterForeground(_:)
  • sceneDidEnterBackground(_:)

These methods should look very familiar to you if you’re familiar with the AppDelegate that existed prior to iOS 13. Let’s have a look at scene(_:willConnectTo:options:) first, this method probably looks least familiar to your and it’s the first method called in the lifecycle of a UISceneSession.

The default implementation of scene(_:willConnectTo:options:) creates your initial content view (ContentView if you’re using SwiftUI), creates a new UIWindow, sets the window’s rootViewController and makes this window the key window. You might think of this window as the window that your user sees. This, unfortunately, is not the case. Windows have been around since before iOS 13 and they represent the viewport that your app operates in. So, the UISceneSession controls the visible window that the user sees, the UIWindow you create is the container view for your application.

In addition to setting up initial views, you can use scene(_:willConnectTo:options:) to restore your scene UI in case your scene has disconnected in the past. For example, because it was sent to the background. You can also read the connectionOptions object to see if your scene was created due to a HandOff request or maybe to open a URL. I will show you how to do this later in this blog post.

Once your scene has connected, the next method in your scene’s lifecycle is sceneWillEnterForeground(_:). This method is called when your scene will take the stage. This could be when your app transitions from the background to the foreground, or if it’s just becoming active for the first time. Next, sceneDidBecomeActive(_:) is called. This is the point where your scene is set up, visible and ready to be used.

When your app goes to the background, sceneWillResignActive(_:) and sceneDidEnterBackground(_:) are called. I will not go into these methods right now since their purpose varies for every application, and the comments in the Xcode template do a pretty good job of explaining when these methods are called. Actually, I’m sure you can figure out the timing of when these methods are called yourself.

A more interesting method is sceneDidDisconnect(_:). Whenever your scene is sent to the background, iOS might decide to disconnect and clear out your scene to free up resources. This does not mean your app was killed or isn’t running anymore, it simply means that the scene passed to this method is not active anymore and will disconnect from its session.

Note that the session itself is not necessarily discarded too, iOS might decide to reconnect a scene to a scene session at any time, for instance when a user brings a particular scene to the foreground again.

The most important thing to do in sceneDidDisconnect(_:) is to discard any resources that you don’t need to keep around. This could be data that is easily loaded from disk or the network or other data that you can recreate easily. It’s also important to make sure you retain any data that can’t be easily recreated, like for instance any input the user provided in a scene that they would expect to still be there when they return to a scene.

Consider a text processing app that supports multiple scenes. If a user is working in one scene, then backgrounds it to do some research on Safari and change their music in Spotify, they would absolutely expect all their work to still exist in the text processing app, even though iOS might have disconnected the text processing app’s scene for a while. To achieve this, the app must retain the required data, and it should encode the current app state in an NSUserActivity object that can be read later in scene(_:willConnectTo:options:) when the scene is reconnected.

Since this workflow of connecting, disconnecting and reconnecting scenes is going to separate the good apps from the great, let’s have a look at how you can implement state restoration in your app.

Performing additional scene setup

There are several reasons for you to have to perform additional setup when a scene gets set up. You might have to open a URL, handle a Handoff request or restore state. In this section, I will focus mostly on state restoration since that’s possibly the most complex scenario you might have to handle.

State restoration starts when your scene gets disconnected and sceneDidDisconnect(_:) is called. At this point, it's important that your application already has a state set up that can be restored later. The best way to do this is to use NSUserActivity in your application. If you’re using NSUserActivity to support Handoff, Siri Shortcuts, Spotlight indexing and more, you don’t have a lot of extra work to do. If you don’t use NSUserActivity yet, don’t worry. A simple user activity might look a bit as follows:

let activity = NSUserActivity(activityType: "com.donnywals.DocumentEdit")
activity.userInfo = ["documentId": document.id]

Note that this user activity is not structured how Apple recommends it, it’s a very bare example intended to illustrate state restoration. For a complete guide on NSUserActivity, I recommend that you take a look at Apple’s documentation on this topic.

When the time comes for you to provide a user activity that can be restored at a later time, the system calls stateRestorationActivity(for:) method on your SceneDelegate. Note that this method is not part of the default template

func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
  return scene.userActivity
}

Doing this associates the currently active user activity for a scene with the scene session. Remember that whenever a scene is disconnected, the UISceneSession that owns the UIScene is not discarded to allow the session to reconnect to a scene. When this happens, scene(_:willConnectTo:options:) is called again. In this method, you have access to the UISceneSession that owns the UIScene so you can read the session’s stateRestorationActivity and restore the application state as needed:

if let activity = session.stateRestorationActivity,
  activity.activityType == "com.donnywals.DocumentEdit",
  let documentId = activity.userInfo["documentId"] as? String {

  // find document by ID
  // create document viewcontroller and present it
}

Of course, the fine details of this code will vary based on your application, but the general idea should be clear.

If your UISceneSession is expected to handle a URL, you can inspect the connectionOptions object’s urlContexts to find URLs that your scene should open and information about how your application should do this:

for urlContext in connectionOptions.urlContexts {
  let url = urlContext.url
  let options = urlContext.options

  // handle url and options as needed
}

The options object will contain information about whether your scene should open the URL in place, what application requested this URL to be opened and other metadata about the request.

The basics of state restoration in iOS 13 with the SceneDelegate are surprisingly straightforward, especially since it's built upon NSUserActivity which means that a lot of applications won’t have to do too much work to begin supporting state restoration for their scenes.

Keep in mind that if you want to have support for multiple scenes for your app on iPadOS, scene restoration is especially important since iOS might disconnect and reconnect your scenes when they switch from the foreground to the background and back again. Especially if your application allows a user to create or manipulate objects in a scene, a user would not expect their work to be gone if they move a scene to the background for a moment.

In summary

In this blog post, you have learned a lot. You learned what roles the AppDelegate and SceneDelegate fulfill in iOS 13 and what their lifecycles look like. You now know that the AppDelegate is responsible for reacting to application-level events, like app launch for example. The SceneDelegate is responsible for scene lifecycle related events. For example, scene creation, destruction and state restoration of a UISceneSession. In other words, the main reason for Apple to add UISceneDelegate to iOS 13 was to create a good entry point for multi-windowed applications.

After learning about the basics of UISceneDelegate, you saw a very simple example of what state restoration looks like in iOS 13 with UISceneSession and UIScene. Of course, there is much more to learn about how your app behaves when a user spawns multiple UISceneSessions for your app, and how these scenes might have to remain in sync or share data.

If you want to learn more about supporting multiple windows for your iPad app (or your macOS app), make sure to check out my post Adding support for multiple windows to your iPadOS app. Thanks for reading, and don’t hesitate to reach out on Twitter if you have any questions or feedback for me.

When to use map, flatMap and compactMap in Swift

Any time you deal with a list of data, and you want to transform the elements in this list to a different type of element, there are several ways for you to achieve your goal. In this week’s Quick Tip, I will show you three popular transformation methods with similar names but vastly different applications and results.

By the end of this post, you will understand exactly what map, flatMap and compactMap are and what they do. You will also be able to decide which flavor of map to use depending on your goals. Let’s dive right in by exploring the most straightforward of the three; map.

Understanding map

Any time you have a set of data where you want to transform every element of the sequence into a different element, regardless of nested lists or nil values, you’re thinking of using map. The working of map is probably best explained using an example:

let numbers = [1, 2, 3, 4, 5, 6]

let mapped = numbers.map { number in
  return "Number \(number)"
}

// ["Number 1", "Number 2", "Number 3", "Number 4", "Number 5", "Number 6"]
print(mapped)

The example shows an array of numbers from one through six. By calling map on this array, a loop is started where you can transform the number and return another number or, like in this example, something completely different. By using map, the original array of numbers is unaltered and a new array is created that contains all of the transformed elements. When you call map on an array with nested arrays, the input for the map closure will an array. Let’s look at another example:

let original = [["Hello", "world"], ["This", "is", "a", "nested", "array"]]
let joined = original.map { item in
  return item.joined(separator: " ")
}

// ["Hello world", "This is a nested array"]
print(joined)

By joining all strings in the nested array together, we get a new array with two strings instead of two nested arrays because each array was mapped to a string. But what if we wanted to do something else with these strings like for instance remove the nested arrays and get the following output:

["Hello", "world", "This", "is", "a", "nested", "array"]

This is what flatMap is good at.

Understanding flatMap

The slightly more complicated sibling of map is called flatMap. You use this flavor of map when you have a sequence of sequences, for instance, an array of arrays, that you want to "flatten". An example of this is removing nested arrays so you end up with one big array. Let’s see how this is done exactly:

let original = [["Hello", "world"], ["This", "is", "a", "nested", "array"]]
let flatMapped = original.flatMap { item in
  return item
}

// ["Hello", "world", "This", "is", "a", "nested", "array"]
print(flatMapped)

Even though the type of item is [String], and we return it without changing it, we still get a flat array of strings without any nested arrays. flatMap works by first looping over your elements like a regular map does. After doing this, it flattens the result by removing one layer of nested sequences. Let’s have a look at an interesting misuse of flatMap:

let original = [["Hello", "world"], ["This", "is", "a", "nested", "array"]]
let flatMapped2 = original.flatMap { item in
  item.joined(separator: " ")
}

print(flatMapped2)

This example joins the item (which is of type [String]) together into a single string, just like you did in the map example. I want you to think about the output of this flatMap operation for a moment. What do you expect to be printed?

I’ll give you a moment here.

If you thought that the result would be the same as you saw before with map, you expected the following result:

["Hello world", "This is a nested array"]

This makes sense, you’re returning a String from the flatMap closure. However, the actual result is the following:

["H", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "T", "h", "i", "s", " ", "i", "s", " ", "a", " ", "n", "e", "s", "t", "e", "d", " ", "a", "r", "r", "a", "y"]

Wait, what? Every letter is now an individual element in the array? That’s correct. Since String is a sequence of Character, flatMap will flatten the String, pulling it apart into its individual Character objects. If this is not the result you had in mind, that’s okay. flatMap can be a confusing beast. It’s important to keep in mind that it will flatten anything that is a sequence of sequences into a single sequence. Even if it’s a String.

Let’s see how the last flavor of map, compactMap is different from map and flatMap.

Understanding compactMap

The last flavor of map we’ll look at in this Quick Tip is compactMap. Whenever you have an array of optional elements, for instance [String?], there are cases when you want to filter out all the nil items so you end up with an array of [String] instead. It might be tempting to do this using a filter:

let arrayWithOptionals = ["Hello", nil, "World"]
let arrayWithoutOptionals = arrayWithOptionals.filter { item in
  return item != nil
}

// [Optional("Hello"), Optional("World")]
print(arrayWithoutOptionals)

This kind of works but the type of arrayWithoutOptionals is still [String?]. How about using a for loop:

let arrayWithOptionals = ["Hello", nil, "World"]
var arrayWithoutOptionals = [String]()
for item in arrayWithOptionals {
  if let string = item {
    arrayWithoutOptionals.append(string)
  }
}

// ["Hello", "World"]
print(arrayWithoutOptionals)

Effective, but not very pretty. Luckily, we have compactMap which is used for exactly this one purpose. Turning a sequence that contains optionals into a sequence that does not contain optionals:

let arrayWithOptionals = ["Hello", nil, "World"]
var arrayWithoutOptionals = arrayWithOptionals.compactMap { item in
  return item
}

// ["Hello", "World"]
print(arrayWithoutOptionals)

Neat, right? A somewhat more interesting example might be to take an array of strings and converting them into an array of URL objects. Immediately filtering out the nil results that occur when you convert an invalid String to URL:

let urlStrings = ["", "https://blog.donnywals.com", "https://avanderlee.com"]
let urls = urlStrings.compactMap { string in
  return URL(string: string)
}

// [https://blog.donnywals.com, https://avanderlee.com]
print(urls)

We now have converted the input strings to valid URL instances, omitting any nil values. Pretty neat, right.

In summary

This Quick Tip showed you three ways to convert an array or sequence of elements into a different kind of sequence. map is the most basic variant, it converts every element in the sequence into a different sequence. No exceptions. Next, we looked at flatMap which looks similar to map but it flattens the first level of nested sequences. You also saw how it would flatten an array of two strings into an array of many single characters because a String is a sequence of Character. Lastly, you learned about compactMap. This mapping function transforms all elements in a sequence into a different element, just like map, but it removes nil values afterward.

Now that you have learned about map on sequence types like arrays, you should have a vague idea of what might happen if you map a Publisher in Combine, or maybe if you call map on an Optional. If you’re not sure, I challenge to go ahead and try it out. You might find some interesting results!

Thanks for reading this post, and as always your feedback is more than welcome! Don’t hesitate to shoot me a Tweet if you have any questions, feedback or just want to reach out.

Faking network responses in tests

Modern applications often rely on data from a network connection to work as intended. Sometimes they rely heavily on the network and are almost worthless without an internet connection while other apps can function mostly fine without a network connection. What these apps have in common is that they contain code that might be challenging to write tests for.

Whenever you write unit tests, you should always strive to make your tests as predictable, reproducible and most importantly independent of external factors as possible. This is a huge difference compared to integration testing where you’d test a certain part of a system within its context. An internet connection is quite possibly one of the most unpredictable factors that you do not want to introduce in a unit test suite.

Why is the network so unpredictable you ask? Reasons include but are not limited to:

  • The network connection might suddenly drop, this is outside of your control.
  • You might not control the server, changes, bugs or outages on the server would impact your tests.
  • Network speeds might vary depending on several factors, this could result in unjust test failures.

There are several things you can do to remove the network as a dependency from your tests. In this post, I will show you how you can build a dedicated networking layer that is abstracted using protocols. This allows you to swap out the networking implementation when running tests, which helps you avoid going to the network altogether. A setup like this allows you to test logic completely independent from what might be happening on a server. Let’s say, for example, that you’re building a reward system. Whenever a user is eligible for a reward, they tap a button. Your app fires off a request to a web server that returns a response to your app that contains between 0 and 2 rewards. There are four reward types:

  1. Jackpot, you win a free ticket for an event.
  2. Normal reward, you win a 50% ticket discount for an event.
  3. Small reward, you win a 25% ticket discount for an event.
  4. Loyalty points, you win between 50 and 100 loyalty points.

If a user wins the jackpot they should not receive any other prices. A user cannot receive the Small and Normal reward at once. A user can, however, get the Normal or Small reward together with loyalty points.

If a user wins the Jackpot, they should be shown a special Jackpot animation. If a user wins the Normal or Small award, they should be taken to a shopping page where they can use their discount if they desire to do so. If they win loyalty points and a reward, they should see a small banner at the top of the screen and they should also be taken to the shopping page. If a user only wins loyalty points, they should only be shown a banner that informs them about their loyalty points.

These are a lot of different paths that might have to be taken in the app, and testing this with a network connection on a real server isn’t feasible. The selection process is random after all! Complex flows like the one described above are another reason that makes faking network responses so convenient. Let’s get started with implementing this complex feature!

Designing the basics

Since this is a complex feature, we’re going to pick and choose a couple of elements from different common app architectures to use as building blocks. As described in the introduction, we’re building a feature where a user is on a certain screen and depending on the result of a network call, they will be taken to a new screen, and potentially they will see an overlay banner. This sounds like a great job for a ViewModel, and we’ll want to extract the networking logic into an object that the ViewModel can use to make network calls.

In this blog post, we will focus on tests for the networking layer.

Preparing the fake responses

When you’re dealing with responses from a server, you often have some kind of documentation or example response to work from. In this case, we have an example JSON response that contains all the information needed to derive every response we can expect from the server:

{
  "rewards": [
    {
      "type": "jackpot" // jackpot, regular or small
    }
  ],
    "loyalty_points": 60 // value between 50 and 100 or null
}

This relatively simple document contains all the information we need right now. It’s tempting to create a bunch of JSON files with different compositions of valid responses and use those while testing. Not a bad approach but we can do better. Since we have the Codable protocol in Swift, we can easily convert JSON data into model objects, but this also works the other way around! We can define our model in the main project, make it Codable and then spawn instances of the model in our test suite, convert them to JSON data and use those as inputs for the networking client. Here’s the model definition we’re using:

struct RewardsResponse: Codable {
  let rewards: [Reward]
  let loyaltyPoints: Int?
}

struct Reward: Codable {
  enum RewardType: String, Codable {
    case jackpot, regular, small
  }

  let type: RewardType
}

This simple model should be sufficient to hold the example JSON data. Let’s add a new file to the test target too, it should be a new Swift file named RewardsResponseFactory. This factory is going to help you create RewardsResponse objects and it will contain a convenient extension on RewardsResponse so you can easily convert the response object to JSON using a JSONEncoder. Add the following code to RewardsResponseFactory.swift:

@testable import RewardsApp

class RewardsResponseFactory {
  static func createResponseWithRewards(_ types: [Reward.RewardType], points: Int?) -> RewardsResponse {
    let rewards = types.map { type in Reward(type: type) }
    return RewardsResponse(rewards: rewards, loyaltyPoints: points)
  }
}

extension RewardsResponse {
  var dataValue: Data {
    let encoder = JSONEncoder()
    encoder.keyEncodingStrategy = .convertToSnakeCase
    return try! encoder.encode(self)
  }
}

You know have everything in place to start writing some tests and implementing the complex reward flow!

Writing your tests and implementation code

Like good practitioners of TDD, we’ll begin implementing our feature in tests first. Once we have a couple of tests outlined we can go ahead and begin implementing the code for our feature.

In Xcode’s Project Navigator, find the test target and rename the default test file and class to RewardsServiceTests. Remove the default code from Xcode’s test template. You should now have the following contents in RewardsServiceTests:

import XCTest
@testable import RewardsApp

class RewardsServiceTests: XCTestCase {
}

We’ll begin by adding tests for the API itself, we want to make sure that the API makes the correct requests and that it converts the JSON data that it receives to RewardResponse objects which are then passed back to the caller of the RewardsService’s fetchRewards method. Quite the mouthful but what we’re testing here is whether the RewardsService can make and handle requests. Since we can’t use the internet in our test, we’ll need to abstract the networking behind a protocol called Networking, add a new file called Networking.swift to your application target and add the following contents to it:

protocol Networking {
  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void)
}

This protocol will allow us to use a fake networking object in our tests and a URLSession in the app. You can use the following extension on URLSession to make it usable as a Networking object in your application:

extension URLSession: Networking {
  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    self.dataTask(with: url) { data, response, error in
      completion(data, response, error)
    }.resume()
  }
}

To write your test, create a new file in your test target and name it MockNetworking.swift. This file will hold the code used to fake networking responses. Don’t forget to add an @testable import statement for the app target and add the following implementation to this file:

class MockNetworking: Networking {
  var responseData: Data?
  var error: Error?

  func fetch(_ url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
    completion(responseData, nil , error)
  }
}

Simple as it might look, this mock networking object is really powerful. You can assign the response data and error, and the networking object will simply invoke the fetch completion handler callback with your predefined response. Users of the mock networking object won’t notice the difference between going to the network or receiving this predefined response which is exactly what we want because now you can use the mock network instead of a URLSession without any changes to your RewardsService. Let’s write some tests for the rewards service and then implement the service itself. Add the following code to the RewardsServiceTests file.

class RewardsServiceTests: XCTestCase {
  func testRewardServiceCanReceiveEmptyRewardsWithNoPoints() {
    let expect = expectation(description: "Expected test to complete")

    let response = RewardsResponseFactory.createResponseWithRewards([], points: nil)
    let mockNetwork = MockNetworking()
    mockNetwork.responseData = response.dataValue
    let service = RewardsService(network: mockNetwork)
    service.fetchRewards { result in
      guard case .success(let response) = result else {
        XCTFail("Expected the rewards to be fetched")
        return
      }

      XCTAssertTrue(response.rewards.isEmpty)
      XCTAssertNil(response.loyaltyPoints)
      expect.fulfill()
    }

    waitForExpectations(timeout: 1, handler: nil)
  }
}

This code uses the factory you created earlier to generate a response object. The extension on RewardsResponse that you added to the test target is used to convert the response into JSON data and you assign this data to the mock network. Next, the rewards service is initialized with the mock network and we call its fetchRewards method with a completion closure that fulfills the test expectation and checks whether the rewards response has the expected values.

Tip:
If you’re not quite sure what a test expectation is, check out Getting Started With Unit Testing - part 2. This blog post covers the basics of testing asynchronous code, including test expectations.

Your tests don’t compile yet because you haven’t implemented the RewardsService yet. Add a new Swift file called RewardsService.swift to your application target and add the following implementation to this file:

struct RewardsService {
  let network: Networking

  func fetchRewards(_ completion: @escaping (Result<RewardsResponse, Error>) -> Void) {
    network.fetch(URL(string: "https://reward-app.com/rewards")!) { data, urlResponse, error in
      let decoder = JSONDecoder()

      decoder.keyDecodingStrategy = .convertFromSnakeCase

      guard let data = data,
        let response = try? decoder.decode(RewardsResponse.self, from: data) else {
          // You should call the completion handler with a .failure result and an error
          return
      }

      completion(.success(response))
    }
  }
}

This is a pretty minimal implementation and that’s fine. Since we’re separating concerns in this little feature as much as possible it isn’t surprising that every single bit of implementation code we’ve written so far was fairly small; every bit of out code only does a single thing, making it easy to use in a test. Go ahead and run your test now, it should succeed!

Try adding a couple more test cases for using the following calls to the RewardsResponseFactory:

let response = RewardsResponseFactory.createResponseWithRewards([.jackpot], points: nil)
let response = RewardsResponseFactory.createResponseWithRewards([.regular], points: 50)
let response = RewardsResponseFactory.createResponseWithRewards([], points: 100)
let response = RewardsResponseFactory.createResponseWithRewards([.small, .regular], points: 70)
let response = RewardsResponseFactory.createResponseWithRewards([.small, .regular], points: nil)

There are many more possible combinations for you to test, go ahead and add as many as you want. Don’t forget to keep your code DRY and use XCTest's setUp method for shared configuration to avoid lots of boilerplate code in every test.

Next steps

If you have implemented the tests I suggested, you should currently have a pretty solid test suite set up for the RewardsService. Your suite tests whether the service can decode many different shapes of responses. If you got stuck implementing these tests, fear not. You can go to this blog post’s accompanying Github Repository and look at the ServiceTests folder to see the results.

But the feature I described at the start of this post isn’t complete yet! Try to add some tests for a RewardsViewModel, maybe add some tests to see whether your coordinator can determine the correct next view controller based on the response from the RewardsService. In the Completed folder in this post’s Github Repository, you’ll find an example of a completed test suite that covers the entire rewards feature!

In summary

In this post, you have learned a lot about testing code that involves using data that comes from a remote source. You saw how you can hide networking logic behind a protocol, allowing you to mock the object that’s responsible for ultimately making the network call.

I have also shown you how you can leverage extensions and Swift’s Codable protocol to generate response data based on the objects you ultimately want to decode. Of course, there are many other ways to obtain such mock data, for example by adding prepared JSON files to your test target, or possibly running a local web server that your tests can use instead of a remote server. These are all valid ways to test networking logic, but I use and prefer the method described in this post myself.

Thank you for reading this post! And as always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

An in-depth look at for loops in Swift

In Swift, there is more than one way to loop over a list of elements. If we only consider the loops that aren't intended to create new lists or have side effects, you end up with two types of loops that are very similar; forEach and for item in list, or the for loop. In this week's Quick Tip, I will explain the difference between for and forEach loops, and you I will show some examples that should help you make a decision picking one of either loop methods, depending on your needs.

Understanding for loops

Commonly when you write code that need to do something with every item in a list (Array, Set or other Sequence), you might write something like the following:

let list = [1, 2, 3, 4, 5]

for item in list {
  // do something with item
}

This is possibly the most basic example of a for loop and I'm sure you have written loops like this at least once before you found this Quick Tip. So, what else can we do with for loops?

A cool feature that for loops have is that you can apply a filter to them using a where clause. This means that your loop body is only called for elements that meet the requirements specified in the where clause. The following code is an example of such a filter:

let list = [1, 2, 3, 4, 5]

for item in list where item > 2 {
  print(item)
}

The above code applies a where clause to the for loop that makes sure any element that we receive in the for loop's body is larger than two. This can make your for loop more readable than checking this criterion inside of the for loop body. This segues nicely into learning about the continue keyword that can be used in a for loop!

When you write continue in a for loop, you are telling Swift to stop executing the for loop body for the current item and move over to the next item in the list. The following code produces the same output as the for loop that has a where clause:

let list = [1, 2, 3, 4, 5]

for item in list {
  guard item > 2
    else { continue }

  print(item)
}

In terms of performance, you won't really notice a difference between using a guard or a where clause. You might think that the where clause would make sure you don't loop over as many items but this is not entirely true. Swift will still need to make sure whether each item meets the criteria specified in the where clause. The bigger difference in my personal opinion is readability and semantics. The version that has a where clause tells me exactly under what condition a certain item is passed on to the for loop's body, but it also makes it very easy to spot that there is a condition present in the first place. In the example with the guard, you need to spend a little bit more time looking at the code to figure out that we need the item to be larger than two. Of course, a where clause isn't always the best option, especially if there are multiple, potentially complicated, reasons for an item to be considered skippable.

Sometimes, you don't want to loop over every item in a list. You might want to only loop until you have found a certain item, much like Swift's first(where:) method does. Or maybe you just want to collect items from an array of numbers until you've reached a certain accumulated value. An example of this is shown in the following code snippet:

let list = [1, 2, 3, 4, 5]
var accumulated = 0

for item in list {
  accumulated += item

  if accumulated >= 4 {
    break
  }
}
print(accumulated)

This code loops through items and adds them to an accumulated value. Once this accumulated value is larger than or equal to 4, the break keyword is used to break out of the for loop. Doing this abandons the loop altogether, unlike continue which will only cancel the current executing of the for loop body, allowing the next item to be processed. In the example above, we would loop over 1, then 2 and finally 3. This puts the accumulated value at 6, and we will never reach the 4 or 5 in the list since the loop is cancelled using the break keyword.

Another way to break out of a for loop early is by returning a value:

let list = [1, 2, 3, 4, 5]

func findFirstItemLargerThan(_ threshold: Int) -> Int? {
  for item in list {
    if item > threshold {
      return item
    }
  }

  return nil
}

print(findFirstItemLargerThan(3))

Returning a value from a for loop will prevent further invocations of the loop body and immediately returns the provided value to the caller of the method that contains the for loop. This can be useful if you're implementing a function that should find the first match in a list, like in the example above.

Two other important features of for loops are the ability to use named for loops, and the ability to nest for loops. Let's look at both of these features in a single example:

let list = [1, 2, 3, 4, 5]

outerLoop: for item in list {
  for nestedItem in list {
    if item == nestedItem {
      continue outerLoop
    }

    print(item, nestedItem)
  }
}

By prepending the outer for loop with the outerLoop: label, we can now refer to it from inside of the inner loop. In the preceding code example, we loop over a list of integers in the outer loop. We loop over the same list in the inner loop. If we would omit the if statement and continue statement you would see an output that roughly looks like this:

1 1
1 2
1 3
1 4
1 5
2 1

//etc...

With the continue outerLoop statement in place, you see the following output:

2 1
3 1
3 2
4 1

//etc...

If you try removing the outerLoop label from the continue statement, you'll find that only the entries where item and nestedItem are equal will not be printed since the inner loop would move on to the next item in the inner list. By putting the outerLoop after the continue statement, the outer loop itself will proceed to the next item in the outer list. Pretty powerful stuff!

In this short introduction to for loops, you have seen several features and ways to use for loops. However, I promised you a comparison between for and forEach, so let's have a look at what a forEach loop is, and how it's different from a for loop.

Comparing for loops to forEach loops

Honestly, it's almost unfair to compare for loops to forEach loops, because if you look at how forEach is defined in the Swift language you might quickly understand how they're different:

@inlinable
public func forEach(_ body: (Element) throws -> Void) rethrows {
  for element in self {
    try body(element)
  }
}

We can see that forEach is a method that takes a closure and uses a for loop to iterate over all of the elements in the list and invokes the closure with each element as the argument. Let's look at a simple usage example:

let list = [1, 2, 3, 4, 5]

list.forEach { item in
  print(item)
}

So, what are the differences between for and forEach then? If they both are really just a for loop, they must be pretty much the same right? Unfortunately, they are not the same. A forEach loop lacks the capability to apply a where clause, use a continue statement, you can't return anything from a forEach loop and you also can't break out of it. You can nest forEach loops but you can't label them and you can't access (or continue / break / return from) the outer loop.

A forEach will always iterate over all elements in your list and apply whatever closure you pass it to every single element in your list, no exceptions.

In summary

In this blog post, you learned a lot about for loops, and you saw how Swift implements the forEach method. Now that you understand the differences between the two different ways of looping, you should be able to determine which loop you need. Do you need to break out of the loop early? Or maybe you want to use a where clause to prevent the loop body from being called for every item. Or maybe you're just looking for the first item to match a certain condition and you want to return that item. In all of these scenarios you'll want to use a for loop or a built-in function like first(where:) from the Swift standard library.

Alternatively, if you want to loop over every single item in an array or another type of list, without any exceptions, you can safely use forEach since that's what it's good at.

Thanks for reading this blog post and as always, your input, feedback, and questions are highly appreciated. Reach out on Twitter if you want to get in touch!

Optimizing Your Application’s Reviews

We all love the idea of getting loads of App Reviews, preferably with five stars and a description that explains why our apps are amazing. Unfortunately, users often don’t take the time to reviews the apps they enjoy. Instead, users will review your app when they’re unhappy. If something about your app doesn’t please them or something doesn’t work as expected, they will happily write an angry review and rate your app one star. So how do we get people to review apps when they are happy so they give us five stars? That’s exactly what I would like to discuss in this blogpost. By the end of this post you will have learned the following:

  • How to use iOS’ built-in review prompt to allow users to rate your app without going to the App Store.
  • Timing your review prompt to avoid frustrating your users.

Let’s dive right in, shall we?

Using iOS’ built-in review prompt

In iOS 10.3 Apple shipped an amazing little feature that might have gone unnoticed for many people. It’s called SKStoreReviewController and Apple describes it as follows:

An object that controls the process of requesting App Store ratings and reviews from users.

Sounds good right? Before I explain how you can use it in your app, let’s see what it does:

In the above screenshot, you can see a neat prompt that asks a user to review the current app. They can immediately tap the number of stars they want to give your app and they can choose to write a review if they please. Because this prompt is so straightforward to use, users are far more likely to rate your app than when you would show them a different kind of alert. For example, the following alert is far less likely to get the same amount of engagement as the SKStoreReviewController version.

Using the SKStoreReviewController is astonishingly straightforward. The following code is all you need to show a rating prompt:

SKStoreReviewController.requestReview()

Don’t forget to import StoreKit in the file where you wish to ask for reviews.

When your app is in production and you call requestReview, iOS will not always present a review prompt to the user. For example, if they have already rated your app or choose not to rate your app, iOS might choose to not ask the user again for a while until you have shipped an app update.

Now that you know how to ask for a review, let’s see how you can optimize when to ask your users to review your app.

Improving your review prompt strategy

Some apps are very eager to ask users for reviews and they don’t have a good strategy in place. Or any strategy at all for that matter. When a user has just downloaded your app, it’s not the best time to ask them for reviews. They are exploring your app, trying to figure out what it does and whether it suits their needs. They are still forming an opinion to see if they wish to use your app more often, or maybe look further for a different app that has similar features. Asking for a review at that moment isn’t a great look, your app will come across as a little pushy, inconsiderate and not very optimized for the user. This will be review prompt strategy lesson one:

Lesson one:
Don’t ask users for review during their first time using the app. Wait until the user has used your app a couple of times over a several day period.

Often users will come to your app with a goal. Sometimes the goal is small, like checking the weather and sometimes the goal is more complex, like writing a document or email. Interrupting users to ask them to review your app is unlikely to make your users happy. They might decide to simply close your prompt and get on with their task. If they get interrupted by review prompts too often, they might just rate your app one star, just to get it over with. Instead, try to postpone asking for reviews until your user has achieved their goal. You are far more likely to get a positive response from your users.

Lesson two:
Interrupting users to get them to review your app isn’t great. Try asking for reviews at the end of a task, game or after the user has found the information they were looking for.

Lastly, if a user has just had a negative experience with your app, you might want to hold off asking for a review. Imagine you’ve built a game and the user has failed to complete a level several times. They’ve spent the better part of an hour trying but simply couldn’t succeed. Normally, you might consider a user that returns to the game’s home screen as a user that completed a task and ask them for a review. However, this particular user hasn’t had the best experience today so it’s best to ask them for a review some other time, maybe after they have completed a couple of levels in your game without. Users that just had a positive experience are far more likely to bless your app with a good review than users that are frustrated.

Lesson three:
Asking users for reviews when they just had a less than ideal experience isn’t going to give you the best results. Instead, try to ask for a review after the user has done something positive in your app, like win a game for example.

Knowing these lessons is great, but how can you determine the best time to ask for a review, especially if you don’t want to ask a user for a review on their first launch, or after they have just lost a game. In the next section, I will show you a simple approach that uses the UserDefaults storage to keep track of some statistics.

Implementing your review strategy

Keeping track of what a user has done in your app, and whether it’s a good time to ask for reviews is essential to get right. Luckily, it’s not very complex to store some basic information in UserDefaults. Personally, I like to wrap all logic related to collecting reviews in a ReviewManager object that has access to a UserDefaults store:

struct ReviewManager {
  let userDefaults: UserDefaults

  init(userDefaults: UserDefaults = UserDefaults.standard) {
    self.userDefaults = userDefaults
  }
}

Some of the basic functionality for this ReviewManager typically is to track the first launch date, the number of total launches and an askForReviewIfNeeded() method:

struct ReviewManager {
  let userDefaults: UserDefaults
  let minimumInstallTime: TimeInterval = 60 * 60 * 24 * 30 // 1 month
  let minimumLaunches = 10

  init(userDefaults: UserDefaults = UserDefaults.standard) {
    self.userDefaults = userDefaults
  }

  func trackLaunch() {
    if userDefaults.double(forKey: "firstLaunch") == 0.0 {
      userDefaults.set(Date().timeIntervalSince1970, forKey: "firstLaunch")
    }

    let numberOfLaunches = userDefaults.integer(forKey: "numberOfLaunches")
    userDefaults.set(numberOfLaunches + 1, forKey: "numerOfLaunches")
  }

  func askForReviewIfNeeded() {
    guard shouldAskForReview()
      else { return }

    SKStoreReviewController.requestReview()
  }

  private func shouldAskForReview() -> Bool {
    let timeInstalled = Date().timeIntervalSince1970 - userDefaults.double(forKey: "firstLaunch")
    let numberOfLaunches = userDefaults.integer(forKey: "numberOfLaunches")

    guard timeInstalled > minimumInstallTime,
      numberOfLaunches > minimumLaunches
      else { return false }

    return true
  }
}

Of course, you can expand the logic to determine whether it is appropriate to ask for a review, for instance by tracking the number of games played or won in a game, the number of documents created or other metrics that you think are relevant to your user. Make sure to pass your reviewManager around in your application to the relevant places so they can call askForReviewIfNeeded whenever you think it’s a good moment to do so.

Summary

In this post, you have learned about the SKStoreReviewController and how it allows you to prompt users to review your app in a convenient manner. You have also learned several essential rules to ensure you ask for reviews when users are most likely to leave you a positive review. Lastly, you also learned how you can keep track of a user’s behavior in your app by storing values in UserDefaults and using them to determine whether it’s a good time to ask your user for an app review.

As always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

What is Module Stability in Swift and why should you care?

The Swift team has recently released Swift 5.1. This version of the Swift language contains many cool features like Function Builders that are used for SwiftUI and Property Wrappers that can be used to add extra functionality to properties. This release also contains a feature called Module Stability. But what is this feature? And what does it mean to you as a developer? In this week’s blog post, I will explain this to you. But before we get to Module Stability, let’s do a little time traveling back to Swift 5.0, which shipped with ABI (Application Binary Interface) Stability.

Understanding ABI Stability

Up to Swift 5.0, every time you built an app with Swift, your app would include the entire Swift standard library and runtime. The reason for this was that Swift was changing so much that it wasn’t feasible to include the Swift fundamentals into releases of iOS. Swift 5.0 changed this with ABI Stability. Swift now defines a stable binary interface that will not change across Swift versions. In practice, this means that the Swift 5.1 (or later) runtime can execute code that was compiled with the Swift 5.0 compiler due to this stable interface definition.

When you write code, you do not need to understand or think about this binary interface, or ABI. This is all handled internally by the Swift compiler and it’s possible that the biggest win for your app is simply that your application binary is smaller for iOS versions that include the Swift runtime because the App Store will now strip the Swift runtime out of your binary if your app is being downloaded on a platform that has ABI Stability.

One common misunderstanding I’ve seen with ABI stability is that people were hoping that it would allow them to use frameworks that are compiled with Swift 5.0 in a project that uses the Swift 5.1 compiler. Unfortunately, this is not the case and the following error is likely one that you’ve encountered when you upgraded from Swift 5.0.1 to Swift 5.1.

Module compiled with Swift 5.0.1 cannot be imported by the Swift 5.1 compiler

This brings us to the Module Stability introduced by Swift 5.1.

Understanding Module Stability

While Module Stability and ABI stability are completely different animals altogether, they are also quite similar. While ABI Stability allows programs written with different versions of Swift to exist in a shared runtime, Module Stability allows you to use frameworks compiled with different versions of swift in a project that might use yet another version of Swift. The way this works is best explained by the following excerpt from the Swift 5.1 release announcement:

Swift 5.1 enables the creation of binary frameworks that can be shared with others leveraging the language’s added support for module stability. Module stability defines a new text-based module interface file that describes the API of a binary framework, allowing it to be compiled with code using different versions of the compiler.

Binary frameworks that were built with Module Stability enabled contain a special folder with the .swiftmodule suffix which contains .swiftinterface and .swiftmodule files. These files are the text-based definition of your framework's API that is mentioned in the Swift 5.1 release announcements. To build your own frameworks with support for module stability you need to enable the Build Library for Distribution setting in your framework's Project Settings pane and set the Skip Install property to NO. After doing this, Xcode includes the .swiftmodule in your product when you archive it, making your framework "module stable".

Every binary framework that is compiled with the Swift 5.1 compiler and Xcode 11 or later can be used in any project that uses Swift 5.1 or later as long as the Build Library for Distribution and Skip Install properties are configured correctly. So, in short, Module Stability means that you should never see an error message that reads “Module compiled with Swift 5.1 cannot be imported by the Swift 5.2 compiler” because all frameworks that were built with module stability are automatically compatible with the Swift version it was built for and newer. Pretty cool, right?

In conclusion

Now that we have Module Stability and ABI Stability in Swift, the language is likely to change at a slower rate than we’re used to. We should see less radical, source breaking changes and the language should slowly mature into a beautiful, fast and stable language that will be a great basis for your applications for years to come. However, don’t think that Swift will stop innovating and evolving. The Swift Evolution forum is still going strong and folks are still working very hard to make Swift a versatile, safe and clean language to work with.

As always, feedback, compliments, and questions are welcome. You can find me on Twitter if you want to reach out to me.

Finding the difference between two Arrays

Many applications work with data, often they are built to retrieve data and display this data to the user in a table view, collection view, list (if you're using SwiftUI) or a different kind of component. It's not uncommon for this data to change and when it does you might be interested in figuring out what elements were added to the data and which items were removed. This isn't always straightforward so up until now you might have written code something like the following:

func didFetchNewRecipes(_ newRecipes: [Recipe]) {
  recipes = newRecipes
  tableView.reloadData()
}

Simple and effective, much easier than figuring out the difference and only reloading the cells that have changed. On iOS 13, you can use new methods on any collection that conforms to BiDirectionalCollection to determine the changes between two collections as long as the items that are contained in the collection conform to Equatable. You can then use the information from this method to update a table view:

func didFetchNewRecipes(_ newRecipes: [Recipe]) {
  let changes = newRecipes.difference(from: recipes)

  let insertedIndexPaths = changes.insertions.compactMap { change -> IndexPath? in
    guard case let .insert(offset, _, _) = change
      else { return nil }

    return IndexPath(row: offset, section: 0)
  }

  let removedIndexPaths = changes.removals.compactMap { change -> IndexPath? in
    guard case let .remove(offset, _, _) = change
      else { return nil }

    return IndexPath(row: offset, section: 0)
  }

  self.recipes = newRecipes
  tableView.beginUpdates()
  tableView.insertRows(at: insertedIndexPaths, with: .automatic)
  tableView.deleteRows(at: removedIndexPaths, with: .automatic)
  tableView.endUpdates()
}

Tip:
If your elements are not Equatable, use the differences(from:by:) method to supply a closure where you can perform your own comparison to determine if two elements are equal.

Cool stuff right? What kind of applications do you see for this kind of method? Let me know on Twitter!

Getting started with unit testing your Swift code on iOS – part 2

In part 1 of this two-part blog post, you’ve learned how to write synchronous unit tests for a login view model. As a reminder, you saw how to implement tests for the following requirements:

  • When both login fields are empty, pressing the login button should trigger an error that informs the user that both fields are mandatory.
  • When one of the two fields is empty, pressing the login button should trigger an error that informs the user that the empty field is mandatory.
  • When the user’s email address does not contain an @ symbol, pressing the login button should trigger an error that informs the user that they must provide a valid email address.

You implemented these requirements using a LoginFormViewModel that has two properties for the username and password and a login method. And of course, you wrote tests for every requirement listed. If you want to start following this blog post with the final code of the previous part, check out the HelloUnitTest-PartOne folder in this blog post’s GitHub repository.

In this second part, you will learn how to refactor the current test suite to support asynchronous code. You will also implement a networking abstraction that will act as a fake server for login requests.

The objective of this blog post

Just like in the previous post, you will implement tests for a couple of requirements. This time, there are just two requirements:

  • When the user’s credentials are filled in, but the server doesn’t recognize the email address or the password is wrong, an error should be triggered that informs the user that their credentials were incorrect.
  • When the user’s credentials are filled in and they are valid, the login method should invoke the completion handler with a User object.

So, without further ado, let’s dive right in and begin refactoring and writing some more test code!

Getting to work

The first requirement we’re going to implement is the following:

  • When the user’s credentials are filled in, but the server doesn’t recognize the email address or the password is wrong, an error should be triggered that informs the user that their credentials were incorrect.

Note that this requirement speaks of a server. In this post, I will not focus on the nitty-gritty details and many possibilities of setting up a mock networking layer. Instead, I will show you a basic form of abstracting a network layer so you can do testing with it. First, let’s refactor the existing code from part 1 so it’s ready for the new asynchronous nature of the tests you’re going to write in this post.

In order to make the form view model asynchronous, we’re going to change the code first and update the existing tests after. I will assume that your code looks identical to the code that’s in the HelloUnitTests-PartOne folder of this blog post’s GitHub repository. The login method on the LoginFormViewModel should be changed so it supports asynchronous execution. This means that the method should no longer throw errors and instead invoke a completion handler with a result of type Result<User, Error>. You will define user as an empty struct since we’re not going to actually decode and create User objects later. The definition of User should look as follows:

struct User {

}

Completely empty. Simple right? You can either put it in its own file or throw it in the LoginFormViewModel.swift file since it’s just an empty struct. Next, rewrite the login method as follows:

func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
  guard let password = password, let username = username else {
    if self.username == nil && self.password == nil {
      completion(Result.failure(LoginError.formIsEmpty))
    } else if self.username == nil {
      completion(Result.failure(LoginError.usernameIsEmpty))
    } else {
      completion(Result.failure(LoginError.passwordIsEmpty))
    }
    return
  }

  guard username.contains("@") else {
    completion(Result.failure(LoginError.emailIsInvalid))
    return
  }
}

If at this point you’re thinking: “Hey! This isn’t right. TDD dictates that we change our tests first”, you would be absolutely right. However, I find that sometimes it’s simpler to have a bunch of passing tests, then refactor your logic, and refactor the tests to match afterwards. It’s really a preference of mine and I wanted to make sure you see this method of writing code and tests too.

Let’s refactor the tests so they compile and pass again. I will only show you how to refactor a single test case, all the other test cases should be refactored in a similar way. Again, if you get stuck, refer to this blogpost’s GitHub repository. The solutions for this part of the post are in the HelloUnitTests-PartTwo folder. Refactor the testEmptyLoginFormThrowsError method in your test file as follows:

func testEmptyLoginFormThrowsError() {
  XCTAssertNil(loginFormViewModel.username)
  XCTAssertNil(loginFormViewModel.password)

  // 1
  let testExpectation = expectation(description: "Expected login completion handler to be called")

  loginFormViewModel.login { result in
    guard case let .failure(error) = result, case error = LoginError.formIsEmpty else {
      XCTFail("Expected completion to be called with formIsEmpty")
      testExpectation.fulfill()
      return
    }

    // 3
    testExpectation.fulfill()
  }

  // 2
  waitForExpectations(timeout: 1, handler: nil)
}

This test contains a couple of statements that you haven’t seen before. I have added comments with numbers in the code so we can go over them one by one.

  1. This line of code creates a so-called test expectation. An expectation is an object that is used often when testing asynchronous code. Expectations start out in an unfulfilled state and remain like that until you call fulfill on them. If you never call fulfill, your test will eventually be considered failed.
  2. At the end of the test method, waitForExpectation is called. This method instructs the test that even though the end of the control flow has been reached, the test is not done executing yet. In this case, the test will wait for one second to see if all expectations are eventually fulfilled. If after one second there are one or more unfulfilled expectations in this test, the test is considered to be failed.
  3. Since the login method is now asynchronous, it received a callback closure that uses Swift’s Result type. Once the result has been unpacked and we find the error that we expect, the expectation that was created earlier is fulfilled. If the expected error is not found, the test is marked as failed and the expectation is also fulfilled because the completion handler was called. There is no need to wait a full second for this test to fail since the XCTFail invocation already marks the test as failed.

Now that login supports callbacks, we can write a new test that makes sure that the LoginFormViewModel handles server errors as expected. To do this, we’re going to introduce a dependency for LoginFormViewModel. Using a specialized object for the networking in an application allows you to create a mock or fake version of the object in your test, which provides a high level of control of what data the fake network object responds with.

This time, we’re going to jump from implementation code into tests and then back into implementation code. First, add the following protocol definition to LoginFormViewModel.swift:

protocol LoginService {
  func login(_ completion: @escaping (Result<User, LoginError>) -> Void)
}

This protocol is fairly straightforward. It dictates that any networking object acting as a login service must have a login method that takes a completion handler that takes a Result<User, LoginError> object. This method signature is identical to the one you’ve already seen on the LoginFormViewModel. Next, add a property to the LoginFormViewModel to hold the new login service:

let loginService: LoginService

Finally, call the login method on the login service at the end of LoginFormViewModel’s login method:

func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
  // existing code

  guard username.contains("@") else {
    completion(Result.failure(LoginError.emailIsInvalid))
    return
  }

  loginService.login(completion)
}

So far so good, now let’s make the tests compile again. Add a property for the login service to your test class and update the setUp method as shown below:

var loginService: MockLoginService!

override func setUp() {
  loginService = MockLoginService()
  loginFormViewModel = LoginFormViewModel(loginService: loginService)
}

Note that this code uses a type that you haven’t defined yet; MockLoginService. Let’s implement that type now. Select your test suite’s folder in Xcode’s File Navigator and create a new Swift file. When naming the file, double check that the file will be added to your test target and not to your application target. Name it MockLoginService and press enter. Next, add the following implementation code to this file:

import Foundation
@testable import HelloUnitTests

class MockLoginService: LoginService {
  var result: Result<User, LoginError>?

  func login(_ completion: @escaping (Result<User, LoginError>) -> Void) {
    if let result = result {
      completion(result)
    }
  }
}

The code for this file is pretty simple. The mock login service has an optional result property that we’re going to give a value later in the test. The login method that will be called from the login view model immediately calls the completion block with the result that is defined by the test. This setup is a cool way to fake very basic network responses because your application code can now use any object that conforms to LoginService. You create and inject a real networking object if you’re starting your app normally, and you create and inject a mock networking object when you’re running tests. Neat!

Note that the MockLoginService is a class and not a struct. Making this object a class ensures that any assignments to result are applied to all objects that have a reference to the MockLoginService. If you’d make this object a struct, every object that receives a MockLoginService will make a copy and assignments to result would not carry over to other places where it’s used.

Your tests should still run and pass at this point. We are now ready to finally add that test we set out to write in the first place:

func testServerRespondsWithUnkownCredentials() {
  loginFormViewModel.username = "[email protected]"
  loginFormViewModel.password = "password"
  XCTAssertNotNil(loginFormViewModel.username)
  XCTAssertNotNil(loginFormViewModel.password)

  loginService.result = .failure(LoginError.incorrectCredentials)

  let testExpectation = expectation(description: "Expected login completion handler to be called")

  loginFormViewModel.login { result in
    guard case let .failure(error) = result, case error = LoginError.incorrectCredentials else {
      XCTFail("Expected completion to be called with incorrectCredentials")
      testExpectation.fulfill()
      return
    }

    testExpectation.fulfill()
  }

  waitForExpectations(timeout: 1, handler: nil)
}

The above code looks very similar to the asynchronous test code you’ve already written. The major difference here is on line 7: loginService.result = .failure(LoginError.incorrectCredentials). This line of code assigns a failure response with a specific error to the mock login service’s result property. All other code is pretty much unchanged when you compare it to your previous tests. Now add the incorrectCredentials error case to your LoginError enum and run the tests. They should pass, which indicates that the LoginFormViewModel uses a networking object and that it forwards errors that it received from the networking object to the calling object.

You now have enough information to implement the final test case yourself:

  • When the user’s credentials are filled in and they are valid, the login method should invoke the completion handler with a User object.

When you’re done and want to check your solution, or if you’re stuck, don’t hesitate to check out the GitHub repository for this post.

Next steps

First of all, congratulations on making it this far! You’ve achieved a lot by working through all of the examples shown in this two-part post. Part one showed you how to write simple unit tests, and with the information from this second part, you can even write tests for asynchronous code. Moreover, you now even have an idea of how you can architect your application code to be easily testable by using protocols as an abstraction layer.

Of course, there is much more to learn about testing. For now, you should have plenty of information to stay busy, but if you’re eager to start learning more, I can recommend taking a look at some other blog posts I wrote about testing:

Alternatively, you can try to refactor the test suite a little bit by removing some of the code duplication you’ll currently find in there. After all, test code is code too, and we should aim to make it as DRY and high-quality as possible.