Setting up a Core Data store for unit tests

Unit testing is an important skill in the toolbox of any engineer. Knowing how to write a reliable, robust test suite helps you write reliable and robust code. If you've followed my introduction to unit testing part one and part two, or if you're experienced with unit testing you know that your tests should run isolated from any other tests. You also know that you should make sure that your test relies on as few external dependencies as possible.

When you want to test your Core Data code, it might not be immediately obvious how you can test your Core Data store in isolation.

Luckily, you can configure your NSPersistentContainer to write data to memory rather than disk. There are two ways to achieve this. One option is to create a persistent store description that has its type property set to NSInMemoryStoreType. When you use this method of creating an in-memory store, you will find that certain features like cascading deletes might not work as expected. These features appear to rely on features from the default storage (SQLite) that are not available to an in-memory store.

Luckily we can use an SQLite store that writes to memory by configuring a persistent store that writes data to /dev/null/ instead of an actual SQLite file:

lazy var persistentContainer: NSPersistentContainer = {
  let container = NSPersistentContainer(name: "YourDataStore")

  let description = NSPersistentStoreDescription()
  description.url = URL(fileURLWithPath: "/dev/null")
  container.persistentStoreDescriptions = [description]

  container.loadPersistentStores(completionHandler: { _, error in
    if let error = error as NSError? {
      fatalError("Failed to load stores: \(error), \(error.userInfo)")
    }
  })

  return container
}()

Most of this code should look familiar to you. The only difference between this code and a standard Core Data setup are the following three lines:

let description = NSPersistentStoreDescription()
description.url = URL(fileURLWithPath: "/dev/null")
container.persistentStoreDescriptions = [description]

These three lines of code create a persistent store description that informs the persistent container that it should write data to /dev/null while using the default SQLite storage mechanism.

While it's not documented anywhere publicly, Apple's current recommendation appears to favor using an SQLite store that writes to /dev/null over an NSInMemoryStoreType based store. Writing to /dev/null effectively uses an in-memory store, except you get all the features that you also get from the SQLite store that your app uses. This makes unit testing with a /dev/null based store far more accurate than an NSInMemoryStoreType based store.

Note:
The initial version of this article covered NSInMemoryStoreType. Thanks to some feedback and information from Geoff Pado and Vojta Stavik I found out that writing to /dev/null is the currently preferred way to create an in-memory store. Apple talks about it in this WWDC video, and you can learn more about in-memory SQLite stores here.

Unfortunately, Apple has not updated their documentation for NSInMemoryStoreType to express their latest recommendations so using the /dev/null based approach will probably remain somewhat obscure for a while.

Exposing a Core Data stack for testing from your app

Since you don't want to add any app-related code to your test target, you need to have a way to expose your testing stack to your unit tests from within your app.

I like to create a very lightweight abstraction where I initialize and manage my Core Data stack. A very bare implementation of such an abstraction looks like this:

class CoreDataStore {
  let persistentContainer: NSPersistentContainer

  init() {
    self.persistentContainer = NSPersistentContainer(name: "YourDataStore")

    self.persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
  }
}

The code above is pretty basic but what's nice about it, is that the initialization of my persistent container is hidden from the rest of my app. This means that I have full control over how my persistent container is initialized and that I can set it up however I want.

An abstraction like this works well when you use dependency injection in your app.

Tip:
Did you know you can use dependency injection with storyboards in iOS 13 and above? Learn more here.

When you use dependency injection you can inject a different datastore into the objects that require a data store if you want to use them in your tests.

To make the abstraction I've just shown you useful for unit testing, we need to make a couple of modifications so we can choose whether a specific instance of CoreDataStore should use a regular SQLite backed store or an in-memory store:

enum StorageType {
  case persistent, inMemory
}

class CoreDataStore {
  let persistentContainer: NSPersistentContainer

  init(_ storageType: StorageType = .persistent) {
    self.persistentContainer = NSPersistentContainer(name: "YourDataStore")

    if storageType == .inMemory {
      let description = NSPersistentStoreDescription()
      description.url = URL(fileURLWithPath: "/dev/null")
      self.persistentContainer.persistentStoreDescriptions = [description]
    }

    self.persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
  }
}

With the code above, we can create a new CoreDataStore instance and pass inMemory to its initializer to create an instance of CoreDataStore that's useful for testing. In our application code, we can explicitly pass persistent to the initializer, or pass no arguments at all because persistent is the default store type.

Imagine that you have a view model object called MainViewModel that looks as follows:

struct MainViewModel {
  let storage: CoreDataStore

  // and of course it has a bunch of properties and method
}

You can create an instance of MainViewModel in your AppDelegate, SceneDelegate, or App struct if you're using SwiftUI 2.0 as follows:

let coreDataStore = CoreDataStore()
let viewModel = MainViewModel(storage: coreDataStore)

Note:
Every time you initialize a new CoreDataStore the persistent stores are loaded again. Make sure that you only create a single instance of your Core Data storage object to avoid loading multiple copies of your persistent store.

In your tests you can create an instance of your MainViewModel that uses a temporary in memory Core Data store as follows:

let coreDataStore = CoreDataStore(.inMemory)
let viewModel = MainViewModel(storage: coreDataStore)

The view model does not "know" whether it runs in a testing environment or an app environment. It just knows that a store exists and that this store is used to read and write data.

Every time you create a new in-memory instance of CoreDataStore, you start with a fresh database. This is perfect for unit testing and you should create a new instance of your CoreDataStore for every unit test you run to make sure it has a fresh, non-polluted database that is not modified by external actors.

