일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Refactoring
- 리펙터링
- Protocol
- swift documentation
- HIG
- ribs
- UICollectionView
- SWIFT
- uiscrollview
- collectionview
- Observable
- Human interface guide
- Clean Code
- Xcode
- UITextView
- rxswift
- 스위프트
- ios
- clean architecture
- MVVM
- 리펙토링
- map
- RxCocoa
- uitableview
- combine
- 애니메이션
- tableView
- 클린 코드
- swiftUI
- 리팩토링
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[Architecture] Coordinator pattern, XCoordinator 프레임워크 [응용], PostTaskManager 본문
[Architecture] Coordinator pattern, XCoordinator 프레임워크 [응용], PostTaskManager
jake-kim 2020. 10. 17. 22:42* XCoordinator를 이용하여 모든 화면에서 NavigationController하나를 공유하며 화면전환 하는 방법
+ 딥링크를 고려한 코드 스타일 적용
Xcoordinator개념편은 여기 참고
사전 지식
- XCoordinator에서는 strongRouter와 UnownedRouter가 있는데, 자식들을 계속 참조하며 잃지 않으려면 strongRouter로 사용
단, push와 같이 Transition을 반환하게 되면, strongRouter이후에 unownedRouter로 전달해도 참조를 잃지 않음
- Coordinator에서 다른 Coordinator로 이동 시키려면, addChild(_:) -> Transition함수를 정의하여 이 값을 리턴해야지만 deeplink가 가능한 구조
addChild(_:) -> Transition추가
import XCoordinator
extension Transition {
static func addChild(_ child: Presentable) -> Transition {
Transition(presentables: [child], animationInUse: nil) { _, _, completion in
completion?()
}
}
}
Start화면
- coordinator를 통해서 이동: case .next부분
- NavigationController를 공유해서 쓰기 위해서 init의 인수에 rootViewController로 삽입
(rootViewController를 init하지 않으면, 새로운 NavigationController가 생기기 때문에, 다음 push에서 중복 navigat9onController삽입으로 오류 발생)
//
// StartCoordinator.swift
// test
//
// Created by 김종권 on 2020/10/17.
// Copyright © 2020 jongkwon kim. All rights reserved.
//
import Foundation
import XCoordinator
indirect enum StartRoute: Route {
case start
case next
case back
case popAndBack(StartRoute)
}
class StartCoordinator: NavigationCoordinator<StartRoute> {
init(initialRoute: StartRoute) {
super.init(initialRoute: nil) // nil을 하지 않으면, animation이 디폴트로 적용됨 (switch문에 있는 push와 같은 애니메이션을 사용하려면 아래와 같이 trigger로 따로 불러야함)
trigger(initialRoute)
}
override func prepareTransition(for route: StartRoute) -> NavigationTransition {
rootViewController.setNavigationBarHidden(true, animated: false)
switch route {
case .start:
// start View Controller 초기화하여 이동
return .none()
case .next:
// Coordinator를 통해서 이동, 단 현재의 rootVC인 navigationController를 공유하기 위해서 인수로 rootVC도 넘김
let nextCoordinator = NextCoordinator(rootViewController: rootViewController, initialRoute: .next)
return .addChild(nextCoordinator)
case .back:
return .pop()
case .popAndBack(let startRoute):
trigger(.back)
trigger(startRoute)
return .none()
}
}
}
(NextCoordinator코드)
//
// NextCoordinator.swift
// test
//
// Created by 김종권 on 2020/10/17.
// Copyright © 2020 jongkwon kim. All rights reserved.
//
import Foundation
import XCoordinator
indirect enum NextRoute: Route {
case next
}
class NextCoordinator: NavigationCoordinator<NextRoute> {
init(rootViewController: rootViewController, initialRoute: NextRoute) {
super.init(rootViewController: rootViewController, initialRoute: nil)
trigger(initialRoute)
}
override func prepareTransition(for route: NextRoute) -> NavigationTransition {
switch route {
case .next:
// Next View Controller 초기화하여 이동
return .none()
}
}
}
BaseNavigationCoordinator 사용방법
- NavigationCoordinator를 상속받아서, 공통처리 로직을 위해 생성 (back, popAndPush, popTwice등)
//
// BaseNavigationCoordinator.swift
// test
//
// Created by 김종권 on 2020/11/03.
//
import Foundation
import UIKit
import XCoordinator
class BaseNavigationCoordinator<T: Route>: NavigationCoordinator<T> {
func back() -> NavigationTransition {
if rootViewController.presentedViewController != nil {
rootViewController.presentedViewController?.dismiss(animated: true)
} else {
rootViewController.popViewController(animated: true)
}
return .none()
}
func popAndPush(route: T) -> NavigationTransition {
trigger(route)
let count = rootViewController.viewControllers.count
if count > 2 {
rootViewController.viewControllers.remove(at: count - 2)
}
return .none()
}
func popTwice() -> NavigationTransition {
let countVC = rootViewController.viewControllers.count
if countVC > 2 {
rootViewController.viewControllers.remove(at: countVC - 1)
rootViewController.popViewController(animated: true)
} else if countVC == 1 {
rootViewController.popViewController(animated: true)
}
return .none()
}
func dismissAndPush(route: T) -> NavigationTransition {
if rootViewController.presentedViewController != nil {
rootViewController.presentedViewController?.dismiss(animated: false)
trigger(route)
}
return .none()
}
}
- 사용예제: BaseNaviagtionCoordinator를 상속 받고, 공통처리 로직들은 위에서 정의한 함수를 반환
//
// HomeCoordinator.swift
// test
//
// Created by 김종권 on 2020/10/08.
//
import Foundation
import SideMenu
import Domain
import XCoordinator
enum HomeRoute: Route {
case home
case menu(HomeVC)
case back
}
class HomeCoordinator: BaseNavigationCoordinator<HomeRoute> {
let postTaskManager: PostTaskManager
init(rootViewController: RootViewController, postTaskManager: PostTaskManager, initialRoute: HomeRoute) {
self.postTaskManager = postTaskManager
super.init(rootViewController: rootViewController, initialRoute: nil)
trigger(initialRoute)
}
override func prepareTransition(for route: HomeRoute) -> NavigationTransition {
switch route {
case .home:
let destinationPickerCoordinator = DestinationPickerCoodinator(
rootViewController: rootViewController,
postTaskManager: postTaskManager
)
let vc = HomeBuilder.build(
router: strongRouter,
postTaskManager: postTaskManager,
destinationPickerCoordinator: destinationPickerCoordinator
)
return .set([vc])
case let .menu(homeVC):
let sideMenuCoordinator = SideMenuCoordinator(
rootViewController: rootViewController,
postTaskManager: postTaskManager,
initialRoute: .sideMenu(homeVC)
)
return .addChild(sideMenuCoordinator)
case .back:
return back()
}
}
}
* PostTaskManager란 화면간의 이동전에 특정 화면에서 무슨일을 시키고자 할 때 task를 다른 화면에서 등록하고, 해당화면으로 돌아온 경우 처리할 수 있도록 하는 기능
import Foundation
import UIKit
enum PostTaskType {
case showToast(message: String)
}
enum PostTaskTarget {
case home
}
struct PostTask {
let target: PostTaskTarget
let task: PostTaskType
}
class PostTaskManager {
private var postTasks = [PostTaskTarget: [PostTaskType]]()
func register(postTask: PostTask) {
guard postTasks[postTask.target] != nil else {
postTasks[postTask.target] = [postTask.task]
return
}
postTasks[postTask.target]?.append(postTask.task)
}
func postTasks(postTastTarget: PostTaskTarget) -> [PostTaskType]? {
if isExist(taskTarget: postTastTarget) {
return postTasks[postTastTarget]
} else {
return nil
}
}
func removeAll() {
postTasks.removeAll()
}
func remove(for input: PostTaskTarget) {
postTasks[input]?.removeAll()
}
private func isExist(taskTarget: PostTaskTarget) -> Bool {
return !(postTasks[taskTarget]?.isEmpty ?? true)
}
}
NavigationController를 공유해서 사용할 때, Deeplink사용에 유의
Coordinator에서 넘어온 곳에서 처음에(NextCoordinator에서 .next경우) storngRouter로 초기화 해야함
'Architecture (swift)' 카테고리의 다른 글
[iOS - swift] Mixin 패턴(mix-in), Traits 패턴 (0) | 2021.12.17 |
---|---|
[iOS - swift] Clean swift (VIP) 패턴 (기본 개념) (0) | 2021.02.13 |
[Architecture] Coordinator pattern, XCoordinator 프레임워크 [개념] (0) | 2020.10.01 |
[Architecture] RxSwift, MVVM 구조 코드 (2) | 2020.09.26 |
[Clean Architecture] 4단계의 layer로 구성된 앱의 구조 (0) | 2020.09.19 |