본문 바로가기
🍎 iOS

[Swift] MVVMC 자세히 알아보기 - 튜토리얼 1 (Coordinator와 AppCoordinator 구현하기)

by 틴디 2022. 7. 10.
728x90
반응형

https://www.pexels.com/ko-kr/photo/12499889/

 

그 동안 MVVM과 MVVMC 그리고 Coordinator에 대해 포스팅을 했습니다. 혹시 MVVM이나 MVVMC가 생소하시다면 아래 포스팅을 참고하시면 좋을거 같아요!

만약 MVVM이 뭔지 자세히 모르겠다면 👇
2022.06.04 - [🍎 iOS/Architecture Pattern] - [Swift] MVVM 간단하게 알아보기
MVVM-C가 무엇인지 모르겠다면 👇
2022.06.11 - [🍎 iOS/Architecture Pattern] - [Swift] MVVMC 간단하게 알아보기
Coordinator의 역활이 궁금하다면 👇
2022.06.14 - [🍎 iOS/Architecture Pattern] - [Swift] MVVMC의 Coordinator 알아보기

 

  우선 MVVMC 구현 방법은 굉장히 많습니다! 그래서 딱 정해진 답은 아니기 때문에 만약 ⭐️⭐️더 좋은 방법이 있다면 댓글로 공유 부탁드려요!!⭐️⭐️

아키텍처 패턴 관련된 부분만 포스팅하기 위해서 네트워킹을 제외 했습니다! ViewModel, Model, Service 보다는 View, Coordinator에 중점을 둔 포스팅입니다!

 


플로우

  AppCoordinator가 최상위 Coordinator 입니다. 앱을 최초 실행하면 랜딩 화면이, 자동 로그인이 되어 있는 경우 홈으로 이동한다고 가정합니다!

  랜딩 페이지에서는 회원가입(Register)과 로그인을 선택할 수 있습니다. 회원가입 관련 화면은 RegisterCoordinator가, 로그인 관련 화면은 LoginCoordinator가 처리합니다

  랜딩 화면에서 로그인 버튼을 누르고 아이디와 비밀번호를 입력하여 로그인을 하게 되면 홈으로 이동합니다. 홈으로 이동하는 것은 Landing과 관련된 화면이동이 끝난 것입니다. 이때는 최상위 Coordinator에서 LadingCoordinator을 child coordinator에서 지우고 HomeCoordinator을 추가합니다.


디렉토리 구조

  Randing, Login, Home, Register가 각각 하나의 Scene으로 생각하고 Scenes를 최상위 폴더로 지정했습니다. Networking은 튜토리얼에서 제외 해서 Model, Service는 빠져 있는 구조입니다. 스토리보드는 사용하지 않고 코드로만 작성합니다. 


Coordinator 프로토콜

protocol Coordinator: AnyObject {
    var parentCoordinator: Coordinator? { get set }
    var childCoordinators: [Coordinator] { get set }
    
    func start()
    func finish()
    func addChildCoordinator(_ coordinator: Coordinator)
    func removeChildCoordinator(_ coordinator: Coordinator)
}

Coordinator.swift

  • parentCoordinator
    • 이 프로토콜을 구현하는 Coordinator를 호출한 상위 Coordinator
    • LandingCoordinator의  parentCoordinator는 AppCoordinator, LoginCoordinator의 parentCoordinator는 LandingCoordinator
  • childCoordinators
    • 호출한 Coordinator을 담아두는 Coordinator 어레이
    • LandingCoordinator가 랜딩화면에서 로그인 화면으로 이동할 때 LoginCoordinator을 childCoordinators에 추가함
  • start()
    • Coordinator를 생성하고 해당 함수를 호출. 해당 coordinator의 첫 화면이나 필요한 설정을 해줌
    • 현재 화면에 보여줄 ViewController를 생성하고 ViewController에 필요한 ViewModel을 설정하는 등의 작업 후 현재 화면에 보여주도록 처리하는 함수
  • finish()
    • 해당 Coordinator가 끝나면 호출.
    • 예를 들어 회원가입 절차가 끝나고 로그인 화면으로 이동할 때 RegisterCoordinator를 finish하여 상위 coordinator의 child coordinator에서 삭제하고 화면을 dismiss, pop 등을 수행함
extension Coordinator {
    func start() {
        preconditionFailure("오버라이드 해서 사용")
    }
    
    func finish() {
        preconditionFailure("오버라이드 해서 사용")
    }
    
    func addChildCoordinator(_ coordinator: Coordinator) {
        childCoordinators.append(coordinator)
    }
    