In Summary

In this week's post, you saw how you can create and use an in-memory version of your Core Data database that is optimized for testing. In-memory storage is wiped as soon as the persistent container associated with the in-memory store goes out of memory, and every persistent container that's created with an in-memory store starts with a completely clean slate, making it perfect for unit tests.

You also saw how you can create a very simple and plain abstraction around your Core Data store to make it very simple to create persistent and in-memory instances of your persistent container.

If you have any questions or feedback for me, make sure to reach out to me on Twitter. I would love to hear from you.

Using Core Data with SwiftUI 2.0 and Xcode 12

In Xcode 12 you can create projects that no longer use an AppDelegate and SceneDelegate to manage the application lifecycle. Instead, we can use Swift's new @main annotation to turn a struct that conforms to the App protocol into the main entry point for our applications.

When you create a new project in Xcode 12, you have the option to use the SwiftUI App application lifecycle for your SwiftUI project.

While Xcode 12 beta 5 introduces an option to include Core Data in your SwiftUI application when you create a new project, you might have an existing SwiftUI project that doesn't use Core Data. Alternatively, you might just be curious how you could manually integrate Core Data in a SwiftUI project since there is nothing magic about the new Core Data template provided by Apple.

Adding Core Data to a SwiftUI project just takes two small steps:

  • Add a Core Data model file to your project
  • Initialize an NSPersistentContainer

Adding a Core Data model file to your project

To add a new Core Data model file to your project select File -> New -> File (cmd + N) and select Data Model from the Core Data section in the file type picker.

New file picker for Core Data model

After selecting this, pick a name for your model. The default that Xcode used to pick is the name of your project but you can choose any name you want.

I personally usually go with the name of my project for no reason other than it feeling familiar since it's the default name Xcode would have chosen in older Xcode versions. Currently, the default name Xcode would pick for you is Model which is a perfectly fine name too.

And that's all there is to it. Your Core Data model file is now added to your project and available to use by an NSPersistentContainer.

Initializing an NSPersistentContainer

Since iOS 10, the recommended way to use and manage a Core Data stack is through NSPersistentContainer. When Xcode generates a Core Data implementation for you, it uses an NSPersistentContainer too. You can initialize an NSPersistentContainer anywhere you want. I will show you how to initialize it as a property in your App struct, but you could just as well initialize the NSPersistentContainer in a dedicated data source object. I would recommend against initializing your NSPersistentContainer from within a View, but it's possible. Keep in mind that you should only load your container once though.

Let's look at the code needed to instantiate an NSPersistentContainer:

struct MyApplication: App {
  let persistentContainer: NSPersistentContainer = {
      let container = NSPersistentContainer(name: "MyApplication")
      container.loadPersistentStores(completionHandler: { (storeDescription, error) in
          if let error = error as NSError? {
              fatalError("Unresolved error \(error), \(error.userInfo)")
          }
      })
      return container
  }()

  var body: some Scene {
    WindowGroup {
      Text("Hello, World!")
    }
  }
}

All you have to do when initializing a persistent container is tell it which model file to load by passing the model name to the initializer, and then call loadPersistentStores on your container instance. After doing this your Core Data stack is initialized and ready to go. From here you can insert the container's viewContext into your app's environment using the @Environment property wrapper in your View, or you can pass it around in other ways.

Remember that you don't have to initialize your persistent container in your App struct. You could also create a PersistenceManager object for example:

class PersistenceManager {
  let persistentContainer: NSPersistentContainer = {
      let container = NSPersistentContainer(name: "MyApplication")
      container.loadPersistentStores(completionHandler: { (storeDescription, error) in
          if let error = error as NSError? {
              fatalError("Unresolved error \(error), \(error.userInfo)")
          }
      })
      return container
  }()
}

struct MyApplication: App {
  let persistence = PersistenceManager()

  var body: some Scene {
    WindowGroup {
      Text("Hello, World!")
    }
  }
}

This would work perfectly fine.

The old version of the Core Data stack generated in your AppDelegate contains one extra feature which is to automatically save any changes when your application goes to the background. You can mimic this behavior by listening for the UIApplication.willResignActiveNotification on NotificationCenter.default.

Note that this auto-save feature is also missing from Apple's new Core Data template for SwiftUI applications.

Unfortunately, I haven't found a way yet to subscribe to this notification from within the App struct because it's a struct and using the closure based listener complains that I capture a mutating self parameter when I access persistentContainer.

The easiest way to work around this is to subscribe within a specialized PersistenceManager like the one I showed you earlier:

class PersistenceManager {
  let persistentContainer: NSPersistentContainer = {
      let container = NSPersistentContainer(name: "MyApplication")
      container.loadPersistentStores(completionHandler: { (storeDescription, error) in
          if let error = error as NSError? {
              fatalError("Unresolved error \(error), \(error.userInfo)")
          }
      })
      return container
  }()

  init() {
    let center = NotificationCenter.default
    let notification = UIApplication.willResignActiveNotification

    center.addObserver(forName: notification, object: nil, queue: nil) { [weak self] _ in
      guard let self = self else { return }

      if self.persistentContainer.viewContext.hasChanges {
        try? self.persistentContainer.viewContext.save()
      }
    }
  }
}

And with that, you should have all the information needed to start using Core Data in your SwiftUI 2.0 applications

In Summary

