How to add custom swipe actions 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 swipe actions.

You can add both leading and trailing swipe actions to a cell by assigning a UISwipeActionsConfigurationProvider instance to the collection view's UICollectionLayoutListConfiguration object's leadingSwipeActionsConfigurationProvider and trailingSwipeActionsConfigurationProvider properties. This swipe actions provider is expected to return an instance of UISwipeActionsConfiguration. A UISwipeActionsConfiguration is created using an array of one or more UIContextualAction instances.

You can customize the icon, title, and background color of a contextual icon as needed. Setting an image and background is optional but you are required to pass a title to the UIContextualAction initializer.

Let's look at an example of how you can add a simple trailing swipe action to a list configuration instance:

let listConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)

listConfig.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in 
  guard let self = self else { return nil }

  let model = self.dataSource[indexPath.row] // get the model for the given index path from your data source

  let actionHandler: UIContextualAction.Handler = { action, view, completion in
    model.isDone = true
    completion(true)
    self.collectionView.reloadItems(at: [indexPath])
  }

  let action = UIContextualAction(style: .normal, title: "Done!", handler: actionHandler)
  action.image = UIImage(systemName: "checkmark")
  action.backgroundColor = .systemGreen

  return UISwipeActionsConfiguration(actions: [action])
}

let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig)

collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)

This is a very simple configuration that adds a single trailing action to every cell in the list. The action is configured to have a green background and has a checkmark icon. Note that when you set an image on the action, the tite is hidden from view on iOS. It's still used for accessibillity so make sure to set a good title, even if you plan on using an image in the UI.

Important
Notice that I assign listConfig.trailingSwipeActionsConfigurationProvider before creating my listLayout property. Since UICollectionLayoutListConfiguration is a struct you need to make sure it's fully configured before using it to create a list layout. If you create the list layout first, it will use its own copy of your list configuration and updates you make to your initial instance won't carry over to the UICollectionViewCompositionalLayout because it uses its own copy of the configuration object.

You can return different configurations depending on the index path that you're supplying the swipe actions for. If you don't want to have any swipe actions for a certain index path, you can return nil from the closure that's used to configure your swipe actions.

Note that I created a .normal contextual action. The other option here is .desctructive to indicate that an action might destruct data.

The action handler that's passed to UIContextualAction receives a reference to the action that triggered it, the view that this occured in and a completion handler that you must call.

You also receive a completion handler in the action handler. You must call this handler when you're done handling the action and pass true if you handled the action successfully. If you failed to successfully handle the action, you must pass false. This allows the system to perform any tasks (if needed) related to whether you were able to handle the action or not.

Configure collection view cells with UICollectionView.CellRegistration

In iOS 14 you can use the new UICollectionView.CellRegistration class to register and configure your UICollectionViewCell instances. So no more let cellIdentifier = "MyCell", no more collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) and best of all, you no longer need to cast the cell returned by dequeueReusableCell(withReuseIdentifier:for:) to your custom cell class.

Adopting UICollectionView.CellRegistration in your project is surprisingly straightforward. For demo purposes I created the following UICollectionViewCell subclass:

class MyCollectionViewCell: UICollectionViewCell { 
  let label = UILabel()

  required init?(coder: NSCoder) {
    fatalError("nope!")
  }

  override init(frame: CGRect) {
    super.init(frame: frame)

    contentView.addSubview(label)
    label.translatesAutoresizingMaskIntoConstraints = false
    label.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
    label.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
    label.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor, constant:  -8).isActive = true
    label.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor, constant: -8).isActive = true
  }
}

To register this cell on a collection view using UICollectionView.CellRegistration all you need is an instance of UICollectionView.CellRegistration that's specialized for your cell class and data model. My case I'm going to use a String as my data model since the cell only has a single label. You can use any object you want for your cells. Usually it will be the model that you retrieve in the cellForItemAt delegate method.

// defined as an instance property on my view controller
let simpleConfig = UICollectionView.CellRegistration<MyCollectionViewCell, String> { (cell, indexPath, model) in
  cell.label.text = model

}

Notice that UICollectionView.CellRegistration is generic over two types. The UICollectionViewCell that I want to use, and the model type which is a String in my case. The initializer for UICollectionView.CellRegistration takes a closure that's used to set up the cell. This closure receives a cell, an index path and the model that's used to configure the cell.

In my implementation I simply assign my String model to cell.label.text.

You don't have to register your UICollectionView.CellRegistration on your UICollectionView. Instead, you can use it when you ask your collection view to dequeue a reusable cell in cellForItemAt as follows:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

  let model = "Cell \(indexPath.row)"

  return collectionView.dequeueConfiguredReusableCell(using: simpleConfig,
                                                      for: indexPath,
                                                      item: model)
}

When you call dequeueConfiguredReusableCell(using:for:item:) on a UICollectionView, it dequeues a cell that has the correct class and the closure that you passed to your UICollectionView.CellRegistration initializer is called so you can configure your cell.

All you need to do is make sure that you grab the correct model from your data source, pass it to dequeueConfiguredReusableCell(using:for:item:) and return the freshly obtained cell. That's all there is to it! Pretty nifty right? There's no other special setup involved. No secret tricks. Nothing. Just a much nicer way to obtain and configure collection view cells.

What’s the difference between @StateObject and @ObservedObject?

Views in SwiftUI are thrown away and recreated regularly. When this happens, the entire view struct is initialized all over again. Because of this, any values that you create in a SwiftUI view are reset to their default values unless you've marked these values using @State.

This means that if you declare a view that creates its own @ObservedObject instance, that instance is replaced every time SwiftUI decides that it needs to discard and redraw that view.

If you want to see what I mean, try running the following SwiftUI view:

class DataSource: ObservableObject {
  @Published var counter = 0
}

struct Counter: View {
  @ObservedObject var dataSource = DataSource()

  var body: some View {
    VStack {
      Button("Increment counter") {
        dataSource.counter += 1
      }

      Text("Count is \(dataSource.counter)")
    }
  }
}

struct ItemList: View {
  @State private var items = ["hello", "world"]

  var body: some View {
    VStack {
      Button("Append item to list") {
        items.append("test")
      }

      List(items, id: \.self) { name in
        Text(name)
      }

      Counter()
    }
  }
}

