Open URLs using the iOS UIResponder chain in Swift

Joseph El Mallah
4 min readDec 9, 2018

--

When creating iOS apps we find the need to have some buttons or UIControls opening a URL link.

This can be easily done by calling the open(_:options:completionHandler:) method of UIApplication. With the addition of the SFSafariViewController in iOS 9, this opened new possibilities to show links without the user leaving the app.

While the UIApplication open(_:options:completionHandler:) can be called from anywhere in your code, presenting a SFSafariViewController can happen only from a UIViewController.

A real-world use case that leads to this solution

I was recently working on an application that parsed and builds interactive documents from a JSON object. Among the multiple objects that constitute an interactive document, there is a simple link object. If the user taps the link, we present the content in a SFSafariViewController (SafariVC) without leaving the app.

The app hierarchy looked similar to the following diagram:

The app hierarchy when viewing a document

At the root we have a navigation controller and several embeds after we can see the link. The tricky part was conveying the user interaction from the link all the way up to the root view controller so we can present the SafariVC by pushing it in the Navigation stack.

A multitude of solutions can be used to solve this issue. One of them is to assign the DocumentController as a Target for the TouchUpInside event on the Link, and expose a Delegate property with a didTapLink(_ link: URL) callback. Then ask the DocumentGridController to assign its parent as the delegate for its children.

This seems like a viable solution but it involves a lot of constraints:
- the LinkView needs to be a UIControl,
- the DocumentController needs to know about the Link,
- the TopicViewController needs to adhere to a Protocol,
- the DocumentGridController needs to know its parent and assign it to its children. This becomes very messy and hard to maintain.

On top of that, what would happen if we presented a DocumentController from elsewhere? We should always remember to assign a delegate and implement the corresponding protocol.

The solution: Propagating links via the Responder Chain

What if we can use the same logic as the touch handling in iOS uses?

The goal is to propagate the action of opening a link up in the hierarchy. So each object in that hierarchy can decide if it can open the URL or should pass it to the next one.

If you are not familiar with the UIResponder chain, I would recommend you reading: Using Responders and the Responder Chain to Handle Events from Apple documentation.

Here is a diagram showing how the propagation will take place if the UIResponder chain is used to carry the URL call.

URL propagation over the Responder chain

Once the Link gets tapped, it will propagate a call to open a URL. All intermediate Views and Controllers will forward it until it reaches the TopicViewController that will handle it and push a SafariVC.

Here is a snippet of the Protocol and Implementation:

Every class that conforms to URLHandler can receive URL requests via the responder chain. The function propagateURL in the UIResponder extension will trigger the propagation:

  • It will make sure that the chain is not over yet. Otherwise, it will return a failure
  • It will then check if the next responder cannot handle URLs. Otherwise, it will carry on the propagation
  • If the next responder can handle URLs, it will ask it to handle the URL
  • If the completion is not successful then the propagation will continue. Otherwise, the propagation of the URL stops.

So since our LinkView is a UIResponder, it can call propagateURL to start the propagation. The propagation will continue until it reaches the TopicViewController that conforms to URLHandler protocol. All the intermediate views and controllers will forward the calls since they do not conform to the URLHandler protocol.

Inside our HandleURL method in the TopicViewController we can instantiate a SafariVC and present it.

Handling specific URLs

By having the URLHandler conformance an object can decide whether it can or can’t handle a URL. For example, our TopicViewController can check the URL domain and decide whether to open it in SafariVC or continue its propagation. In our app, we chose to open all links using the SFSafariController except Youtube links.

Asynchronous execution

Unlike the touch events propagation, the URLHandling can happen asynchronously. The propagation only continues when the completion block of the URLHandler method is called. This allows checking a remote server for Whitelisted URLs for example.

Bonus

Since UIApplication is also a UIResponder and is always at the end of the responder chain, we can use that to channel all unhandled URL propagation into the Safari app.

Conclusion

Using the UIResponder chain to propagate URLs can help decoupling classes. It works on the fact that if an object cannot handle a URL, then it should pass it to the next object and so forth. Using the UIResponder chain ensure a logical hierarchy that ends with the application delegate.

Thanks for reading!

--

--