In this week's post I showed you how you can initialize Core Data from anywhere in your app, allowing you to use it with SwiftUI 2.0's new application lifecycle.

You saw that all you need to do add Core Data to your app, is creating a model file, and initializing an NSPersistentContainer object. This works from anywhere in your app, including apps that use the old AppDelegate based application lifecycle.

If you have any questions about this post, you can find me on Twitter.

Understanding the importance of abstractions

As developers, we constantly deal with layers of abstractions that make our lives easier. We have abstractions over low level networking operations that allow us to make network calls with URLSession. Core Data provides an abstraction over data persistence that can be used to store information in an sqlite database. And there are many, many more abstractions that we all use every day.

Over the past few weeks I have seen many people ask about using Core Data in pure SwiftUI projects created in Xcode 12. These projects no longer require an App- and SceneDelegate, and the checkbox to add Core Data is disabled for these projects. Some folks immediately thought that this meant Core Data can't be used with these projects since Xcode's template always initialized Core Data in the AppDelegate, and since that no longer exists it seems to make sense that Core Data is incompatible with apps that don't have an AppDelegate. How else would you initialize Core Data?

Fortunately, this isn't true. It's still possible to use Core Data in projects, even if they don't have an AppDelegate. In fact, the only thing that AppDelegate has to do with Core Data is that Apple decided that they wanted to setup Core Data in the AppDelegate.

They didn't have to make that choice. Core Data can be initialized from anywhere in your app.

However, this got me thinking about abstractions. Folks who have built a layer of abstraction between their app and Core Data probably already know that you don't need Xcode to generate a Core Data stack for you. They probably also already know that you can initialize Core Data anywhere.

While thinking about this, I started thinking more about abstractions. Adding the right abstractions to your app at the right time can help you build a more modular, portable and flexible code base that can quickly adapt to changes and new paradigms.

That why in this week's post, I would like to talk about abstractions.

Understanding what abstractions are

Abstractions provide a seperation between the interface you program against and the underlying implementation that performs work. In essence you can think of most, if not all, frameworks you use every day on iOS as abstractions that make working with something complex easier.

In programming, we often work with abstractions on top of abstractions on top of more abstractions. And yet, there is value in adding more abstractions yourself. A good abstraction does not only hide complexity and implementation details. It should also be reusable. When your abstraction is reusable it can be used in multiple projects with similar needs.

I could try to make the explanation more wordy, fancy or impressive but that wouldn't help anybody. Abstractions wrap a complex interface and provide an (often simpler) inferface while hiding the wrapped, complex interface as an implementation detail. Good abstractions can be reused.

Knowing when to write an abstraction

Earlier I wrote that adding your own abstractions has value. That said, it's not always obvious to know when you should write an abstraction. Especially since there are no hard or clear rules.

A good starting point for me is to determine whether I will write a certain block of tedious code more than once. Or rather, whether I will write similar blocks of tedious code multiple times. If the answer is yes, it makes sense to try and create a lightweight abstraction to wrap the tedious code and make it less annoying to work with.

Another method I often use to determine whether I should write an abstraction is to ask myself how easily I want to be able to swap a certain mechanism in my app out for testing or to replace it entirely.

Usually the answer to this question is that I want to be able to swap things out as easily as possible. And more often than not this means that I should add an abstraction.

For instance, when I write code that uses Core Data I always wrap it in a small abstraction layer. I don't want my entire app to depend directly on Core Data. Instead, my app uses the abstraction to interface with a persistence layer. The code in my app doesn't know how the persistence layer works. It just knows that such a layer exists, and that it can fetch and save objects of certain types.

Creating an abstraction like this allows me to easily change the underlying storage mechanism in my persistence layer. I could switch to Realm, use sqlite directly, or even move from local persitence to persisting data on a server or in iCloud. The app shouldn't know, and the app shouldn't care. That's the beauty of abstractions.

Designing an abstraction

Once you've decided that you want to write an abstraction, you need to design it. The first thing I always do is make sure that I decide which properties and methods should be publicly available. I then define a protocol that captures this public API for my abstraction. For example:

protocol TodoItemPersisting {
  func getAllTodoItems() -> Future<[TodoItem]>
  func getTodoItem(withId id: UUID) -> Future<TodoItem?>
  func updateItem(_ item: TodoItem)
  func newTodoItem() -> Future<TodoItem>
}

This is a very simple protocol that exposes nothing about the underlying persistence layer. In the rest of my code I will always refer to TodoItemPersisting when I want to use my persistence abstraction:

struct TodoListViewModel {
  private let itemStore: TodoItemPersisting
}

In this example I defined a ViewModel that has an itemStore property. This property conforms to TodoItemPersisting and the object that creates an instance of TodoListViewModel gets to decide which concrete implementation of TodoItemPersisting is injected. And since the protocol for TodoItemPersisting uses Combine Futures, we know that the persistence layer does work asynchronously. The ViewModel doesn't know whether the persistence layer goes to the network, file system, Core Data, Realm, Firebase, iCloud or anywhere else for persistence.

It just knows that items are fetched and created asynchronously.

At this point you're free to create objects that implement TodoItemPersisting as needed. Usually you'll have one or two. One for the app to use, and a second version to use while testing. But you might have more in your app. It depends on the abstraction and what it's used for.

For instance, if your app uses In-App Purchases to provide syncing data to a server you might have a local persistence abstraction, and a premium local + remote persistence abstraction that you can swap out depending on whether the user bought your premium IAP.

