안녕하세요 화면전환로직을 관리하기 위해 코디네이터패턴을 적용하기로 하였고 관련 내용과 적용방법에 대해 설명하겠습니다.
Coordinator
의미 : 움직임을 조정하는 사람
Coordinator Pattern은 2015년, Soroush Khanlou가 The Coordinator라는 글을 쓰면서 소개됩니다.
Khanlou는 ViewController가 flow로직, view로직, business로직등 너무 많은 역할을 한다고 생각하였습니다.
따라서 flow로직을 담당하는 객체를 만들었고 이 객체를 Coordinator
또는 Directors
라고 지칭합니다.
UINavigationController
를 사용하기가 버거워집니다. 왜냐하면 화면전환을 담당하는 코드가 ViewController에 의존하기 때문입니다. 따라서 Coordinator로 의존성을 분리하면 각각의 ViewController는 이전, 다음 ViewController에 대해서 알 필요가 없어지게 됩니다.Coordinator패턴을 적용시키는 방법은 두가지로 나뉠 수 있습니다.
첫번째로 기본적인 방법으로는 자식Coordinator를 관리하지 않는 방법과 두번째로 자식Coordinator를 함께 관리하는 방법이 있습니다.
Coordinator 프로토콜 생성
import UIKit
protocol Coordinator: AnyObject {
var navigationController: UINavigationController { get set }
init(navigationController: UINavigationController)
func start()
}
객체 생성하기
import UIKit
final class MainCoordinator: Coordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController")
navigationController.pushViewController(vc, animated: false)
}
}
엔트리포인트에 Coordinator 연결하기
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var coordinator: MainCoordinator?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let navController = UINavigationController()
coordinator = MainCoordinator(navigationController: navController)
coordinator?.start()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var coordinator: MainCoordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let navController = UINavigationController()
coordinator = MainCoordinator(navigationController: navController)
coordinator?.start()
window = UIWindow(windowScene: windowScene)
window?.rootViewController = navController
window?.makeKeyAndVisible()
}
}
화면전환
Coordinator 프로토콜 생성
// MARK: - 기본 Coordinator 프로토콜
protocol Coordinator: AnyObject {
var finishDelegate: CoordinatorFinishDelegate? { get set }
var navigationController: UINavigationController { get set }
var childCoordinators: [Coordinator] { get set }
var type: CoordinatorType { get }
func start()
func finish()
func findCoordinator(type: CoordinatorType) -> Coordinator?
init(_ navigationController: UINavigationController)
}
extension Coordinator {
func finish() {
childCoordinators.removeAll()
finishDelegate?.coordinatorDidFinish(childCoordinator: self)
}
func findCoordinator(type: CoordinatorType) -> Coordinator? {
var stack: [Coordinator] = [self]
while !stack.isEmpty {
let currentCoordinator = stack.removeLast()
if currentCoordinator.type == type {
return currentCoordinator
}
currentCoordinator.childCoordinators.forEach({ child in
stack.append(child)
})
}
return nil
}
}
작업을 마친경우 작동할 프로토콜을 선언합니다.
protocol CoordinatorFinishDelegate: AnyObject {
func coordinatorDidFinish(childCoordinator: Coordinator)
}
각각의 Coordinator의 Type을 지정할 enum을 선언합니다.
enum CoordinatorType {
case app, login, home
case signUp, signIn
}
각각의 Coordinator의 프로토콜을 선언합니다.
protocol LoginCoordinatorProtocol: Coordinator {
func openSignUpCoordinator() // 회원가입 과정 시작
func openSignInCoord() // 로그인 과정 시작
}
객체(Coordinator)를 작성합니다.
final class LoginCoordinator: LoginCoordinatorProtocol {
weak var finishDelegate: CoordinatorFinishDelegate?
var navigationController: UINavigationController
var loginViewController: LoginHomeViewController
var childCoordinators: [Coordinator] = []
var type: CoordinatorType = .login
init(_ navigationController: UINavigationController) {
self.navigationController = navigationController
self.loginViewController = LoginHomeViewController.instantiate()
}
func start() {
self.loginViewController.coordinator = self
self.navigationController.viewControllers = [self.loginViewController]
}
func modalStart() {
self.loginViewController.modalPresentationStyle = .fullScreen
self.loginViewController.modalTransitionStyle = .crossDissolve
self.navigationController.modalPresentationStyle = .fullScreen
self.navigationController.modalTransitionStyle = .crossDissolve
self.loginViewController.coordinator = self
self.navigationController.viewControllers = [self.loginViewController]
}
func openSignUpCoordinator() {
let signUpCoordinator = DefaultSignUpCoordinator(self.navigationController)
signUpCoordinator.finishDelegate = self
self.childCoordinators.append(signUpCoordinator)
signUpCoordinator.start()
}
func openSignInCoord() {
let signInCoordinator = DefaultSignInCoordinator(self.navigationController)
signInCoordinator.finishDelegate = self
self.childCoordinators.append(signInCoordinator)
signInCoordinator.start()
}
}
extension LoginCoordinator: CoordinatorFinishDelegate {
func coordinatorDidFinish(childCoordinator: Coordinator) {
self.childCoordinators.removeAll()
self.finishDelegate?.coordinatorDidFinish(childCoordinator: self)
}
}
HackingWithSwift
Khanlou