Published on

๐ŸŽ Swift - Coordinator ํŒจํ„ด

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

์ง„์งœ๋กœ ์ด ํ”„๋กœ์ ํŠธ๊ฐ€ ์ด๋ ‡๊ฒŒ ์ด๊ฑฐ์ €๊ฑฐ ๋งŽ์ด ํ•ด๋ณด๊ฒŒ ๋  ์ค„์€ ๋ชฐ๋ž๋Š”๋ฐ.. ์•„๋ฌด๋ž˜๋„ ์ด๊ฑฐ์ €๊ฑฐ ๋„ˆ๋ฌด ๋งŽ์ด ํ•ด๋ณด๋Š๋ผ ์ดํ•ด๊ฐ€ ๋ถ€์กฑํ•˜๊ฒŒ ๋„˜์–ด๊ฐ€๋Š” ๋ถ€๋ถ„๋„ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์•„์š”. ๋งˆ๋ฌด๋ฆฌ ๋˜๋ฉด ๋ž˜ํ•‘์„ ํ•œ๋ฒˆ ํ•ด๋ด์•ผ๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿ˜ญ

์ด๋ฒˆ์—๋Š” Coordinator ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

View Controller๊ฐ„์˜ Flow๋ฅผ ์ œ์–ดํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋‘์–ด์„œ ๋ทฐ์ปจ๊ฐ„์˜ ์ „ํ™˜์„ ์•ˆ์ •์ ์ด๊ณ  ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ํŒจํ„ด์ธ๋ฐ์š”, ์‚ฌ์‹ค ์ œ ํ”„๋กœ์ ํŠธ์—๋Š” ๋‹น์žฅ ํ•„์š”์„ฑ์€ ๋ชป๋Š๋ผ๊ณ  ์žˆ์ง€๋งŒ ๋˜ ๋‹น์žฅ ์ ์šฉํ•ด๋ณผ์ˆ˜ ์žˆ๋Š” ๋ฐ ์•ˆํ•ด๋ณผ ์ˆ˜ ์—†์ง€ ์•Š์Šต๋‹ˆ๊นŒ? ใ…Žใ…Ž;;

๊ทธ๋ž˜์„œ ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์˜ค๋Š˜ ๋„์›€์„ ์–ป์€ ๊ฒŒ์‹œ๋ฌผ์€

Coordinator Pattern with Tab Bar Controller

์œ„ ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž…๋‹ˆ๋‹ค!

Coordinator Pattern

์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์ด๋ž€?

์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์ด๋ž€ ์œ„์—์„œ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ–ˆ๋“ฏ์ด

์•ฑ์˜ View Controller๋“ค ์‚ฌ์ด์˜ Flow Control๊ณผ Navigation์„ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์„œ ํ™”๋ฉด์˜ ํ๋ฆ„์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ณ , ๋˜ ๋ทฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ฝ”๋“œ์— ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋ถ€์—ฌํ•ด์ค€๋‹ค๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์›๋ž˜์˜ ๋ฐฉ์‹

์ž ์›๋ž˜๋ผ๋ฉด View Controller๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ์–ด๋–ค ์‹์œผ๋กœ ํ–ˆ๋‚˜์š”? ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๊ณ  ํ•œ๋‹ค๋ฉด

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mainViewController = storyboard.instantiateInitialViewController() as! MainViewController
self.navigationController.pushViewController(mainViewController, animated: true)

์œ„์ฒ˜๋Ÿผ ์•„์ฃผ์•„์ฃผ ๊ธด ์ฝ”๋“œ๋ฅผ ์ƒˆ๋กœ์šด ๋ทฐ์ปจ์„ ๋ถˆ๋Ÿฌ์˜ฌ๋•Œ๋งˆ๋‹ค ์ž…๋ ฅํ•˜๊ณ  ์‹คํ–‰ํ•ด์•ผ ํ–ˆ์ฃ ?

ํ™”๋ฉด์ด ๋งŽ์ง€ ์•Š์€ ์•ฑ์ด๊ฑฐ๋‚˜ ํ•˜๋‚˜์˜ ํ™”๋ฉด์—์„œ ํ•˜๋‚˜์˜ ํ™”๋ฉด์œผ๋กœ๋งŒ ๋„˜์–ด๊ฐ€๋Š” ๊ฒฝ์šฐ์—์•ผ ์ „ํ˜€ ๋ฌธ์ œ๊ฐ€ ๋  ๊ฒŒ ์—†์ง€๋งŒ, ํ™”๋ฉด์ด ์•„์ฃผ ๋งŽ๊ณ  ํ•œ ํ™”๋ฉด์—์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ™”๋ฉด์œผ๋กœ์˜ ์ „ํ™˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์œ„ ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๊ณ„์† ์ž…๋ ฅํ–ˆ์„ ๊ฑฐ์—์š”.

