hansontechsolutions.com

Make Your Swift Controllers Reusable and Efficient

Written on

Chapter 1: Introduction to Reusable Controllers

In app development, it’s common to need the same UI element across various screens. For instance, consider the case where you want to display a custom tab or navigation bar. The standard approach involves creating a distinct custom UIViewController for this navigation bar, which you then incorporate as a child view controller. In this article, I will demonstrate how to minimize the boilerplate code associated with managing this child view controller, making it highly reusable.

Keep in mind that when I mention reusing the child view controller class, I am not implying the reuse of a single instance of the controller.

For illustrative purposes, let's consider a scenario where your tech lead assigns you a task on a Monday with a Friday deadline. The objective is to finalize the login and registration flow, as outlined in the documentation below: The introduction page presents two options—login and register. When a user selects either option, the Phone/Otp confirmation page appears. After confirming their phone, the user is directed to either the registration page (if they came from the register action) or the main application page.

You might be tempted to dive right into the task and pass an enum or flag to the OTP page to indicate the user's origin. While this solves the immediate issue, if additional requirements arise later, you’ll find yourself adding more flags, leading to cluttered and difficult-to-maintain controllers.

So, what’s the solution? Take a moment to reflect over your coffee. I’ll be here when you’re ready to explore my solution.

Conceptualizing reusable controllers in Swift

The answer lies in crafting a Reusable ViewController. You might be thinking, “Talk is cheap; show me the code!” Let’s delve into the details.

Our PhoneConfirmation Controller should function like a service, allowing us to use it wherever phone confirmation is needed. Upon confirmation, we can redirect to multiple screens. Below are the components we’ll use:

  • IntroductionController
  • PhoneConfirmationController
  • RegisterController
  • MainPageController
  • LoginCoordinator — this manages all navigation throughout the login and registration process.

First, we need to establish our application’s initial controller, which we will handle through the SceneDelegate configuration.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

var loginCoordinator: LoginCoordinator?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

guard let scene = (scene as? UIWindowScene) else { return }

let window = UIWindow(windowScene: scene)

self.loginCoordinator = LoginCoordinator(window: window)

self.loginCoordinator?.start()

window.makeKeyAndVisible()

self.window = window

}

}

In this code, the SceneDelegate creates a LoginCoordinator associated with our window and configures the initial controller to be the IntroductionController, which is displayed first when the app launches.

Now, we need to design the controllers and establish navigational paths between them.

Chapter 2: Structuring Navigation

The key to crafting reusable controllers is to extract navigation responsibilities from the controller itself.

For instance, rather than embedding navigation logic directly within a button action, we can pass a notifier (closure) out of the controller. This way, the navigation handling is managed externally, in our Coordinator.

Let’s examine the IntroductionController in detail:

class IntroductionController: UIViewController {

var onLogin: (()-> ())?

var onRegister: (()-> ())?

override func viewDidLoad() {

super.viewDidLoad()

self.view.backgroundColor = .white

}

@IBAction func loginPressed(_ sender: Any) {

self.onLogin?()

}

@IBAction func registerPressed(_ sender: Any) {

self.onRegister?()

}

}

In the LoginCoordinator, we configure the IntroductionController:

func openIntroduction() {

let introductionController = IntroductionController()

self.initialController = introductionController

introductionController.onLogin = {

self.openPhoneConfirmation { isConfirmed in

if isConfirmed {

print("open main page")

}

}

}

introductionController.onRegister = {

self.openPhoneConfirmation { isConfirmed in

if isConfirmed {

print("open register");

}

}

}

}

This separation of concerns enhances the maintainability and reusability of the controllers.

Next, we define the PhoneConfirmationController:

class PhoneConfirmationController: UIViewController {

var onConfirm: ((Bool)-> ())?

override func viewDidLoad() {

super.viewDidLoad()

self.onConfirm?(true) // Simulate confirmation

}

}

The final version of the LoginCoordinator looks like this:

class LoginCoordinator {

var window: UIWindow

var navigationController: UINavigationController?

private var initialController: UIViewController? {

didSet {

if let initialController = initialController {

navigationController = UINavigationController(rootViewController: initialController)

self.window.rootViewController = navigationController

}

}

}

init(window: UIWindow) {

self.window = window

}

func start() {

openIntroduction()

}

func openIntroduction() {

let introductionController = IntroductionController()

self.initialController = introductionController

introductionController.onLogin = {

self.openPhoneConfirmation { isConfirmed in

if isConfirmed {

print("open main page")

}

}

}

introductionController.onRegister = {

self.openPhoneConfirmation { isConfirmed in

if isConfirmed {

print("open register")

}

}

}

}

func openPhoneConfirmation(confirmed: @escaping ((Bool)->())) {

let confirmationController = PhoneConfirmationController()

confirmationController.onConfirm = { isConfirmed in

confirmed(isConfirmed)

}

self.navigationController?.pushViewController(confirmationController, animated: true)

}

func openRegister() {

let registerController = RegisterController()

self.navigationController?.pushViewController(registerController, animated: true)

}

func openMainPage() {

let mainPage = MainPageController()

self.navigationController?.pushViewController(mainPage, animated: true)

}

}

Congratulations! You’ve now created controllers that are reusable. You can employ them anywhere in your application in any configuration that suits your needs.

Chapter 3: Video Resources

A tutorial on creating reusable buttons in Swift.

Learn how to implement generic and reusable API calls in iOS development.

Conclusion

In summary, the approach of reusing UI elements across multiple screens can significantly enhance your code's maintainability. By extracting navigation responsibilities from controllers and utilizing closure callbacks, you create more organized and reusable components.

Thank you for taking the time to read my insights on software development, coffee, and general topics. I appreciate your support and hope to make your reading experience valuable.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Reclaiming Your Time: Bidding Farewell to Distractions

Discover effective strategies to eliminate distractions and reclaim your time for more meaningful pursuits.

A Celestial Event: Witnessing the Great Conjunction of 2020

Experience the rare celestial alignment of Jupiter and Saturn on December 21, 2020, a sight not seen for centuries.

The Definitive Comprehensive Guide to Understanding CBD

Explore the essential facts, benefits, and risks of CBD, along with its legality and scientific insights.