By desiginig abstractions as protocols you gain a lot of flexibility and power. So whenever possible I always recommend to design and define your abstractions as protocols.

Things to watch out for when writing abstractions

Once you get the hang of abstracting code, it's very tempting to go overboard. While abstractions provide a lot of power, they also add a layer of indirection. New members of your team might understand the things you've abstracted really well, but if you added to many layers your code will be really hard to understand and your abstractions will be in the way of understanding the code base.

It's also possible that you didn't design your abstractions properly. When this happens, you will find that your abstractions are holding you back rather than helping you write code that does exactly what you want it to do. When you find you're fighting your abstractions it's time to revise your design and make improvements where needed.

And the last word of warning I want to give you is that it's important to limit the levels of abstractions you add. No matter how good your abstractions are, there will come a point where it'll get harder and harder to understand and debug your app when something is wrong. There's no hard cutoff point but eventually you'll develop a sense for when you're going too far. For now it's good to know that you can abstract too much.

In Summary

In this week's post you learned about abstractions in programming. You learned what an abstraction is, what abstractions are used for and how you can determine whether you should write an abstraction of your own.

You learned that abstractions can be extremely useful when you want to write code that's testible, flexible, and maintainable. Good abstractions make difficult work easier, and allow you to hide all implementation details of the thing or process you've written your abstraction for. You also learned that protocols are a fantastic tool to help you define and design your abstraction. Lastly, I gave you some things to watch out for when writing abstractions to make sure you don't overcomplicate matters or abstract too much.

If you have any questions for me, or if you have feedback about this week's post make sure to reach out to me on Twitter.

Handling deeplinks in iOS 14 with onOpenURL

Starting with iOS 14, we can write apps that are fully built using SwiftUI, dropping the need to have AppDelegate and SceneDelegate files entirely. For as long as I remember, I've handled deeplinks in my AppDelegate and for the past year in the SceneDelegate. So when Apple introduced developers to this new @main annotated App struct style of building apps, I'm sure we all had the same question on our mind. How does the new App struct work with deeplinks and other tasks that are normally performed in the AppDelegate?

Luckily, Apple engineers made sure that handling deeplinks in our apps is still possible with the new onOpenURL(perform:) view modifier.

Handling deeplinks with onOpenURL

The new onOpenURL(perform:) view modifier is new in iOS 14. It allows developers to register a URL handler on their views so they can respond to URLs by modifying state for their views as needed.

This is vastly different from how we're used to dealing with URLs in UIKit and the SceneDelegate flow.

The old way of handling deeplinks requires you to handle each link in the SceneDelegate (or AppDelegate). You would have to manipulate the selected tab in a UITabBarViewController, or present a UIViewController by inspecting the current view controller hierarchy and pushing the needed UIViewController from right inside of the SceneDelegate.

In SwiftUI, you can use the onOpenURL(perform:) on the root of your scene as follows:

@main
struct MyApplication: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL { url in
          // handle the URL that must be opened
        }
    }
  }
}

I will cover what it means exactly to handle the url in the next section of this article, but usually it will involve mutating some state to load and display the view associated with the URL that must be opened.

What's really neat is that you can specify multiple onOpenURL handlers throughout your app. This means that you can make multiple, smaller changes to your app state which means that you no longer have one place where all of your deeplink handling and view manipulation takes place.

Furthermore, onOpenURL is called when your app is in the foreground, background or not running at all. This means that there is now a single entry point for your app to handle URLs. Even if your app is relaunched after being force-closed.

In the next section, I will show you an example of how you can select a tab in a TabView depending on the URL that your app is requested to open. After that, I will show you how to navigate to a list item in a view that's embedded in a TabView by adding a second onOpenURL view modifier on a child View that contains a List.

Activating a tab in a TabView when opening a URL

In SwiftUI, views are a function of their state. This means that virtually everything in a SwiftUI application can be represented and manipulated as a data model. This means that we can represent the currently selected tab in a SwiftUI TabView as a property on an App struct.

The following code shows how:

struct MyApplication: App {
  @State var activeTab = 0

  var body: some Scene {
    WindowGroup {
      TabView(selection: $activeTab) {
        HomeView()
          .tabItem {
            VStack {
              Image(systemName: "house")
              Text("Home")
            }
          }
          .tag(0)

        SettingsView()
          .tabItem {
            VStack {
              Image(systemName: "gearshape")
              Text("Settings")
            }
          }
          .tag(1)
      }
      .onOpenURL { url in
        // determine which tab should be selected and update activeTab
      }
    }
  }
}

What's important to notice here is the activeTab property. This property is marked as @State and represents the selected tab in the TabView. When creating the TabView, I pass a binding to activeTab to the TabView's initializer. Setting the TabView up like this means that updating activeTab will cause the TabView to update its selected tab as well.

Notice that I set a tag on the views that are added to the TabView. This tag is used to identify the TabView's items. When activeTab matches one of the tags associated with your views, the TabView will activate the matching tab.

In this case that means setting activeTab to 1 would activate the tab that displays SettingsView.

Let's see how you can implement onOpenURL to figure out and activate the correct tab. To do this, I'm going to introduce an extension on URL, and a new type called TabIdentifier:

enum TabIdentifier: Hashable {
  case home, settings
}

extension URL {
  var isDeeplink: Bool {
    return scheme == "my-url-scheme" // matches my-url-scheme://<rest-of-the-url>
  }