์ฝ”๋””๋„ค์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด?

import UIKit

// MARK: - Coordinator
protocol Coordinator: class {
  var finishDelegate: CoordinatorFinishDelegate? { get set }
  // Each coordinator has one navigation controller assigned to it.
  var navigationController: UINavigationController { get set }
  /// Array to keep tracking of all child coordinators. Most of the time this array will contain only one child coordinator
  var childCoordinators: [Coordinator] { get set }
  /// Defined flow type.
  var type: CoordinatorType { get }
  /// A place to put logic to start the flow.
  func start()
  /// A place to put logic to finish the flow, to clean all children coordinators, and to notify the parent that this coordinator is ready to be deallocated
  func finish()
  
  init(_ navigationController: UINavigationController)
}

extension Coordinator {
  func finish() {
    childCoordinators.removeAll()
    finishDelegate?.coordinatorDidFinish(childCoordinator: self)
  }
}

// MARK: - CoordinatorOutput
/// Delegate protocol helping parent Coordinator know when its child is ready to be finished.
protocol CoordinatorFinishDelegate: class {
  func coordinatorDidFinish(childCoordinator: Coordinator)
}

// MARK: - CoordinatorType
/// Using this structure we can define what type of flow we can use in-app.
enum CoordinatorType {
  case app, login, tab
}

๋„ค ์—„์ฒญ ๋ณต์žกํ•ด๋ณด์ด์ฃ ..? ๊ทธ๋ƒฅ ์›๋ž˜์˜ ๋ฐฉ์‹์„ ์“ฐ๊ณ  ์‹ถ์–ด์ง€์ฃ ..?

ํ•˜์ง€๋งŒ ํ•œ๋ฒˆ ๋ฐฐ์›Œ๋‘๋ฉด ๋ถ„๋ช… ์ž˜ ์จ๋จน์„๊ฑฐ๋ผ ๋ฏฟ๊ณ  ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๊ฑธ ์–ด๋–ป๊ฒŒ ์“ฐ์ฃ ?

์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์•ฑ์—๋Š” ์ตœ์†Œํ•œ ํ•˜๋‚˜์˜ ์ฝ”๋””๋„ค์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ๊ฐ€์žฅ ๋ฟŒ๋ฆฌ๊ฐ€ ๋˜๋Š” ์ฝ”๋””๋„ค์ดํ„ฐ๋ถ€ํ„ฐ ๋งŒ๋“ค๊ธฐ ์‹œ์ž‘ํ•˜๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ AppCoordinator๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ฝ”๋””๋„ค์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์šฐ์„  ๊ฐ€์žฅ ๋ฟŒ๋ฆฌ๊ฐ€ ๋˜๋Š” ์นœ๊ตฌ๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ณต์žกํ•ด๋ณด์ด์ง€๋งŒ ์•ž์— ๋งŒ๋“ค์—ˆ๋˜ ํ”„๋กœํ† ์ฝœ๋Œ€๋กœ ๋งŒ๋“  ๊ฒƒ ๋ฟ์ด์—์š”!

๊ทธ๋ฆฌ๊ณ  AppCoordinator๋ฅผ initํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ์ข‹์€ ๊ณณ์€ AppDelegate๋ผ๊ณ  ํ•˜๋„ค์š”

์ด AppDelegate๋ผ๋Š” ๋†ˆ๋„ ํ•œ๋ฒˆ ์ œ๋Œ€๋กœ ๊ณต๋ถ€ํ•ด๋ด์•ผํ•˜๋Š”๋ฐ ์žŠ๊ณ  ์žˆ์—‡๋„ค์š”. ์ด์ฐธ์— ๋ฆฌ์ŠคํŠธ์— ์˜ฌ๋ ค๋‘๊ณ .. ๊ทธ๋Ÿฐ๋ฐ AppDelegate๋ผ๋Š” ๋†ˆ์€ iOS13 ์ดํ›„๋ถ€ํ„ฐ SceneDelegate์™€ ๊ธฐ๋Šฅ์„ ๋‚˜๋ˆ„์–ด๊ฐ–๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ ์„ค๋ช…๋“œ๋ฆฌ๊ณ  ๋„˜์–ด๊ฐ€์ž๋ฉด ์•ฑ์˜ ์ง„์ž…์ง€์ ์„ ์ •ํ•ด์ฃผ๊ณ  ์‹คํ–‰์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•ด์ค€๋‹ค๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. (์‚ฌ์‹ค ์ €๋„ ์ž˜ ๋ชฐ๋ผ์š” ๐Ÿ˜“)

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
            
    window = UIWindow.init(frame: UIScreen.main.bounds)
    
    let navigationController: UINavigationController = .init()

    window?.rootViewController = navigationController
    window?.makeKeyAndVisible()
    
    appCoordinator = AppCoordinator.init(navigationController)
    appCoordinator?.start()
            
    return true
}