While the views in this example might not be super useful, it does a good job of demonstrating how Counter creates its own @ObservedObject. If you'd tap the Increment counter button defined in Counter a couple of times, you'd see that its Count is ... label updates everytime. If you then tap the Append item to list button that's defined in ItemList, the Count is ... label in Counter resets back to 0. The reason for this is that Counter got recreated which means that we now have a fresh instance of DataSource.

To fix this, we could either create the DataSource in ItemList, keep that instance around as a property on ItemList and pass that instance to Counter, or we can use @StateObject instead of @ObservedObject:

struct Counter: View {
  @StateObject var dataSource = DataSource()

  var body: some View {
    VStack {
      Button("Increment counter") {
        dataSource.counter += 1
      }

      Text("Count is \(dataSource.counter)")
    }
  }
}

By making DataSource a @StateObject, the instance we create is kept around and used whenever the Counter view is recreated. This is extremely useful because ItemList doesn't have to retain the DataSource on behalf of the Counter, which makes the DataSource that much cleaner.

You should use @StateObject for any ObservableObject properties that you create yourself in the object that holds on to that object. So in this case, Counter creates its own DataSource which means that if we want to keep it around, we must mark it as an @StateObject.

If a view receives an ObservableObject in its initializer you can use @ObservedObject because the view does not create that instance on its own:

struct Counter: View {
  // the DataSource must now be passed to Counter's initializer
  @ObservedObject var dataSource: DataSource

  var body: some View {
    VStack {
      Button("Increment counter") {
        dataSource.counter += 1
      }

      Text("Count is \(dataSource.counter)")
    }
  }
}

Keep in mind though that this does not solve the problem in all cases. If the object that creates Counter (or your view that has an @ObservedObject) does not retain the ObservableObject, a new instance is created every time that view redraws its body:

struct ItemList: View {
  @State private var items = ["hello", "world"]

  var body: some View {
    VStack {
      Button("Append item to list") {
        items.append("test")
      }

      List(items, id: \.self) { name in
        Text(name)
      }

      // a new data source is created for every redraw
      Counter(dataSource: DataSource())
    }
  }
}

However, this does not mean that you should mark all of your @ObservedObject properties as @StateObject. In this last case, it might be the intent of the ItemList to create a fresh DataSource every time the view is redrawn. If you'd have marked Counter.dataSource as @StateObject the new instance would be ignored and your app might now have a new hidden bug.

A not completely unimportant implication of @StateObject is performance. If you're using an @ObservedObject that's recreated often that might harm your view's rendering performance. Since @StateObject is not recreated for every view re-render, it has a far smaller impact on your view's drawing cycle. Of course, the impact might be minimal for a small object, but could grow rapidly if your @ObservedObject is more complex.

So in short, you should use @StateObject for any observable properties that you initialize in the view that uses it. If the ObservableObject instance is created externally and passed to the view that uses it mark your property with @ObservedObject.

For a quick reference to SwiftUI's property wrappers, take a look at swiftuipropertywrappers.com.

Using custom publishers to drive SwiftUI views

In SwiftUI, views can be driven by an @Published property that's part of an ObservableObject. If you've used SwiftUI and @Published before, following code should look somewhat familiar to you:

class DataSource: ObservableObject {
  @Published var names = [String]()
}

struct NamesList: View {
  @ObservedObject var dataSource: DataSource

  var body: some View {
    List(dataSource.names, id: \.self) { name in
      Text(name)
    }
  }
}

Whenever the DataSource object's names array changes, NamesList will be automatically redrawn. That's great.

Now imagine that our list of names is retrieved through the network somehow and we want to load the list of names in the onAppear for NamesList.

class DataSource: ObservableObject {
  @Published var names = [String]()

  let networkingObject = NetworkingObject()
  var cancellables = Set<AnyCancellable>()

  func loadNames() {
    networkingObject.loadNames()
      .receive(on: DispatchQueue.main)
      .sink(receiveValue: { [weak self] names in
        self?.names = names
      })
      .store(in: &cancellables)
  }
}

struct NamesList: View {
  @ObservedObject var dataSource: DataSource

  var body: some View {
    List(dataSource.names, id: \.self) { name in
      Text(name)
    }.onAppear(perform: {
      dataSource.loadNames()
    })
  }
}

This would work and it's the way to go on iOS 13 but I've never liked having to subscribe to a publisher just so I could update an @Published property. Luckily, in iOS 14 we can refactor loadNames() and do much better with the new assign(to:) operator:

class DataSource: ObservableObject {
  @Published var names = [String]()

  let networkingObject = NetworkingObject()

  func loadNames() {
    networkingObject.loadNames()
      .receive(on: DispatchQueue.main)
      .assign(to: &$names)
  }
}

The assign(to:) operator allows you to assign the output from a publisher directly to an @Published property under one condition. The publisher that you apply the assign(to:) on must have Never as its error type. Note that I had to add an & prefix to $names. The reason for this is that assign(to:) receives its target @Published property as an inout parameter, and inout parameters in Swift are always passed with an & prefix. To learn more about replacing errors so your publisher can have Never as its error type, refer to this blog post I wrote about catch and replaceError in Combine.

Pretty cool, right?

Ignore first number of elements from a publisher in Combine

If you have a Combine publisher and you want to ignore the first n elements that are published by that publisher, you can use the dropFirst(_:) operator. This operator will swallow any values emitted until the threshold you specify is reached. For example, dropFirst(1) will ignore the first emitted value from a publisher:

[1, 2, 3].publisher
  .dropFirst(1)
  .sink(receiveValue: { value in 
    print(value) // 2 and 3 are printed
  })

For more information about dropFirst and several variations of drop like drop(while:) and drop(untilOutputFrom:) you can refer to Apple's documentation.

Recursively execute a paginated network call with Combine

Last week, my attention was caught by a question that Dennis Parussini asked on Twitter. Dennis wanted to recursively make calls to a paginated API to load all pages of data before rendering UI. Since I love Combine and interesting problems I immediately started thinking about ways to achieve this using a nice, clean API. And then I realized that this is a non-trivial task that was worth exploring.