  var tabIdentifier: TabIdentifier? {
    guard isDeeplink else { return nil }

    switch host {
    case "home": return .home // matches my-url-scheme://home/
    case "settings": return .settings // matches my-url-scheme://settings/
    default: return nil
    }
  }
}

The code above is just a convient way to figure out which tab belongs to a URL without having to duplicate logic all over the app. If you decide to implement a similar object, the isDeeplink computed property should be updated according to the URLs you want to support. If you're implementing Universal Links, you'll want to check whether the URL's host property matches your hostname. I've set up a very minimal check here for demonstration purposes where I only care about the URL scheme.

The tabIdentifier property is a computed property that uses the host property to determine which tab should be selected. For a Universal Link you'll probably want to use the pathComponents property and compare using the second entry in that array, depending on your mapping strategy. Again, I set this up to be very basic.

You can use this basic setup in the App struct as follows:

struct MyApplication: App {
  @State var activeTab = TabIdentifier.home

  var body: some Scene {
    WindowGroup {
      TabView(selection: $activeTab) {
        HomeView()
          .tabItem {
            VStack {
              Image(systemName: "house")
              Text("Home")
            }
          }
          .tag(TabIdentifier.home) // use enum case as tag

        SettingsView()
          .tabItem {
            VStack {
              Image(systemName: "gearshape")
              Text("Settings")
            }
          }
          .tag(TabIdentifier.settings) // use enum case as tag
      }
      .onOpenURL { url in
        guard let tabIdentifier = url.tabIdentifier else {
          return
        }

        activeTab = tabIdentifier
      }
    }
  }
}

Because I made TabIdentifier Hashable, it can be used as the activeTab identifier. Each tab in the TabView is associated with a TabIdentifier through their tags, and by reading the new tabIdentifier that I added to URL in my extension, I can easily extract the appropriate tab identifier associated with the URL that I need to open.

As soon as I assign the acquired tabIdentifier to activeTab, the TabView is updated marking the appropriate tab as selected along with displaying the appropriate View.

Of course this is only half of what you'll want to typically do when opening a deeplink. Let's take a look at activating a NavigationLink in a different view next.

Handling a URL by activating the correct NavigationLink in a List

You already know how to activate a tab in a TabView when your app needs to handle a URL. Often you'll also need to navigate to a specific detail page in the view that's shown for the selected tab item. The cleanest way I have found to do this, is by adding a second onOpenURL handler that's defined within the detail view that should activate your navigation link.

When you define multiple onOpenURL handlers, the system will call them all, allowing you to make small, local changes to your view's data model. Like selecting a tab in the App struct, and activating a NavigationLink in a child view. Before I show you how to do this, We'll need another extension on URL to extract the information we need to activate the appropriate NavigationLink in a List:

enum PageIdentifier: Hashable {
  case todoItem(id: UUID)
}

extension URL {
  var detailPage: PageIdentifier? {
    guard let tabIdentifier = tabIdentifier,
          pathComponents.count > 1,
          let uuid = UUID(uuidString: pathComponents[1]) else {
      return nil
    }

    switch tabIdentifier {
    case .home: return .todoItem(id: uuid) // matches my-url-scheme://home/<item-uuid-here>/
    default: return nil
    }
  }
}

I've added a new enum called PageIdentifier. This enum has a single case with an associated value. This associated value represents the identifier of the object that I want to deeplink to. My app is a to-do app, and each to-do item uses a UUID as its unique identifier. This is also the identifier that's used in the deeplink. The approach above is similar to what I've shown in the previous section and if you decide you like my URL parsing approach, you'll have to make some modifications to adapt it in your app.

The next step is to implement the HomeView, and select the appropriate item from its list of items:

struct HomeView: View {
  @StateObject var todoItems = TodoItem.defaultList // this is just a placeholder.  
  @State var activeUUID: UUID?

  var body: some View {
    NavigationView {
      List(todoItems) { todoItem in
        NavigationLink(destination: TodoDetailView(item: todoItem), tag: todoItem.id, selection: $activeUUID) {
          TaskListItem(task: todoItem)
        }
      }
      .navigationTitle(Text("Home"))
      .onOpenURL { url in
        if case .todoItem(let id) = url.detailPage {
          activeUUID = id
        }
      }
    }
  }
}

Notice that my HomeView has a property called activeUUID. This property serves the exact same purpose that activeTab fulfilled in the previous section. It represents the identifier for the item that should be selected.

When creating my NavigationLink, I pass a tag and a binding to activeUUID to each NavigationLink object. When SwiftUI notices that the tag for one of my navigation links matches the activeUUID property, that item is selected and pushed onto the NavigationView's navigation stack. If you already have a different item selected, SwitUI will first go back to the root of your NavigationView (deactivating that link) and then navigate to the selected page.

In onOpenURL I check whether url.detailPage points to a todoItem, and if it does I extract its UUID and set it as the active UUID to navigate to that item.

By definiing two onOpenURL handlers like I just did, I can make small changes to local state and SwiftUI takes care of the rest. Both of these onOpenURL handlers are called when the app is expected to handle a link. This means that it's important for each View to check whether it can (and should) handle a certain link, and to make small changes to the view it belongs to rather than making big app-wide state changes like you would in a UIKit app.

In Summary

This week, you saw how you can use iOS 14's new onOpenURL view modifier to handle open URL requests for your app. You learned that you can define more than one handler, and that onOpenURL is called for all scenarios where your app needs to open a URL. Even if your app is launched after being force closed.