    func removeChildCoordinator(_ coordinator: Coordinator) {
        if let index = childCoordinators.firstIndex(where: { $0 === coordinator }) {
            childCoordinators.remove(at: index)
        } else {
            print("coordinator 삭제 실패: \(coordinator). ")
        }
    }
}

Coordinator.swift

  • Coordinator 프로토콜의 extension을 추가해 줍니다. start와 finish는 해당 Coordinator에 맞게 오버라이드 하여 구현해서 사용해야 하기 때문에 preconditionFailure만 작성해 줍니다.
  • addChildCoordinator 는 childCoordinators 어레이에 인자로 전달된 coordinator을 추가해 줍니다
  • removeChildCoordinator는 인자로 전달된 coordinator을 childCoordinator에서 삭제합니다. 랜딩화면에서 로그인 버튼을 눌러 로그인 화면으로 진입 한 뒤 뒤로 가기로 다시 랜딩화면으로 넘어와도 LandingCoordinator의 childCoordinator에서 삭제해 주어야 합니다.

최상위 Coordinator인 AppCoordinator

AppCoordinator는 Coordinator 중에서도 최상위 Coordinator입니다. 처음 앱에 진입했을 때 UIWindow의 rootViewController를 설정하여 초기 화면으로 진입할 수 있게 합니다. AppCoordinator 파일을 추가합니다.

class AppCoordinator: Coordinator {
    var parentCoordinator: Coordinator? = nil
    var childCoordinators = [Coordinator]()
    private let window: UIWindow?
    
    init(window: UIWindow?) {
        self.window = window
    }
}

AppCoordinator.swift

 

Coordinator프로토콜을 채택한 뒤 구현해주어야 하는 parentCoordinator와 childCoordinator을 작성해 줍니다.

AppCoordinator는 이니셜라이저로 UIWindow를 전달 받습니다. 이 window는 SceneDelegate로 부터 전달 받습니다. 앱 진입시 AppCoordinator가 호출 되도록 SceneDelegate를 작성해 주겠습니다. 

 

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    // 1
    var appCoordinator: AppCoordinator?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        // 2
        window = UIWindow(windowScene: windowScene)
        appCoordinator = AppCoordinator(window: window)
        //3
        appCoordinator?.start()
    }
}

1 & 2. appCoordinator 프로퍼티를 생성하고 이니셜라이져로 window를 전달합니다.

3. AppCoordinator는 Coordinator프로토콜을 따르기 때문에 start()를 호출 할 수 있습니다. 

 

window의 rootViewController 설정을 어디서 해주어야 할지 고민이 됬는데 일종의 화면 전환이고 앱 진입시 첫 화면을 설정해 주는 것이라 판단해서 AppCoordinator에서 처리하는 방법을 택했습니다. 

 

class AppCoordinator: Coordinator {
    var parentCoordinator: Coordinator? = nil
    var childCoordinators = [Coordinator]()
    private let window: UIWindow?
    
    //1
    lazy var navigationController: UINavigationController = {
        let rootViewController = UIViewController()
        let navigationController = UINavigationController(rootViewController: rootViewController)
        return navigationController
    }()
    
    init(window: UIWindow?) {
        self.window = window
    }
    
    // 2
    func start() {
        guard let window = window else {
            return
        }
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
    }
}

AppDelegate에서 해주었던 window의  rootViewController 설정을 AppCoordinator에서 해줍니다. 

1. window의 rootViewController가 될 UINavigationController을 생성합니다. 이때 UINavigationController의 rootViewController는 특정 VC가 아닌 UIViewController를 생성해서 지정해 주었습니다. 

2. start()를 작성해 줍니다. 앱이 실행되고 SceneDelegate가 호출되면 AppCoordinator의 start()를 호출합니다. 

AppCoordinator의 start()에서는 window를 설정해 주어야 합니다. 

 

여기까지 작성하고 앱을 실행하면 정상적으로 UIViewController가 rootViewController로 설정된  UINavigationController가 나타납니다. 만약 검은 화면 말고 하얀화면을 보고 싶다면 

    lazy var navigationController: UINavigationController = {
        let rootViewController = UIViewController()
        rootViewController.view.backgroundColor = .white
        let navigationController = UINavigationController(rootViewController: rootViewController)
        return navigationController
    }()

여기에서 rootViewController의 view 백그라운드 컬러를 하얀색으로 설정해 주면 됩니다. 

 

다음 포스팅에서는  AppCoordinator에서 특정 화면으로 이동하는 코드를 작성하겠습니다. 

728x90
반응형

댓글