In this week's post, I would like to share my thought process and solution with you, hoping you'll learn something new about Combine in the process.

Understanding the problem and setting a goal

Whenever I get to work on a problem like this, I always start by writing the code I would like to write when using an API or abstraction that I've written. In this case, I would like to be able to write something like the following code to fetch all pages from the paginated endpoint:

networking.loadPages()
  .sink(receiveCompletion: { _ in
    // handle errors
  }, receiveValue: { items in
    print(items)
  })
  .store(in: &cancellables)

In this case, it didn't matter to me what networking is, or what kind of object owns it. In other words, I don't care about the architecture this would be used in. All I really care about is that I have an object that implements loadPages(). And that loadPages() will return a publisher that emits data for all pages at once. I don't want to receive all intermediate pages in my sink. The publisher completes immediately after delivering my complete data set.

The tricky bit here is that this means that in loadPages() we'll need to somehow create a publisher that collects the responses from several network calls, bundles them into one big result, and outputs them to the created publisher.

Since I didn't have access to an API that would give me paginated responses I decided that a very naive abstraction would be sufficient. The abstraction uses a Response object that looks as follows:

struct Response {
  var hasMorePages = true
  var items = [Item(), Item()]
}

struct Item {}

My loader should keep making more requests until it receives a Response that has its hasMorePages set to false. At that point, the chain is considered complete and the publisher created in loadPages() should emit all fetched values and complete.

The starting point for my experimentation would look like this:

class RecursiveLoader {
  var requestsMade = 0
  var cancellables = Set<AnyCancellable>()

  init() { }

  private func loadPage() -> AnyPublisher<Response, Never> {
    // this would be the individual network call
    Future { promise in
      DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
        self.requestsMade += 1
        if self.requestsMade < 5 {
          return promise(.success(Response()))
        } else {
          return promise(.success(Response(hasMorePages: false)))
        }
      }
    }.eraseToAnyPublisher()
  }
}

This setup is fairly simple. I have a loadPage() function that will load an individual page depending on the number of requests I have already made. In the real implementation, this would be replaced by a network call but for my purposes, this would do. What matters is that I have a publisher that emits a Response object that I can use to determine whether I need to load another page or not.

So now that I knew what I wanted to write and had set up some scaffolding it was time to write the solution.

Finding an appropriate solution

Attempt one: a simplified version

My initial thought was to use Combine's reduce operator on some kind of publisher that would emit responses or arrays of items as they came in. When you apply reduce to a publisher in Combine you can accumulate all emitted values into one new value that's emitted when the upstream publisher completes. This sounds perfect for my purposes so I started my experimentation with that as a base thought. However, instead of making loadPage() return a publisher I wanted to simplify everything a little bit. To load all pages I would create an instance of RecursiveLoader, subscribe to a publisher that I'd define as a property on RecursiveLoader and tell it to begin loading:

let loader = RecursiveLoader()

loader.finishedPublisher
  .sink(receiveValue: { items in
    print("items \(items.count)")
  })
  .store(in: &cancellables)

loader.initiateLoadSequence()

While it's not at all what I wanted to write, this was a basic idea that I considered to be approachable enough to start with.

Whenever I'm solving complicated problems, or experiment with new ideas I tend to try and get something working first before I go back to my initial design to see how I can adapt my initial prototype to be like my design. By doing this I make sure that I keep typing and trying things rather than fighting the system to immediately get the implementation I wanted.

With the simplified end goal in place, I started writing some code. First, I needed to define the finishedPublisher and a skeleton for initiateLoadSequence():

class RecursiveLoader {
  var requestsMade = 0

  private let loadedPagePublisher = PassthroughSubject<Response, Never>()
  let finishedPublisher: AnyPublisher<[Item], Never>

  var cancellables = Set<AnyCancellable>()

  init() {
    self.finishedPublisher = loadedPagePublisher
      .reduce([Item](), { allItems, response in
        return response.items + allItems
      })
      .eraseToAnyPublisher()
  }

  func initiateLoadSequence() {
    // do something
  }
}

I defined two publishers on RecursiveLoader instead of one. The private loadedPagePublisher is where I decided I would publish pages as they came in from the network. The finishedPublisher takes the loadedPagePublisher and applies the reduce operator. That way, once I complete the loadedPagePublisher, the finsihedPublisher will emit an array of [Item]. Pretty cool, right?

At this point I came up with the following implementation for initiateLoadSequence():

func initiateLoadSequence() {
  loadPage()
    .sink(receiveValue: { response in
      self.loadedPagePublisher.send(response)

      if response.hasMorePages == false {
        self.loadedPagePublisher.send(completion: .finished)
      } else {
        self.initiateLoadSequence()
      }
    })
    .store(in: &cancellables)
}

In initiateLoadSequence() I call loadPage() and subscribe to the publisher returned by loadPage(). When I receive a response I forward that response to loadedPagePublisher and if we don't have any more pages to load, I complete the loadedPagePublisher so the finishedPublisher emits its array of Item objects. If we do have more pages to load, I call self.initiateLoadSequence() again to load the next page.

This example works, but I don't think it's great. An instance of RecursiveLoader can only load all pages once, and users of this object will need to subscriber to finishedPublisher before calling initiateLoadSequence to prevent dropping events since the loadedPagePublisher will not emit any values if it doesn't have any subscribers. For loadedPagePublisher to have subscribers, users of RecursiveLoader must subscribe to finishedPublisher since that publisher is built upon loadedPagePublisher.

That said, this first attempt did show me that using reduce is a good idea, and I also like the idea of having a publisher that I publish fetched results onto so another publisher can reduce over it to collect all responses returned by the paginated API.

Attempt two: the solution I wanted to write

Since I had an okay first attempt that just had a couple of issues I figured I wanted to push that idea forward and make it work as I had initially intended. To do this, I wanted to get rid of finishedPublisher and loadedPagePublisher because those made my RecursiveLoader into a non-reusable object that can only load all pages once. Instead, I figured that I could write a function loadPages() that would create a publisher in its own scope and then pass that publisher to a function that would load an individual page, and then send its result to loadPagePublisher.

Let me show you what I mean by showing you the end result of my second attempt at implementing this functionality:

class RecursiveLoader {
  var requestsMade = 0
  var cancellables = Set<AnyCancellable>()

  init() { }

  private func loadPage() -> AnyPublisher<Response, Never> {
    // unchanged from the original
  }

  private func performPageLoad(using publisher: PassthroughSubject<Response, Never>) {
    loadPage().sink(receiveValue: { [weak self] response in
      publisher.send(response)

      if response.hasMorePages {
        self?.performPageLoad(using: publisher)
      } else {
        requestsMade = 0
        publisher.send(completion: .finished)
      }
    }).store(in: &cancellables)
  }

  func loadPages() -> AnyPublisher<[Item], Never> {
    let intermediatePublisher = PassthroughSubject<Response, Never>()

    return intermediatePublisher
      .reduce([Item](), { allItems, response in
        return response.items + allItems
      })
      .handleEvents(receiveSubscription: { [weak self] _ in
        self?.performPageLoad(using: intermediatePublisher)
      })
      .eraseToAnyPublisher()
  }
}

As you can see, I no longer have any publishers defined as properties of RecursiveLoader. Instead, loadPages() now returns an AnyPublisher<[Item], Never> that I can subscribe to directly which is much cleaner. Inside loadPages() I create a publisher that will be used to push new responses on by the performPageLoad(using:) method. The loadPages() method returns the intermediate publisher but applies a reduce on it to collect all intermediate responses and create an array of items.

I also use the handleEvents() function to hook into receiveSubscription. This allows me to kick off the page loading as soon as the publisher returned by loadPages is subscribed to. By doing this users of loadPage() don't have to kick off any loading manually and they can't forget to subscribe before starting the loading process like they could in my initial attempt.

The performPageLoad(using:) takes a PassthroughSubject<Response, Never> as its argument. Inside of this method, I call loadPage() and subscribe to its result. I then send the received result using the received subject and complete it if there are no more pages to load. If there are more pages to load, I call performPageLoad(using:) again, and pass the same subject along to that method so that next call will also publish its result on the same passthrough subject so I can reduce it into my collection of items.

Using this approach looks exactly as I wanted:

let networking = RecursiveLoader()
networking.loadPages()
  .sink(receiveCompletion: { _ in
    // handle errors
  }, receiveValue: { items in
    print(items)
  })
  .store(in: &cancellables)

There are still some things I'm not entirely happy with in this implementation. For example, performPageLoad(using:) must emit its values asynchrononously. For an implementation like this were you rely on the network that's not a problem. But if you'd modify my loadPage method and remove the delay that I have added before completing my Future, you'll find that a number of items are dropped because the PassthroughSubject didn't forward them into the reduce since the publisher created by loadPage() wasn't set up just yet. The reason for this is that receiveSubscription is called just before the subscription is completely set up and established.

Additionally, I subscribe to the publisher created by loadPage() in performPageLoad(using:) which is also not ideal, but doesn't directly harm the implementation.

Luckily, we can do better.

Attempt three: community help

After publishing the initial version of this article, a reader reached out to me with a very clean, and in hindsight, obvious solution to this problem that fixes both issues I had with my own second attempt. This solution gets rid of the need to subscribe to the publisher created in loadPage() entirely and also ensures that no matter how loadPage() generates its result, all results are always collected and forwarded.

To make this solution work, the RecursiveLoader skeleton needs to be modified slightly compared to my earlier version:

struct Response {
  var hasMorePages = true
  var items = [Item(), Item()]
  var nextPageIndex = 0
}

class RecursiveLoader {
  init() { }

  private func loadPage(withIndex index: Int) -> AnyPublisher<Response, Never> {
    // this would be the individual network call
    Future { promise in
      DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
        let nextIndex = index + 1
        if nextIndex < 5 {
          return promise(.success(Response(nextPageIndex: nextIndex)))
        } else {
          return promise(.success(Response(hasMorePages: false)))
        }
      }
    }.eraseToAnyPublisher()
  }

The loader no longer tracks the number of requests it has made. The loadPage() method is now loadPage(withIndex:). This index represents the page that should be loaded. In this case I want to load 5 pages and then complete the chain. The Response object now has a nextPageIndex that's used to represent the next index that should be loaded. So in this case I will start with an index of 0 and create new Response objects until I reach index 4 which is the fifth page because I started counting at 0.

The loadPages() still does all of the work but it's modified as follows:

func loadPages() -> AnyPublisher<[Item], Never> {
  let pageIndexPublisher = CurrentValueSubject<Int, Never>(0)

  return pageIndexPublisher
    .flatMap({ index in
      return self.loadPage(withIndex: index)
    })
    .handleEvents(receiveOutput: { (response: Response) in
      if response.hasMorePages {
        pageIndexPublisher.send(response.nextPageIndex)
      } else {
        pageIndexPublisher.send(completion: .finished)
      }
    })
    .reduce([Item](), { allItems, response in
      return response.items + allItems
    })
    .eraseToAnyPublisher()
}

Inside loadPages() a CurrentValueSubject is used to drive the loading of pages. Since we want to start loading pages when somebody subscribes to the publisher created by loadPages(), a CurrentValueSubject makes sense because it emits its current (initial) value once it receives a subscriber. The publisher returned by loadPages() applies a flatMap to pageIndexPublisher. Inside of the flatMap, the page index emitted by pageIndexPublisher is used to create a new loadPage publisher that will load the page at a certain index. After the flatMap, handleEvents(receiveOutput:) is used to determine whether the nextPageIndex should be sent through the pageIndexPublisher or if the pageIndexPublisher should be completed. When the nextPageIndex is emitted by the pageIndexPublisher, this triggers another call to loadPage(withIndex:) in the flatMap.

Since we still use a reduce after handleEvents(receiveOutput:), all results from the flatMap are still collected and an array of Item objects is still emitted when pageIndexPublisher completed.

I can imagine that this is slightly mindbending so let's go through it step by step.

When the publisher that's returned by loadPages() receives a subscriber, pageIndexPublisher immediately emits its initial value: 0. This value is transformed into a publisher using flatMap by returning a publisher created by loadPage(withIndex:). The loadPage(withIndex:) fakes a network requests and produces a Response value.