First, I showed you how you can parse a URL to determine which tab in a TabView should be selected. Then I showed you how you can change the active tab in a TabView by tagging your views and passing a selection biding to the TabView's initializer. After that, you saw how you can navigate to a detail view by doing more URL parsing, and passing a tag and binding to your NavigationLink.

I was rather surprised when I learned that deeplink handling on iOS 14 with SwiftUI is this powerful and flexible. Since we can specify onOpenURL handlers wherever they are needed, it's much easier to decouple and compose your app using small parts. You no longer need a SceneDelegate that knows exactly how your entire app is structured because it needs to manipulate your app's entire navigation state from a single place. It feels much cleaner to handle URLs on a local level where the side-effects are limited to the view that's handling the URL.

If you have any comments, questions, or feedback about this post please reach out on Twitter. I love hearing from you.

Implementing an infinite scrolling list with SwiftUI and Combine

Tons of apps that we build feature lists. Sometimes we build lists of settings, lists of todo items, lists of our favorite pictures, lists of tweets, and many other things. Some of these lists could scroll almost endlessly. Think of a Twitter timeline, a Facebook feed or a list of posts on Reddit.

You might argue that knowing how to build a list that scrolls infinitely and fetches new content whenever a user reaches the end of the list is an essential skill of any iOS developer. That's why as one of my first posts that covers SwiftUI I wanted to explore building a list that can scroll forever.

And to be honest, I was surprised with how simple SwiftUI makes implementing this feature on iOS 14, even though we can't read the current scroll offset of a list like we can in UIKit. Instead of reading a scroll offset we can use a list item's onAppear modifier to trigger a new page load.

Let's find out how.

Implementing the SwiftUI portion of an infinite scrolling list

Before I explain, let's look at some code:

struct EndlessList: View {
  @StateObject var dataSource = ContentDataSource()

  var body: some View {
    List(dataSource.items) { item in
      Text(item.label)
        .onAppear {
          dataSource.loadMoreContentIfNeeded(currentItem: item)
        }
        .padding(.all, 30)
    }
  }
}

This code uses @StateObject which is new in iOS 14. Read more about @StateObject and how it compares to @ObservedObject here.

I'll show you the data source in a moment, but let's talk about the couple of lines of code in this snippet first. Surely this can't be all we need to support infinite scrolling, right? Well... it turns out it is all we need.

In SwiftUI, onAppear is called when a view is rendered by the system. This doesn't mean that the view will be rendered within the user's view, or that it ever makes it on screen so we're relying on List's performance optimizations here and trust that it doesn't render all of its views at once.

A List will only keep a certain number of views around while rendering so we can use onAppear to hook into List's rendering. Since we have access to the item that's being rendered, we can ask the data source to load more data if needed depending on the item that's being rendered. If this is one of the last items in the data source, we can kick off a page load and add more items to the data source.

Implementing the data source

Let's look at the data source for this example:

class ContentDataSource: ObservableObject {
  @Published var items = [ListItem]()
  @Published var isLoadingPage = false
  private var currentPage = 1
  private var canLoadMorePages = true

  init() {
    loadMoreContent()
  }

  func loadMoreContentIfNeeded(currentItem item: ListItem?) {
    guard let item = item else {
      loadMoreContent()
      return
    }

    let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
    if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
      loadMoreContent()
    }
  }

  private func loadMoreContent() {
    guard !isLoadingPage && canLoadMorePages else {
      return
    }

    isLoadingPage = true

    let url = URL(string: "https://s3.eu-west-2.amazonaws.com/com.donnywals.misc/feed-\(currentPage).json")!
    URLSession.shared.dataTaskPublisher(for: url)
      .map(\.data)
      .decode(type: ListResponse.self, decoder: JSONDecoder())
      .receive(on: DispatchQueue.main)
      .handleEvents(receiveOutput: { response in
        self.canLoadMorePages = response.hasMorePages
        self.isLoadingPage = false
        self.currentPage += 1
      })
      .map({ response in
        return self.items + response.items
      })
      .catch({ _ in Just(self.items) })
      .assign(to: $items)
  }
}

This code uses Combine's new assign(to:) function. Read more about it here.

There's a lot to unpack in that snippet but I think the most interesting bit is loadMoreContent. The rest of the code kind of speaks for itself.

In loadMoreContent I check whether I'm already loading a page, and whether there are more pages to load. I set isLoadingPage to true, and I construct a URL for a page which points to a feed file that I've uploaded to Amazon S3. This would normally be a URL that points to the page that you want to load in your list. I create a dataTaskPublisher so I can load the URL and I use Combine's handleEvents operator to apply side-effects to my data source when a response was loaded.

Next, I update the canLoadMorePages boolean, set isLoadingPage back to false because the load is complete and increment the currentPage. I prefixed handleEvents with receive(on: DispatchQueue.main) because I modify an @Published property in the handleEvents operator which might change my view and that must be done on the main thread. I don't do this in my map that's applied after handleEvents because map is supposed to be pure and not apply side-effects.

In the map I return a value that merges the current list of items with the newly loaded items. Lastly, I catch any erros that might have occured during the page load and replace them with a publisher that re-emits the current list of items. To update the items property I use Combine's new assign(to:) operator. This operator can pipe the output from a publisher directly into an @Published property without needing to manually subscribe to it.

While it's a lot of code, I think this pipeline is relatively straightforward once you understand all of the operators that are used.

Since I made the ContentDataSource's isLoadingPage property @Published, we can use it to add a loading indicator to the bottom of the list to show the user we're loading a new page in case the page isn't loaded by the time the user reaches the end of the list:

