Product · July 30, 2019

Propagating events on iOS

Olivier Collet
Olivier Collet

Five years ago, I wrote about the idea of the Flow Controller. It was inspired by a presentation from my friend Seb Morel at CocoaHeads Montreal in 2012, and was better brought to light a year later by Soroush Khanlou as he called it Coordinator. But after using the pattern on a few apps, I started to dislike it for two reasons.

The best code is no code.

First, the fact that coordinators have to keep track of the current interface state by keeping references to view controllers, forces us to write a lot of “maintenance” code when you move from a coordinator to another. Ben Sandofsky put it better than me:

In this system, you maintain a coordinator tree that lives a step-above the view controller hierarchy. You start at a central AppCoordinator, which maintains an array of child coordinators, which might have their own sub-coordinators, etc.

This doesn’t make sense to me. Why not use view controllers as coordinators? That’s what I do now, I use container view controllers as coordinators: UITabBarController, UINavigationController, or a simple UIViewController that contains children. It’s much simpler to transition from a coordinator to another when they are view controllers. And it’s easy to keep track of their hierarchy.

The best code is no code. Really.

The second reason I dislike the pattern is a bigger issue. When the view hierarchy is deep, and we need to pass an event up to the coordinator, we have to write a lot of delegates, and pass the event from an object’s delegate to another. That’s way too much code to write just to propagate an event!

Wait… I have an idea!

Because we’re dealing with the view hierarchy, I realized we can use the UIResponder chain to propagate events up the hierarchy.

In case you don’t know how the UIResponder chain works, here’s a short article by Apple that explains it.

Taking advantage of this, I created a new set of tools to help propagate events up the responder chain.


A concrete example

The screenshot and diagram below show a screen that has a deep hierarchy. The diagram presents the ‘old’ way with a coordinator that has a reference to a navigation controller, and delegates that report back to the coordinator. I ignored several more view controllers in the hierarchy because they don’t participate in the delegate propagation. Take a look, I’ll provide some explanations after.

Unsplash for iOS — Long press on a photo shows 3 action buttons
  • There’s a Coordinator that has a reference to a UINavigationController, in which an instance of UserViewController has been pushed.
  • That user view controller offers a way to toggle between three child view controllers. In this particular case, it shows a PhotosViewController, which contains a UICollectionViewController not shown in the diagram.
  • When performing a long press on any cell in the collection view controller, the photos view controller displays the ActionsView that contains three buttons.

When pressing any of these buttons, we need to propagate the action to the coordinator. Why as high up as the coordinator? Because these three actions can be performed in another view controller, so the code that handles these actions should be somewhere shared by both view controllers.

As you can see, this case requires several delegates to propagate the actions up to the coordinator. That’s too much. Luckily, I have a way to make it easier.


Let me introduce the tools I mentioned earlier. There’s two Swift protocols and an extension on UIResponder. That seems a lot, but you’ll see it’s quite simple.

The Event protocol

First, we need to use a type of value to describe the kind of event that will be sent along the responder chain. So I created an empty protocol called Event.

It is used like the Error protocol, by creating types that conform to that protocol, usually enums, with associated values if needed.

The EventHandler protocol

Second, we need a way to process the events as they come. So I created a protocol called EventHandler, which requires the implementation of a single function: func handleEvent(_ event: Event) -> Event?.

That function returns the event to be propagated up the chain, or nil if the propagation should stop. This allows the implementation to propagate a different event than the one it receives, offering a way to ‘transform’ events.

The UIResponder extension

Lastly, the backbone of all of this is an extension on UIResponder. It adds a single function: func postEvent(_ event: Event).

The implementation of that function takes care of propagating the event up the responder chain.


How to use it

Let’s take the example above, we’ll replace the delegates with this new protocol.

First, we create an enum for the actions that can be performed on a photo.

Next, in the ActionsView, we implement the actions for each button, each sending its corresponding event.

Finally, we create a subclass of UINavigationController that we’ll use as the app’s main coordinator. It implements the EventHandler protocol to handle the three events.

You’ll note in that last bit that users need to be authenticated to like or collect photos. The navigation controller is a good place to start the authentication flow by presenting another navigation view controller used as the authentication coordinator.

That’s it. As you can see, it’s quite easy to propagate events up the view hierarchy.


Bonus: EventObservers

Sometimes we may want to handle events in an object that is not part of the responder chain. So I wrote a little something for this.

It’s an extension on UIResponder that adds an eventHandler property. If you’ve paid attention closely, you should have found a mention of it in the implementation of the function of UIResponder extension above.

That way, we can implement the EventHandler protocol on any object, and assign that object to the eventHandler property on a relevant UIResponder in the responder chain, and that object will receive the events.


Use with caution

I’ve had the idea for this whole thing a year ago, but I cautiously used it since, making many improvements to the implementation. While this method is convenient because it avoids to implement tons of delegates in a complex view hierarchy, it isn’t meant to get rid of them.

It’s like a notification center using the responder chain.

It’s like shooting in the dark

If you call postEvent() but the event is not handled anywhere, you’re shooting in the dark. This can make it hard to debug.

There are unwanted listeners

With this event handler protocol, every responder in the responder chain has a chance to snoop into the events going through. They could act as “spies”.

It’s dependent on the responder the chain

If changes are made on how the responder chain works, the whole thing could break. It’s unlikely to happen, but we never know.


Final words

I haven’t looked at Combine yet, but I have the feeling it will make this protocol obsolete. But we’re not going to use iOS 13-only technologies in the short term at Unsplash, so EventHandler is a great solution in the meantime.

Since it’s so easy to use, it is a wonderful tool to add to our toolboxes, and I will use it more often in our apps.

I made the code of the EventHandler available on Github.

Share article