This Response is passed to handleEvents(receiveOutput:), where it's inspected to see if there are more pages to be loaded. If more pages need to be loaded, pageIndexPublisher emits the index for the next page which will be forwarded into flatMap so it can be converted into a new network call. If there are no further pages available, the pageIndexPublisher sends a completion event.

After the Response is inspected by handleEvents(receiveOutput:), it is forwarded to the reduce where the Response object's item property is used to build an array of Item objects. The reduce will keep collecting items until the pageIndexPublisher sends its completion event.

In Summary

This blog post was a fun one to write. Especially because I didn't know I was going to write it until I did. I hope I've been able to give you a glimpse into the thought process that I use what I design and implement solutions to complicated problems. By coming up with an ideal call-site first I usually already get a good sense of what my implementation should look like. And by throwing that ideal implementation aside for a moment and getting something that works first, I always get a good sense of what works and what doesn't without worrying too much about the result.

If you have any questions or feedback for me, don't be scared and send me a message on Twitter.

I did not mention who gave me the tip to use a CurrentValueSubject and a flatMap in my solution because they preferred to remain anonymous.

What’s the difference between Float and Double in Swift

A Double and Float are both used to represent decimal numbers, but they do so in slightly different ways.

If you initialize a decimal number in Swift using as shown below, the Swift compiler will assume that you meant to create a Double:

let val = 3.123 // val is inferred to be Double

The reason for this is that Double is the more precise type when comparing it to Float. A Float holds a total of 8 positions, or 32 bits. Since Double is more precise, it can hold more positions. It uses 64 bits to do this. In practice, that means the following:

print(Double.pi) // 3.141592653589793
print(Float.pi) // 3.1415925

As you can see, Double can represent pi far more accurately than Float. We can make the difference more obvious if we multiply pi by 1000:

print(Double.pi * 1000) // 3141.592653589793
print(Float.pi * 1000) // 3141.5925

Both Double and Float sacrifice some after the comma precision when you multiply pi by 1000. For Float this means that it only has four decimal places while Double still has twelve.

Swift Property Wrappers Explained

Property wrappers are a feature that was introduced in Swift 5.1 and they play a huge role in SwiftUI and Combine which are two frameworks that shipped alongside Swift 5.1 in iOS 13. The community was quick to create some useful examples that were embraced by folks relatively quickly.

As a user of property wrappers, you don't need to be concerned about what they are exactly, or how they work. All that you need to know is how you can use them. However, if you're curious how property wrappers work on the inside, this is just the post for you.

This week I would like to take a deep dive into property wrappers and take you on a journey to see how they work exactly.

Property wrappers look a lot like macros, but they're not the same thing. If you'd like to learn more about their differences, take a look at my comparison post here.

Why do we need property wrappers?

The reason that the Swift team wanted to add property wrappers to the Swift language is to help facilitate common patterns that are applied to properties all the time. If you've ever marked a property as lazy, you've used such a pattern. The Swift compiler knows how to handle lazy, and all the code needed to expand your lazy keyword into code that actually makes your property lazy is hardcoded into the compiler.

Since there are many more of these patterns that can be applied to properties, hardcoding all of them wouldn't make sense. Especially since one of the goals of property wrappers is to allow developers to provide their own patterns in the form of property wrappers.

Let's back up and zoom in on the lazy keyword. I just mentioned that the compiler expands your code into other code that makes your property lazy. Let's take a look at what this would look like if we didn't have the lazy keyword, and we'd have to write a lazy property ourselves.

This example is taken directly from the Swift evolution proposal and slightly modified for readability:

struct MyObject {
  private var _myProperty: Int?

  var myProperty: Int {
    get {
      if let value = _myProperty { return value }
      let initialValue = 1738
      _myProperty = initialValue
      return initialValue
    }
    set {
      _myProperty = newValue
    }
  }
}

Notice that this code is far more verbose than just writing lazy var myProperty: Int?.

Capturing a large number of these patterns in the compiler isn't desirable, and it's also not very extensible. The Swift team wanted to allow themselves, and developers to use keywords to define their own patterns that are similar to lazy to help them clean up their code, and make their code more expressive.

Note that property wrappers do not allow developers to do otherwise impossible things. They merely allow developers to express patterns and intent using a more expressive syntax. Let's move on and look at an example.

Picking apart a property wrapper

A property wrapper that I use a lot is the @Published property wrapper from Combine. This property wrapper converts the property that it's applied to into a publisher that will notify subscribers when you change that property's value. This property wrapper is used like this:

class MyObject {
  @Published var myProperty = 10
}

Fairly straightforward, right?

To access the created publisher I need to use a $ prefix on myProperty. So $myProperty. The myProperty property points to the underlying value which is an Int with a value of 10 by default in this case. There's also a second prefix that can be applied to a property wrapper which is _, so _myProperty. This is a private property so it can only be accessed from within MyObject in this case but it tells us a lot about how property wrappers work. In the case of the MyObject example above, _myProperty is a Published<Int>. $myProperty is a Published.Publisher and myProperty is an Int. So that single line of code results in three different kinds of properties we can access. Let's define a custom property wrapper and find out what each of these three properties is, and what it does.

@propertyWrapper
struct ExampleWrapper<Value> {
  var wrappedValue: Value
}

This property wrapper is very minimal, and not very useful at all. However, it's good enough for us to explore the anatomy of a property wrapper.

First, notice that the ExampleWrapper struct has an annotation on the line before its definition: @propertyWrapper. This annotation means that the struct that's defined after it is a property wrapper. Also note that the ExampleWrapper is generic over Value. This Value is the type of the wrappedValue property.

Property wrappers don't have to be generic. You can hardcode the type of wrappedValue if needed. You could hardcode the wrappedValue if you want your property wrapper to only work for a specific type. Alternatively, you can constrain the type of Value if needed.

The wrappedValue property is required for a property wrapper. All property wrappers must have a non-static property called wrappedValue.

Let's put this ExampleWrapper to work:

class MyObject {
  @ExampleWrapper var myProperty = 10

  func allVariations() {
    print(myProperty)
    //print($myProperty)
    print(_myProperty)
  }
}

let object = MyObject()
object.allVariations()

