swiftviewcontrollercoordinator-pattern

Load Screen with from a View Controller


I am new to Swift/programing in general and I am trying to become comfortable with the idea of coordinators. Sorry to just dump code but I am just learning.. thanks for any help.

My goal is to launch this app with my ViewController showing up on the screen and this view to have two buttons to go to two other views.

I have defined the following protocols.

import Foundation
import UIKit

protocol CoordinatorProtocol {
     var childCoordinators: [CoordinatorProtocol] { get set }
     var navigationController: UINavigationController { get set }

func start()
}

and

import Foundation
import UIKit

protocol StoryboardProtocol {
static func instantiate() -> Self
}

extension StoryboardProtocol where Self: UIViewController {
    static func instantiate() -> Self {
         let id = String(describing: self)
         let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
         return storyboard.instantiateViewController(withIdentifier: id) as! Self
    }
}

I then created my MainCoordinator

import Foundation
import UIKit

class MainCoordinator: CoordinatorProtocol {

    var childCoordinators = [CoordinatorProtocol]()
    var navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        let vc = ViewController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: false)
    }

    func addDisplayOne() {
        let vc = ViewOneController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }

    func addDisplayTwo() {
        let vc = ViewTwoController.instantiate()
        vc.coordinator = self
        navigationController.pushViewController(vc, animated: true)
    }
}

along with my three view controllers

import UIKit

class ViewController: UIViewController, StoryboardProtocol {

    weak var coordinator: MainCoordinator?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func oneTapped(_ sender: Any) {
        coordinator?.addDisplayTwo()
    }

    @IBAction func twoTapped(_ sender: Any) {
        coordinator?.addDisplayTwo()
    }
}

and

import UIKit

class ViewOneController: UIViewController, StoryboardProtocol {

    weak var coordinator: MainCoordinator?

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

and

import UIKit

class ViewTwoController: UIViewController, StoryboardProtocol {

    weak var coordinator: MainCoordinator?

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

I then updated the appDelegate.Swift with the following and left the rest of the functions as is

var coordinator: MainCoordinator?
var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions 
    launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
    let initialViewController : UIViewController = 
        mainStoryboard.instantiateViewController(withIdentifier: "ViewController") as 
        UIViewController
    self.window = UIWindow(frame: UIScreen.main.bounds)
    self.window?.rootViewController = initialViewController
    self.window?.makeKeyAndVisible()

    return true
}

I then updated the SceneDelegate.swift with the following and left the rest as is

var window: UIWindow?

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

    if let controller = UIStoryboard(name: "Main", bundle: 
    nil).instantiateViewController(withIdentifier: "ViewController") as? ViewController {
        if let window = self.window, let rootViewController = window.rootViewController {
            var currentController = rootViewController
            while let presentedController = currentController.presentedViewController {
                currentController = presentedController
            }
            currentController.present(controller, animated: true, completion: nil)
        }
    }

}

I have a Main.storyboard which has three view controllers. Each of the view controllers have their class and their storyboardID name as their class name.

All I get is a full black screen when i run the code... makes no sense to me.


Solution

  • A couple of things are wrong here. It's okay, as SceneDelegate.swift was just added recently.

    Your main issue here is that in your SceneDelegate's willConnectTo your if let window = self.window is nil, thus the rest of your code inside will not get executed resulting to a black screen.

    In order to make Coordinator pattern work with storyboard in SceneDelegate.swift add these 2 properties and replace willConnectTo to the following:

        var coordinator: MainCoordinator?
        var window: UIWindow?
    
    
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {        
            let navController = UINavigationController() //create navController
            coordinator = MainCoordinator(navigationController: navController) //initialize our coordinator
            coordinator?.start() //start coordinator
    
            guard let windowScene = (scene as? UIWindowScene) else { return }
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = navController //make it our root
            window?.makeKeyAndVisible()
            window?.windowScene = windowScene
        }
    

    Lastly, your AppDelegate.swift's didFinishLaunchingWithOptions function body does not need anything but return true to implement Coordinator pattern

    Feel free to use this as reference: https://github.com/SamuelFolledo/NewsApp/blob/646a005e1be9b90a4c63cb99bf92d5b2be6691cc/NewsApp/SceneDelegate.swift