← Writing
January 7, 2022

Making a great iOS app

I love iOS development, really, tho I’ve always touched the top of it. Using frameworks like SwiftUI makes it soo easy to craft a beautiful app that lets you forget UIKit within a second. But that’s only if you ignore all the crispy bugs it also gives you free out of the box ✨. Therefore it was time for me to get back to the good ol UIKit and finally get my hands dirty making a production quality app (with production ready I speak about the code not the product 🥲)

Quality === Complex

Well, that’s something we all think. If you want to write quality code it has to be complex with many comments about how good your code is and stuff. But no, that’s not true, and of course im not the first one to say that. But I do think it is something important to keep in mind while building the application because in every application quality Is the most important element. The quality is not only defined in how easy it is to read. But stuff like scalability and correctness play an important factor. So, what where my decisions while working on this app? Well to sum it up.

  • Create “overkill” code, with that I wanted to code something that’s not usually done on a small scale.
  • Make the code maintainable but also modular , let somebody add an extra module when they want.
  • Separate every single bit I can, just to learn how and and why someone would do that.

With all that in mind I started coding. First I started checking out the Coordinator pattern, mostly due to the fact that I didn’t want to use Storyboards. It’s slow, annoying for GIT and coding views is just more joyful. Using the coordinator pattern, I also give myself the change to remove the tight coupling you get when doing normal navigation. After checking out this page to get to know some more about the pattern, I started Xcode and created my coordinator protocol

import UIKit

protocol Coordinator: AnyObject {
    
    /// The child coordinators
    var childCoordinators: [Coordinator] { get set }
    
    /// The navigation controller
    var navigationController: UINavigationController { get set }
    
    // Designated starter method
    func start()
}

As you see, every class Class conforming to the Coordinator protocol, includes a variable named childCoordinators and one named navigationController. The navigation controller is the place that helps you do the transitions between views. Including a UINavigationController also gives you all the fancy navigation UI elements that works out of the box.

After creating the Coordinator protocol, I made my first Coordinator named AppCoordinator. The AppCoordinator is basically the starting point of the whole app. It shows the loading screen, checks if the necessary frameworks are working and redirects the user to the base coordinator as a child coordinator. With that, it includes all the magic of Coordinators. All without making a mess of my code.

import Foundation
import UIKit

protocol AppCoordinatorDelegate: AnyObject {
    
    func handleLaunchState(_ state: LaunchState)
    
    /// Retry loading the requirements
    func retry()
    
    func reset()
}

class AppCoordinator: Coordinator {
    
    let window: UIWindow
    
    var childCoordinators: [Coordinator] = []
    
    var navigationController: UINavigationController

    // Flag to prevent starting the application more than once
    // which can happen with the config being fetched within the TTL.
    private var isApplicationStarted = false
    
    /// For use with iOS 13 and higher
    @available(iOS 13.0, *)
    init(scene: UIWindowScene, navigationController: UINavigationController) {

        window = UIWindow(windowScene: scene)
        self.navigationController = navigationController
    }

    /// For use with iOS 12.
    init(navigationController: UINavigationController) {

        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.navigationController = navigationController
    }
    
    func start() {
        startLauncher()
    }
   
    // MARK: - Private functions
    
    /// Launch the launcher
    private func startLauncher() {
        let destination = LaunchViewController(
            viewModel: LaunchViewModel(coordinator: self)
        )
        
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        navigationController.viewControllers = [destination]
    }
    
    /// Start the real application
    private func startApplication() {
        guard !isApplicationStarted else { return }
        isApplicationStarted = true
        
        guard childCoordinators.isEmpty else {
            if childCoordinators.first is BaseCoordinator {
                childCoordinators.first?.start()
            }
            return
        }
        
        let coordinator = BaseCoordinator(navigationController: navigationController, window: window)
        startChildCoordinator(coordinator)
    }
    
}

// MARK: - AppCoordinatorDelegate
extension AppCoordinator: AppCoordinatorDelegate {
    
    func handleLaunchState(_ state: LaunchState) {
        
        switch state {
        case .noActionNeeded:
            startApplication()
        case .internetRequired:
            print("internet is required")
        }
    }
    
    func retry() {
        
        if let presentedViewController = navigationController.presentedViewController {
            presentedViewController.dismiss(animated: true) { [weak self] in
                self?.startLauncher()
            }
        } else {
            startLauncher()
        }
    }
    
    func reset() {
    
        isApplicationStarted = false
        childCoordinators = []
        retry()
    }
}

Is it something for me?

Well it depends, after using this pattern I started thinking about whether I like it or not. And although I do like it, I also think it’s made for specific use cases and if I reflect on this project it was a bit of overkill. But that was also the thing I wanted to learn, when do I have to use it and how does it work. I figured it out, so now it’s time for a new adventure.