Notice that I have commented out $myProperty. I will explain why in a moment.

When you run this code, you would see the following printed in Xcode's console:

10
ExampleWrapper<Int>(wrappedValue: 10)

myProperty still prints as 10. Accessing the property that's marked with a property wrapper directly will print the wrappedValue property of the property wrapper. When you print _myProperty, you access the property wrapper object itself. Notice that _myProperty is a member of MyObject. You can type self._myProperty and Swift will know what to do, even though you never explicitly defined _myProperty yourself. I mentioned earlier that _myProperty is private so you can't access it from outside of MyObject but it's there.

The reason is that the Swift compiler will take that @ExampleWrapper var myProperty = 10 line and convert in something else behind the curtain:

private var _myProperty: ExampleWrapper<Int> = ExampleWrapper<Int>(wrappedValue: 10)

var myProperty: Int {
  get { return _myProperty.wrappedValue }
  set { _myProperty.wrappedValue = newValue }
}

There are two things that we can learn from this example. First, you can see that property wrappers really aren't magic. They are actually relatively straightforward. This doesn't make them simple, or easy, but once you know that this conversion from a single definition is exploded into two separate definitions it suddenly becomes a lot easier to reason about.s

The _myProperty isn't some kind of magic value. It's a real member of MyObject that's created by the compiler. And myProperty returns the value of wrappedValue because it's hardcoded that way. Not by us, but by the compiler.

The _myProperty property is called the synthesized storage property. It's where the property wrapper which provides the storage for its wrapped value exists.

So where's $myProperty?

Not all property wrappers come with a $ prefixed flavor. The $ prefixed version of a property wrapped property is called the projected value. The projected value can be useful to provide a special, or different interface for a specific property wrapper like @Published does for example. To add a projected value to a property wrapper you must implement a projectedValue property on the property wrapper definition.

In MyExampleWrapper this would look as follows:

@propertyWrapper
struct ExampleWrapper<Value> {
  var wrappedValue: Value

  var projectedValue: Value {
    get { wrappedValue }
    set { wrappedValue = newValue }
  }
}

This example isn't useful at all, I will show you a more useful example in the next section. For now, I want to show you the anatomy of a property wrapper without any bells and whistles.

If you'd use this property wrapper like before, Swift will generate the following code for you:

private var _myProperty: ExampleWrapper<Int> = ExampleWrapper<Int>(wrappedValue: 10)

var myProperty: Int {
  get { return _myProperty.wrappedValue }
  set { _myProperty.wrappedValue = newValue }
}

var $myProperty: Int {
  get { return _myProperty.projectedValue }
  set { _myProperty.projectedValue = newValue }
}

An extra property is created that uses the private _myProperty's projectedValue for it's get and set implementations.

Since _myProperty is private, your projected value might provide direct access to the property wrapper which is one of the examples shown in the original property wrapper proposals. Alternatively, you could expose a completely different object as your property wrapper's projected value. It's up to you to make this choice. The @Published property wrapper uses its projectedValue to expose a publisher.

Implementing a property wrapper

I have already shown you how to define a simple property wrapper, but let's be honest. That example was boring and kind of bad. In this section, we'll look at implementing a custom property wrapper that mimics the behavior of Combine's @Published property wrapper. If you want to learn about Combine I have several posts about it in the Combine section of this blog.

Let's define the basics first:

@propertyWrapper
struct DWPublished<Value> {
  var wrappedValue: Value
}

This defines a property wrapped that wraps any kind of value. That's good. The goal here is to implement a projected value that exposes some kind of publisher. I will use a CurrentValueSubject for this. Whenever wrappedValue gets a new value, the CurrentValueSubject should emit a new value to its subscribers. A basic implementation might look like this:

@propertyWrapper
class DWPublished<Value> {
  var wrappedValue: Value {
    get { subject.value }
    set { subject.value = newValue }
  }

  private let subject: CurrentValueSubject<Value, Never>

  var projectedValue: CurrentValueSubject<Value, Never> {
    get { subject }
  }

  init(wrappedValue: Value) {
    self.subject = CurrentValueSubject(wrappedValue)
  }
}

Warning:
This implementation is very basic and should not be used as a reference for how @Published is actually implemented. I'm sure there might be bugs with this code. My goal is to help you understand how property wrappers work. Not to show you a perfect custom @Published property wrapper.

This code is vastly different from what you've seen before. The wrappedValue used the private subject to implement its get and set. This means that the wrapped value is always in sync with the subject's current value.

The projectedValue only has it's get specified. We don't want users of this property wrapper to assign anything to projectedValue; it's read-only.

When a property wrapper is initialized in its simplest form, it receives its wrapped value. The wrapped value passed to DWPublished is used to set up the subject with the value we're supposed to wrap as its initial value.

Using this property wrapper would look like this:

class MyObject {
  @DWPublished var myValue = 1
}

let obj = MyObject()
obj.$myValue.sink(receiveValue: { int in
  print("received int: \(int)")
})

obj.myValue = 2

The printed output for this example would be:

received int: 1
received int: 2

Pretty neat, right?

Since the property wrapper's projected value is a CurrentValueSubject, it has a value property that we can assign values to. If I'd do this, the property wrapper's wrappedValue is also updated because the CurrentValueSubject is used to drive the wrappedValue of my property wrapper.

obj.$myValue.value = 3
print(obj.myValue) // 3

This is something that's not possible with the @Published property wrapped because Apple exposes the projectedValue for @Published as a custom type called Published.Publisher instead of a CurrentValueSubject.

A more complicated property wrapper might take some kind of configuration, like a maximum or minimum value. Let's say I want to expand my @DWPublished property wrapper to limit its output by debouncing it. I would like to write the following code in MyObject to configure this:

class MyObject {
  @DWPublished(debounce: 0.3) var myValue = 1
}

This would debounce my published values with 300 milliseconds. We can update the initializer for DWPublished to accept this argument, and refactor the code a little bit:

@propertyWrapper
class DWPublished<Value> {
  var wrappedValue: Value {
    get { subject.value }
    set { subject.value = newValue }
  }

  private let subject: CurrentValueSubject<Value, Never>
  private let publisher: AnyPublisher<Value, Never>

