iOS ViewController Url Navigation
To navigate between ViewControllers can be a bit tricky and should be right when the projects starts. The problem is that there are to many ways you can handle navigation and there is no real best practice. So I started experimenting what could be a clean approach. What I came up with is a web-like navigation with urls.
You can get the full source code from my github page, here I just want to point out the critical parts and how to use it.
One idea of mine was it would be greate if I could define in the URL if we want to pop to the rootviewcontroller and then navigate to our desired viewcontroller or just push the viewcontroller onto the stack. A great way would be the file scheme syntax:
push onto stack
app://foobar
pop to root and then push to stack
app:///foorbar
one reserved word would be animated
which state could be true
, 1
, false
, 0
. As you can guess it defines if we want to transition with animation or not.
The main idea behind this is using the application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
method from UIApplication
. What I was trying to do is parse the url match it to a ViewController and create a ViewModel from the query items.
This delegate starts the lookup by injecting the url and the table where we can find the naming to viewcontroller mapping.
The LookupTable is a Protocol
where you have to define a method viewController(for: String) -> UIViewController
. No real magic here. The next question is how do we inject values into the ViewController
and its views
.
app://foobar?title=foo
so I created a ViewModel
which implements the Protocol
UrlInitable
which says that the object has to implement a init?(url: URL)
method. Here I parse the url query items and set my properties accordingly.
The ViewController
now needs to implement the Protocol
ViewModelBindable
, which says we have a property viewModel
of any type. What this Protocol
and its default implementation does for me is supply a bind(url: URL)
and a bind(viewModel: T)
methods (T
has to be a UrlInitable
in this case). The parsing will do the rest.
Here is how it goes:
If the url is opened, it looks if there is a match between “foobar” and a ViewController in the LookupTable. Then it checks if the ViewController is a ViewModelBindable and we have query parameters. If so we will init the ViewController and bind the the viewModel with the url. This bind
method will now initialize the ViewModel with the url
and then call the bind(viewModel: T)
which simply sets the propertie.
Now we can simply use the didSet
function of the propertie to assigne the viewModel
to our views.
Using this, what do we need to do to set up a new ViewController?
-
create ViewController – implement
Protocol
ViewModelBindable
– implement a property viewModel -
create a ViewModel – implement
Protocol
UrlInitable
– implementinit?(url: URL)
-
add the string name and the viewcontroller to the Lookup Table
now you should be able to call UIApplication.shared.open(URL(string: "app://viewControllerName?title=foobar")!, options: [:], completionHandler: nil)
as a bonus the app is now completly deeplinkable. open the same url in safari and the app should handle it in the same way.
If you dont want deeplinking for all or specific section of your app you can check the scheme UIApplicationOpenURLOptionsKey
in application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
and disable it if the opening app is not app://
If you dont want to set a viewModel in your ViewController just use the Protocol
UrlBindable
, this requires you to implement bind(url: URL)
yourself and our done.
For more information See my project on github
Some thoughts about pros and cons Pro:
- the ViewController does not need to know about other ViewControllers and their ViewModels
Con:
- We do not have any type saftey for our viewModels.
- Really large viewModels will result in a really long url.
My way of handling the cons would be to not pass around the values in the url, but tell the next ViewController where to find the information it needs. In my next post I will show a way to abstract the navigation away from the ViewControllers to a dedicated class for routing.