๊ทธ๋ž˜์„œ SceneDelegate์— ์•„๋ž˜ ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•ด์„œ ์•ฑ ์‹คํ–‰์‹œ์— ์„ค์ •ํ•ด์ค์‹œ๋‹ค!

์ด์ œ ์ด ์•ฑ์€ ์‹คํ–‰์‹œํ‚ค๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š” ๋น„์–ด์žˆ๋Š” Navigation Controller๋งŒ ๋„์šฐ๋Š” ์•ฑ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์‹ค์€ DefaultAppCoordinator์˜ start ํ•จ์ˆ˜๋ฅผ ์ฝœํ•˜์—ฌ ์‹คํ–‰์‹œํ‚ค๊ณ  ์žˆ์ฃ . ๋‹จ์ง€ ์•„์ง ๊ทธ ๋ถ€๋ถ„์„ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์„ ๋ฟ์ž…๋‹ˆ๋‹ค.

์šฐ์„  ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ ‡๊ฒŒ ์›ํ•˜๋Š” ViewController๋ฅผ pushํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ํ•ด๋‹น ๋ทฐ์ปจ์ด ์•ฑ ์‹คํ–‰ ์ดˆ๊ธฐํ™”๋ฉด์œผ๋กœ ๋“ฑ์žฅํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

TabBar + Coordinator ํŒจํ„ด

์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์„ ์กฐ๊ธˆ ์‘์šฉํ•˜๋ฉด TabBar์—๋„ ์ ์šฉ์„ ํ•  ์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ ๋‹จ์ˆœ ์ ์šฉ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ ์œ„์ฃผ๋กœ ์“ฑ์“ฑ ์ง€๋‚˜๊ฐˆ๊ฒŒ์š”!

๋‹ค๋ฅธ ๊ธฐ๋ณธ์ ์ธ ๋ถ€๋ถ„์€ TabBar ๊ตฌ์„ฑ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ๊ณณ์€ ๋ฐ”๋กœ ์—ฌ๊ธฐ!

Home์ด๋ผ๋Š” ํŽ˜์ด์ง€์—๋Š” Home Coordinator์˜ start()๋ฅผ callํ•ด์„œ ๊ตฌ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ด์ฃ ! ๋™์‹œ์— ์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์„ ์ ์šฉ์‹œํ‚ค๊ธฐ ์œ„ํ•ด finishDelegate๋„ ๋“ฑ๋กํ•˜๋ฉด์„œ childCoordinator์— ํ•ด๋‹น ์ฝ”๋””๋„ค์ดํ„ฐ๋ฅผ ๋“ฑ๋ก์‹œ์ผœ์ฃผ๋Š” ์ž‘์—…๊นŒ์ง€ ์ด๋ฃจ์–ด์กŒ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ์—” Home Coordinator ํŒŒ์ผ์„ ์‚ดํŽด๋ณผ๊นŒ์š”?

์ด์ „ ๊ฒŒ์‹œ๋ฌผ์˜ ์ฝ”๋””๋„ค์ดํ„ฐ์™€ ๋น„์Šทํ•œ ๊ตฌ์กฐ๋ฅผ ์ง€๋‹ˆ๊ณ  ์žˆ์ฃ . NavigationController๋ฅผ ๋ฐ›์•„์˜ค๊ณ , View Model๋„ ๋“ฑ๋กํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

let homeStoryboard = UIStoryboard(name: "Home", bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
self.homeViewController = homeVC

์ด ๋ถ€๋ถ„์€ ์ œ๊ฐ€ storyboard๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ๋กœ UI ๊ตฌ์„ฑ์„ ํ•˜์‹ค ๋ถ„๋“ค์€

self.homeVC = HomeViewController()

์ด๋ ‡๊ฒŒ๋งŒ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!