  var projectedValue: AnyPublisher<Value, Never> {
    get { publisher }
  }

  init(wrappedValue: Value, debounce: DispatchQueue.SchedulerTimeType.Stride) {
    self.subject = CurrentValueSubject(wrappedValue)
    self.publisher = self.subject
      .debounce(for: debounce, scheduler: DispatchQueue.global())
      .eraseToAnyPublisher()
  }
}

The initializer for my property wrapper now accepts the debounce interval and uses this interval to create an all-new publisher that debounces my CurrentValueSubject. I erase this publisher to AnyPublisher so I have a nice type for my publisher instead of Publishers.Debounce<CurrentValueSubject<Value, Never>, S> where S : Scheduler which would be the type of my publisher if I didn't erase it.

My property wrapper's wrappedValue still shadows subject.value. The projectedValue now uses the debounced publisher for its get instead of the CurrentValueSubject.

Using this property wrapper now looks as follows:

class MyObject {
  @DWPublished(debounce: 0.3) var myValue = 1
}

var cancellables = Set<AnyCancellable>()

let obj = MyObject()
obj.$myValue
  .sink(receiveValue: { int in
    print("received int: \(int)")
  })
  .store(in: &cancellables)

obj.myValue = 2
obj.myValue = 3
obj.myValue = 4

If you would run this in a playground, only received int: 4 should be printed. That's the debouncer at work and it's exactly what I wanted to happen.

Note that because the property wrapper's projected value is now an AnyPublisher, it's no longer possible to assign new values using $myValue.value like we could before.

In summary

In this week's post, you saw how property wrappers work internally, and what happens when you use a property wrapper. I showed you that the Swift compiler generates code on your behalf and that a property wrapper is far from magic. Swift generates a _ prefixed private property that's an instance of your property wrapper, a $ prefixed property that shadows the private property's projectedValue property, and that the original property shadows the wrappedValue property of the _ prefixed private property.

Once you understand this, you can quickly see how property wrappers work and how they might be implemented. I demonstrated this by implementing my own version of Combine's @Published property wrapper. After that, I showed you how to create a property wrapper that can be configured with extra arguments by expanding your property wrapper's initializer.

I hope that you have a much clearer picture of how property wrappers work now and that using them feels less like magic. For questions or feedback, I would love to hear from you on Twitter.

Five tips to help you become a well-rounded developer

This week I wanted to write about something non-technical. And while the topic of this week's post isn't a technical one, I think it's an important topic for developers who want to expand their knowledge, and deepen their skills.

I have been a developer professionally for more than ten years at this point and in these ten years there are some fundamental lessons I have learned that I believe have helped me get where I am today.

In this week's post, I will share five tips with you that have made into the developer I am today, and I strongly believe that these tips can help you become a more well-rounded developer.

Tip 1: Read books

Some of my fundamental knowledge and programming principles come from books like The Pragmatic Programmer, and Design Patterns. None of these books are about Swift, or iOS Development. In fact, they predate iOS and Swift by many years. However, these books contain fundamental knowledge and experiences that apply to virtually every project you will work on.

The books I have mentioned specifically, will not teach you how to do X or Y. Instead, they contain tons of lessons, examples, principles and information that can help you see programming in the bigger picture. These books can help you put what we're doing today in Swift in a much broader perspective.

And even if you pick up a book about Swift development, like the ones Paul Hudson writes, or one of the books from objc.io or Ray Wenderlich, you will grow as a developer. By reading a complete book from an author you don't just learn the skills they teach. A good book about programming will also give you a glimpse inside of the author's brain. You will learn how they think about programming, and why they teach the code the way they do. Daniel Steinberg is an author who, in my opinion, is exceptionally good at this.

Over the years I have read dozens of programming books and the one thing that they all had in common is that I learned something more than just the concepts I read about. I learned new ways of thinking. I learned new ways to approach problems. And, equally important, my knowledge on the topic I read about got a huge boost.

2. Don't rely on tutorials all the time

Tutorials are a fantastic way to explore something new. Or to learn if you're just getting started. But there comes a point where following tutorial after tutorial won't teach you a lot anymore.

Where books will often help you understand something in depth, or help you see the bigger picture, tutorials are often much shorter publications that simply show you how to get the job done.

Tutorials often don't go too deep into any trade-offs, the reasoning behind certain decisions or how to apply the skills learned in a different context. The tutorial starts with a cool example, the code needed to implement the example is shown, and in the end you have replicated the example 1:1.

This is great because it gives you a sense of accomplishment, but how often do you come out of a tutorial having actually learned something new? Of course, if you're just starting out you will gain tons from tutorials. But there comes a moment where tutorials don't introduce you to any new tools, or where they don't introduce you to new language constructs anymore.

If you feel that this is the case for you, it's time to stop relying on tutorials. It's time that you move on learn how to figure out how you can build those cool tutorial demos on your own. And just follow the occasional tutorial for fun, or just to figure out how something specific was done before you move and and explore the how and why on your own.

This segues nicely into my next tip:

3. Learn to navigate the documentation

Once you stop relying on tutorials all the time, you will need a new way to learn. Picking up a book every time you want to learn something isn't very beneficial so luckily there is an alternative; documentation.

Granted, Apple's documentation for iOS hasn't been great the past couple of years. But regardless, learning how to navigate the documentation, or even learning how to browse the headers for Apple's frameworks can be a huge boost to discovering and learning new frameworks and technologies.

Once you understand how you can learn and read the documentation for the frameworks you use, you will feel far more confident about the products you're building. Instead of just replicating what you saw in a tutorial, you will be able to look up the frameworks and APIs used in a tutorial, and you will be able to reason about the choices that were made.

4. Participate in the community

There's a community around virtually every framework, programming language or tool. Participating in these communities can be scary, but from participating you can gain a ton.

Simply reading along on a community forum like the Swift forum, or being a member of a Slack group like the iOS Developers Slack and reading some of the conversations that go on in there can be eye opening. You don't have to actively participate immediately, but from just soaking up the knowledge that flows writhing these communities you can grow so much.