struct EndlessList: View {
  @StateObject var dataSource = ContentDataSource()

  var body: some View {
    List {
      ForEach(dataSource.items) { item in
        Text(item.label)
          .onAppear {
            dataSource.loadMoreContentIfNeeded(currentItem: item)
          }
          .padding(.all, 30)
      }

      if dataSource.isLoadingPage {
        ProgressView()
      }
    }
  }
}

This if statement will conditionally show and hide a ProgressView depending on whether we're fetching a new page.

We can modify this example to build in infinite scrolling list using a ScrollView and ForEach through a LazyVStack on iOS 14.

Building an endless scrolling LazyVStack

On iOS 13 it's possible to build scrolling lists using ForEach and VStack. Unfortunately, these components don't work well with the technique for building an infinite list that I just demonstrated. A VStack combined with ForEach builds its entire view hierarchy at once rather than lazily like a List does. This would mean that we'd immediately begin loading items from the server and continue to load more until all pages are loaded without any action from the user. This happens because onAppear is called when a view is added to the view hierarchy rather than when the view actually becomes visible.

Luckily, iOS 14 introduces a LazyVStack that builds its view hierarchy lazily, which means that new items are added to its layout as the user scrolls. This means that the onAppear method for items created in ForEach is called at a similar time as it is for items inside a List, and that we can use it to build our infinite scrolling list without using a List:

struct EndlessList: View {
  @StateObject var dataSource = ContentDataSource()

  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(dataSource.items) { item in
          Text(item.label)
            .onAppear {
              dataSource.loadMoreContentIfNeeded(currentItem: item)
            }
            .padding(.all, 30)
        }

        if dataSource.isLoadingPage {
          ProgressView()
        }
      }
    }
  }
}

Pretty nifty, right?

In Summary

In this week's post I finally went all-in on SwiftUI. With iOS 14 I think it has reached a level of maturity that makes it attractive to learn, and since all SwiftUI code from iOS 13 also works on iOS 14 I think it's unlikely that Apple will make huge breaking changes to SwiftUI in the near future.

You saw how you can use SwiftUI to build an infinite scrolling list using the onAppear modifier, and how you can back this up with a data source object that's implemented in Combine. I also showed you the new LazyVStack that was added to SwiftUI in iOS 14 which allows you to apply the technique we used to build an inifite scrolling list to a VStack.

If you have questions about this post, or if you have feedback to me I would love to hear from you on Twitter.

Using multi-colored icons in iOS 14 with SF Symbols 2

Apple introduced SF Symbols in iOS 13. SF Symbols allow developers to easily integrate icons in their apps. The SF Symbols icons integrate really well with the default system font, and provide a consistent look throughout the system.

In iOS 14, Apple added over 750 new icons to the SF Symbols library for developers to use in their apps. Additionally, Apple has expanded SF Symbols to include multi-colored icons. For a full overview of the available SF Symbols that are available, including the newly added and multicolor symbols, download the SF Symbols 2 app from Apple's SF Symbols page.

grid of new symbols

To use a multicolored symbol in your app, all you need to do is set the correct rendering mode for your image.

To use a multi-colored icon in SwiftUI you can use the following code:

Image(systemName: "thermometer.sun.fill")
  .font(.largeTitle)
  .renderingMode(.original)

In a UIKit based app, you can set the icon's tint color as follows:

let image = UIImage(systemName: "star.fill")?
  .withRenderingMode(.alwaysOriginal)

Note that at the time of writing I have not managed to get multi-colored SF Symbols to actually work with UIKit and that only a handful of SF Symbols properly show up with multiple colors when used in SwiftUI depending on the device you're using. The iPhone 11 simulator appears to render all icons correctly but the iOS 14 beta on an iPhone 7 doesn't. There's also a bug currently where setting an icon's font-size can cause it to not be colored correctly.

The ability to use multi-colored symbols in your app is a very welcome addition to the SF Symbols feature and I think it can add a really vibrant touch to your apps.

How to change a UICollectionViewListCell’s separator inset

In WWDC2020's session Lists in UICollectionView a slide is shown where a UICollectionViewListCell's separator inset is updated by assigning a new leading anchor to separatorLayoutGuide.leadingAnchor.

Unfortunately, this doesn't work in when you try to do it.

To set the separator inset for a UICollectionViewListCell you can update the leading anchor constraint by overriding updateConstraints in a UICollectionViewListCell subclass. Setting the anchor in init will cause the system to override your custom anchor leaving you with the default inset.

override func updateConstraints() {
  super.updateConstraints()

  separatorLayoutGuide.leadingAnchor.constraint(equalTo: someOtherView.leadingAnchor, constant: 10).isActive = true
}

You can set the leadingAnchor constraint just like you would set any other constraint. In fact, you can even set the separator's trailingAnchor using the same method I just showed if you want to offset the seperator from the trailing edge of your content view.

What’s new with UICollectionView in iOS 14

Last year, the team that works on UICollectionView shipped massive improvements like compositional layout and diffable data sources.

This year, the team went all out and shipped even more amazing improvements to UICollectionView, making UITableView obsolete through the new UICollectionViewCompositionalLayout.list and UICollectionLayoutListConfiguration. This new list layout allows you to create collection views that look and function identical to UITableView. When paired with UICollectionViewListCell your collection view can support features that used to only be available to UITableView.

For instance, you can now add swipe actions to a cell and set its accessories to add certain affordances on a cell like a disclosure indicator.

