Implementing Builder Pattern to create Navigation Stack for UIViewController on iOS
Introduction
A Builder pattern is one of Design Pattern that belongs to the creational Design Pattern. The pattern exist to solve issue on creating a complex object. This pattern can helps developer to create an object in a simple and elegant way. To use the pattern, we need to create our own type
of the object that we want to build. Let's dive in to the problem.
Case Study
Supposed that we have a requirement :
GIVEN the customer got a notification
WHEN the customer press the notification
THEN the customer should navigated to the transaction detail screenGIVEN the customer is on the detail screen from push notification
WHEN the customer press back button
OR swipe back
THEN the customer should show transaction list screen
Now, based on this requirement, we can see that there is an automatic way to create a certain stack of UIViewController. Of course, we can managed this using deeplink, and then navigate to the screen using Coordinator
.
But, what if there is a simple way to navigate to the target screen? What if, to be able to do based on the requirement, there is a handy way without creating many coordinator(s)?
That is why we can creates the Builder class. A Builder, is one of the Creational Design Pattern, that can helps us to create an complex object in an elegant way.
The typical API when using a builder pattern is like this :
let user = UserBuilder()
.setName(Name(fullName, lastName))
.setAddress(Address(road, city, postalCode, country))
.build()
(Builder pattern from the client’s point of view).
With this API design, we can translate that into our needs, which is creating the navigation stack for the UIViewController.
Our goal is to create this Builder API :
let viewController = NavigationControllerStackBuilder(root: HomeViewController())
.addStack(TransactionsViewController())
.addStack(TransactionDetailViewController())
.build()// Do something with the `viewController`
Beautiful isn’t it?
Steps
To use the pattern, we need to create our own type
of the object that we want to build. First, we need to define the Builder class.
class NavigationControllerStackBuilder {}
Then, since the UINavigationController’s API needs to inject a rootViewController
when creating the navigationController object, then we need also passing the rootViewController
to our Builder, using the constructor injection.
class NavigationControllerStackBuilder { private let navigationController: UINavigationController // injecting the initial view controller
init(root: UIViewController) {
self.navigationController = UINavigationController(rootViewController: root)
}}
After that, we need to add a builder behavior. In this case, we want our builder to be able to stack the viewControllers while pushing it. We can then create the addStack(UIViewController)
method.
We need to return self, or its own type, the Builder instance, so that it can be chained with the next operation.
class NavigationControllerStackBuilder { private let navigationController: UINavigationController init(root: UIViewController) {
self.navigationController = UINavigationController(rootViewController: root)
} // adding stack
func addStack(_ viewController: UIViewController) -> NavigationControllerStackBuilder {
self.navigationController.pushViewController(viewController, animated: true)
return self
}}
Finally, we need to return the instance. In this case, returning the viewController on the build()
method. This is the standard method to returning the modified object for the Builder pattern.
class NavigationControllerStackBuilder { private let navigationController: UINavigationController init(root: UIViewController) {
self.navigationController = UINavigationController(rootViewController: root)
} func addStack(_ viewController: UIViewController) -> NavigationControllerStackBuilder {
self.navigationController.pushViewController(viewController, animated: true)
return self
}
// returning the last viewController that has the navigationController.viewControllers previous stacks.
func build() -> UIViewController {
return navigationController.visibleViewController
}
}
So inside the NavigationControllerStackBuilder
class, the full implementation should like this :
class NavigationControllerStackBuilder { private let navigationController: UINavigationController init(root: UIViewController) {
self.navigationController = UINavigationController(rootViewController: root)
} func addStack(_ viewController: UIViewController) -> NavigationControllerStackBuilder {
self.navigationController.pushViewController(viewController, animated: true)
return self
}
func build() -> UIViewController {
return navigationController.visibleViewController
}
}
Creating the Instance using the Builder Pattern
Now, when the client needs to display the viewController, let say from push notification, the rootViewController, you can see the app will displays the last pushed screen with the stack in the navigationController. Give it a try 🙂.
let viewController = NavigationControllerStackBuilder(root: HomeViewController())
.addStack(TransactionsViewController())
.addStack(TransactionDetailViewController())
.build()window?.rootViewController = viewController
Conclusion
Builder pattern is a creational Design Pattern. The pattern helps us to create a complex object with a simple API. When using a builder pattern, we need to create our own API to make the object creation posssible, and followed by build()
method to return the object that has been modified on the chaining builder API.
References :
Head First Design Patterns https://www.oreilly.com/library/view/head-first-design/0596007124/