When I first started reading along with the Swift mailinglist, which later moved on to the Swift forums, I didn't understand half of the conversations that went on. And to be quite frank, there's still a lot of comments and explanations I don't fully understand all of the time. Either way, seeing what goes on and learning from it is a growing experience for me.

Slowly but surely my confidence has built over the years to the point where I enjoy participating in communities all the time. It's why I write this blog, and it's why I'm a workspace admin for the iOS developers group. Helping people figure things out isn't just fun. It's also a great way for me to learn.

So by passively reading conversations I don't understand well enough to participate in, and by actively helping community members where I can I have learned more than I could have in any other way. I can only say that I strongly recommend to find the community for your favorite framework, programming language or tool and start participating. Maybe just read along at first, and at some point you will have the confidence to start contributing. I know you will.

5. Step outside of your comfort zone

The last tip on my list flows nicely from the previous tip. Participating in a community might mean you have to step outside of your comfort zone. But that's not why I put this tip on the list.

What I mean by stepping outside of your comfort zone is that I think it's very beneficial for you as a developer to try new things. If you're an iOS developer, try building something that runs on the server. Maybe use Swift, or maybe learn a different language to do it, like Python. If you're a JavaScript developer, try working on an app sometimes.

By taking a look at new programming languages and platforms, you will learn more about the worlds you exist in as a developer. And more importantly, you will understand your (future) coworkers much better if you have an idea of what they work with.

If you've learned to step outside of your comfort zone, and learned to develop in more than a single language and/or platform, you will feel much more confident. And questions like "Will iOS development still be in demand in a couple of years" suddenly aren't as scary. You'll know how to adapt. You've learned to be flexible.

Bonus tip: Go beyond development

Once you're proficient at writing code and building apps, it's time to starting focussing on everything that happens around development. Try to gain a deeper understanding of the company you work for, it will help you understand the work you do much better which in turn means that you'll be able to make much better choices while coding. Maybe you can try mentoring or supporting junior developers around you or within your company. Helping others grow and learn is a fantastic way to solidify your own understanding of topics, as well as think about the way you work in a different light.

Just to clarify, I'm not saying go into management. Not even close. Of course, management is a viable career path but if you want to grow beyond senior levels of development you'll need to do more than just work on tickets you're assigned.

Some of the best developers I've worked with hardly stood out due to their coding skills (even when those skills were amazing). What stood out about these developers is that they had tons of connections in the workplace. They know loads of people, and they knew exactly who they needed to go to for which questions. And people also know exactly what to come to these skilled developers for. Whenever tickets were shown during a backlog refinement session, they would be able to discuss these tickets in far greater detail than just the scope of the ticket. They know what's going on in the company, where it's headed, and where it's coming from.

Knowing a lot about the context of your work, and having a solid network can really be a superpower as a developer.

In summary

Everybody's journey towards becoming a developer is different. For some is a long and hard journey, while other are lucky enough to come from a place where learning how to program was made easy. But there's one thing we all have in common. We're all constantly learning, growing and evolving.

In this week's post I have shared five tips with you. These tips are what I believe to be important because I believe that following these tips has helped me grow, learn, and evolve into the developer I am today.

The reason I say this is that I don't believe these five tips are the magical five tips that will help everybody. These are just my five tips. And I hope that they are useful to you. And if they're not, I hope you can find your own five tips. We're all different, and that means that different things work for everybody.

What’s the difference between catch and replaceError in Combine?

There are several ways to handle errors in Combine. Most commonly you will either use catch or replaceError if you want to implement a mechanism that allows you to recover from an error. For example, catch is useful if you want to retry a network operation with a delay.

The catch and replaceError operators look very similar at first glance. They are both executed when an error occurs in your pipeline, and they allow you to recover from an error. However, their purposes are very different.

When to use catch

The catch operator is used if you want to inspect the error that was emitted by an upstream publisher, and replace the upstream publisher with a new publisher. For example:

let practicalCombine = URL(string: "https://practicalcombine.com")!
let donnywals = URL(string: "https://donnywals.com")!

var cancellables = Set<AnyCancellable>()

URLSession.shared.dataTaskPublisher(for: practicalCombine)
  .catch({ urlError in
    return URLSession.shared.dataTaskPublisher(for: donnywals)
  })
  .sink(receiveCompletion: { completion in
    // handle completion
  }, receiveValue: { value in
    // handle response
  })
  .store(in: &cancellables)

In this example I replace any errors emitted by my initial data task publisher with a new data task publisher. Depending on your needs and the emitted error, you can return any kind of publisher you want from your catch. The only thing you need to keep in mind is that the publisher you create must have the same Output and Failure as the publisher that the catch is applied to. So in this case it needs to be a publisher that matches a data task publisher's Output and Failure.

Note that you cannot throw errors in catch. You must always return a valid publisher. If you only want to create a new publisher for a specific error, and otherwise forward the thrown error, you can use tryCatch which allows you to throw errors.

When to use replaceError

The replaceError operator is slightly simpler than the catch operator. With replaceError you can provide a default value that's used to replace any thrown error from upstream publishers. Note that this operator changes your Failure type to Never because with this operator in place, it will become impossible for your pipeline to fail. This is different from catch because the publisher you create in the catch operator might still fail.

Let's look at an example of replaceError:

enum MyError: Error {
  case failed
}

var cancellables = Set<AnyCancellable>()
var subject = PassthroughSubject<Int, Error>()

subject
  .replaceError(with: 42)
  .sink(receiveCompletion: { completion in
    print(completion)
  }, receiveValue: { int in
    print(int)
  })
  .store(in: &cancellables)

subject.send(1)
subject.send(2)
subject.send(completion: .failure(MyError.failed))

If you execute this code in a Playground you'll find that the console will contain the following output:

1
2
42
finished

The first two values are sent explicitly by calling send. The third value is the result of replacing the error I sent as the last value. Note that the publisher completes successfully immediately after. The upstream publisher completes as soon as it emits an error, it's one of the rules of Combine that publishers can only complete once, and they do so through an error event or a completion event.

Note that in catch, the publisher that emitted the error that triggered the catch completes when it emits an error. The publisher you return from catch does not have to complete immediately and it replaces the failed publisher completely. So your sink could receive several values after the source publisher failed because the replacement publisher is still active.