In addition adding features that make collection views look more like table views when needed, the team also made huge improvements to data sources.

Diffable data sources now have first class support for features like reordering and deleting cells. All you have to do is assign a couple of handlers to your data source and the system handles the rest.

As if that's not enough, you can now build collapsable lists in collection views with hardly any effort at all by setting up your diffable data sources with hierarchical data. This is going to save plenty of folks some serious headaches.

If you've worked with diffable data sources before you probably know that they are super convenient. However, every time you want to change a snapshot for your diffable data source in iOS 13 you must recreate or update the entire snapshot. In iOS 14 you can use section snapshots which allow you to only update a specific section in your data source rather than rebuiding the entire snapshot every time.

Apple also made a whole bunch of changes to how we configure collection view cells. Cells can now adopt a new feature that lets developers apply states and configurations to cells to set up their appearance and state. This means that you no longer directly assign values to labels, images or other components of a cell but instead the cell takes a configuration object and updates its UI accordingly. The best part of this feature in my opinion is that these configurations are not tied to collection view cells per se. Any view that can work with these configurations can accept and apply a configuration making this a highly portably and flexible feature.

Last but not least I would like to mention that in iOS 14 there's a new way for developers to register and dequeue their collection view cells. In iOS 13 and earlier you would use a string identifier to register and dequeue cells. In iOS 14 you can do this through the new UICollectionView.CellRegistration and it's truly awesome.

I'm super happy with all of these new collection view features and I can't wait to take them for a spin.

Learn how to use new UICollectionView features

How to add a custom accessory to a UICollectionViewListCell?

Apple provides several accessory types that you can use to apply certain affordances to a UICollectionViewListCell. However, sometimes these options don't suit your needs and you're looking for something more customizable.

To add a custom accessory to a list cell instead of a standard one, you use the .custom accessory type. The initializer for this accessory takes a UICellAccessory.CustomViewConfiguration that describes how your accessory should look and where it's positioned. Let's dive right in with an example:

// create the accessory configuration
let customAccessory = UICellAccessory.CustomViewConfiguration(
  customView: UIImageView(image: UIImage(systemName: "paperplane")),
  placement: .leading(displayed: .always))

// add the accessory to the cell
cell.accessories = [.customView(configuration: customAccessory)]

To create a custom accessory all you really need to provide is a view that you want to display, and you need to specify where and when the accessory should be displayed. In this case, I created a simple UIImageView that shows a paperplane SF Symbol that's always visible on the leading edge of the cell. It's also possible to pass .trailing to make the accessory appear on the trailing edge of the cell. For the displayed argument of placement, you can pass .whenEditing or .whenNotEditing instead of .always to control whether your accessory should or shouldn't be visible when the collection view is in edit mode.

By default your custom accessory will be shown as close to the content as possible if there are multiple accessories shown on the same side of the cell. You can customize this by passing a closure to the placement object:

let customAccessory = UICellAccessory.CustomViewConfiguration(
  customView: UIImageView(image: UIImage(systemName: "paperplane")),
  placement: .trailing(displayed: .always, at: { accessories in
    return 1
  }))

In the closure you receive the other accessories that are shown on the same side as your accessory and you can return the preferred position for your accessory. A lower value makes your accessory appear closer to the content.

While you can already build a pretty nice accessory with this, there are more arguments that you can pass to UICellAccessory.CustomViewConfiguration's initializer:

let customAccessory = UICellAccessory.CustomViewConfiguration(
  customView: UIImageView(image: UIImage(systemName: "paperplane")),
  placement: .leading(displayed: .always, at: { accessories in
    return 1
  }),
  reservedLayoutWidth: .standard,
  tintColor: .darkGray,
  maintainsFixedSize: false)

This is an example of a fully customized accessory. In addition to the required two arguments you can also pass a reservedLayoutWidth to tell iOS how much space should be used for your accessory. This will help iOS build your layout and space the cell's content appropriately. The tintColor argument is used to set a color for your accessory. The default color is a blue color, in this example I changed this color to .darkGray. Lastly you can determine if the accessory maintains a fixed size, or if it can scale if needed.

How to add accessories to a UICollectionViewListCell?

In iOS 14 Apple added the ability for developers to create collection views that look and feel like table views, except they are far, far more powerful. To do this, Apple introduced a new UICollectionViewCell subclass called UICollectionViewListCell. This new cell class allows us to implement several tableviewcell-like principles, including accessories.

Adding accessories to a cell is done by assigning an array of UICellAccessory items to a UICollectionViewListCell's accessories property. For example, to make a UICollectionViewListCell show a disclosure indicator that makes it clear to a user that they will see more content if they tapp a cell, you would use the following code:

listCell.accessories = [.disclosureIndicator()]

You set a cell's accessories in either the cellForItemAt UICollectionViewDataSource method or in your CellRegistration closure.

Apple has added a whole bunch of accessories that you can add to your cells. For example checkmark to to show the checkmark symbol that you might know from UITableView, .delete to indicate that a user can delete a cell, .reorder to show a reorder control, and more.

You have full control over when certain accessories are displayed and their color. For example, a reorder control is normally only visible when a collection view is in edit mode. To make it always visible, and make it orange instead of gray you'd use the following code:

cell.accessories = [.reorder(displayed: .always, options: .init(tintColor: .orange, showsVerticalSeparator: false))]

Every accessory has its own options object with different parameters. Refer to the documentation to see the configuration options for accessories you're interested in.