Almost in every application nowadays there is a list of items inside a table view, when the user clicks on an item another view will be opened to show the item details. Speaking of controllers, an app will contain basically two controllers: the items List controller and the item details controller. Imagine you have a push notification feature in the app, when the user taps on the push icon the app will open the details view controller directly, the problem in this situation is that we do not have the actual object at this moment (because the details VC works with item object) and all we’ve got from the push is the item ID or the item URL for example. Let’s see how can we work around this issue.

Here is the starter project that I am working on. Feel free to clone it if you want to follow along with me.

The project is simple, we have an EpisodesVC that shows us a list of game of thrones episodes from the first season and a DetailsVC that shows the details of a selected episode. Also I have defined a custom url scheme in order to be able to open the app from the outside like Safari.

What we want to do is to enable the details view controller to load data from url and locally. The latter one is already done but it will be affected by the changes.

First let’s create a new Helper.swift file then add the following:

enum Result<T, E> {

case success(T)

case error(E)

} enum LoadingState<T> {

case local(T)

case network(Resource<T>)

}

In the beginning we defined a Generic Result enum that has 2 cases: a success case with an associated value of type T, and an error case with an associated value of type E.

Later we are going to use this enum in order to return a view controller depending on its value be it success or error.

We also defined a Generic LoadingState enum that has 2 cases: a local case with an associated value T, and a network case with a Resource<T> as an associated value. We will use this enum to tell our view controller the origin of our data and where it comes from, either from the internet (network case) represented in a Resource<T> struct or locally (local case) represented by an actual episode.

Now we can add the following:

This Protocol has the blueprint of Two methods and one variable (read only) along with an associated type Content. The first method at line 8 is responsible for fetching the content and it has a completion block, it has a Result type parameter so we can call the block with Result.error(Error) in case the fetch has failed, otherwise if we got a proper response we can call the block with .success(Content). Note that if you want your block to throw an error you can declare fecthContent(…) as follows:

func fetchContent(completion: @escaping (_ inner: () throws -> Result<Content , Error>) -> Void)

Why do we need such Protocol ? this Protocol is going to be conformed by a Controller Coordinator struct. This way we can have as many controllers as we like that can display the data from internet or locally.

To explain further, we write our Details coordinator struct that implements RemoteContentCoordinator:

It is very straight forward. It implements both methods from the Remote ContentCoordinator Protocol and the one variable (loadingState<Episode>). The implementation of fetchContent at line 5 is simple. It checks if the case and the bonded value of loadingState is .local(let episode) then directly calls the completion block with Result.success(let episode), otherwise the case will be .network(let resource) so it will call the webservice to fetch the content. When the content is ready it will call the completion with the freshly downloaded episode object.

The second method required by the protocol is viewControllerForContent(), it is implemented at line 24. Again we have a switch on the result if the result is .success(..) it will return a proper view controller that can deal with the bonded value, else it will return a view controller that represents an error which we will be overlooking right now.

You may wonder how can we benefit from the DetailCoordinator struct. Instead of opening the details view controller directly, our strategy is to open a container view controller that contains the Details view controller as a child view controller.

Let’s implement the container view controller

First we are going to add an extension for UIViewController class to facilitate dealing with add/remove of the child view controller

UIViewController Extension

Container View Controller Implementation

It is a generic view controller which can work with any type that conforms to RemoteContentCoordinator Protocol, that is why we used a Protocol so later on if you have another view controller that can display remote/local data we only have to implement an new struct that conforms to RemoteContentCoordinator.

In its viewDidLoad() we only care if the coordinator loading state is .network(_) in order to display a loading indicator, or you can add your loading view controller as a child view controller. Then we call a getData() func.

getData func will ask the coordinator to fetchData and hands it to the completion handler. For the sake of this example we are overlooking the error case. Again we check if the coordinator’s loadingState is .network case only this time to hide the activity indicator or your own loading view controller. Next we ask the coordinator for its view controller and pass to it the result, then we add the returned view controller as a child view controller using our UIViewcontroller extension method.

What is childNavigationItemSetup for? When we add a UIViewController as a child view controller inside and the parent view controller lives inside a UINavigationController, the self.navigationItem of the child view controller will stop working. Why? according to the docs:

… each view controller pushed onto the navigation stack must have a UINavigationItem object that contains the buttons and views it wants displayed in the navigation bar. The managing UINavigationController object uses the navigation items of the topmost two view controllers to populate the navigation bar with content.

So to be able to set the bar button of the child viewController we should have a reference to its parent view controller because it is the topmost view controller. That is the job of childNavigationItemSetup, you can set it up for example in your viewDidLoad method as follows

(self.parentViewController as? RemoteContantContainerViewController<DetailsCoordinator>)?.childNavigationItemSetup? = { navigationItem in print(navigationItem) }

Let’s put our implementation in use. Remember, our project has a URL Scheme (remoteConf://) for the sake of the example will are going to append the episode number at the end of the URL i.e: remoteConf://4 means we want to open episode number 4. We can handle this in the project’s AppDelegate file as follows

At line 5 we construct the url

At line 6 we create the Resource struct

At line 11 we initialise our coordinator with loadingState .network(resource),

At line 12 we create the remoteContainer passing the coordinator in its initialiser

At line 14 we push the remoteContainer to the navigation stack.

In order to have the same flow in our app we should adjust the way we respond to a user clicking on a table view cell, so in EpisodsTableViewController.swift file find the below func

tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { .. }

Replace its content by:

let selected = episodes[indexPath.row] // get the episode let detail = DetailsCoordinator(loadingState: .local(selected)) // details coordinator with loadingState .local(selected)



let remote = RemoteContentContainerViewController(coordinator: detail) // container controller self.navigationController?.pushViewController(remote, animated: true) // push our container controller into navigation stack

As you can see it follows the same flow as the network case but here we have the actual episode struct so we initialise the DetailsCoordinator with .local(episode) case.

TEST

Run the project, if you are on the simulator click cmd+shit+H, open safari and go to remoteConf://4 it will ask you to open the app click on open then you will see the details of episode 4 as if you just clicked on it in the previous page.

Here is a github link of the full project, the latest changes are on final branch check it out to see the final result. I hope you enjoyed reading my article give it a heart ❤ if you like it.