| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- 비동기(Async)
- Rxcocoa
- ios
- WeatherAPP
- Dispatch Queue
- MVVM
- Transforming Operators
- 동기(Sync)
- Library
- MapKit
- pagination
- Traits
- RxSwift
- 의존성 관리 도구
- SPM
- IAMPopup
- 직렬(Serial)
- NSCache
- Control Event
- Segmented Control
- Multiple Cell Type
- cocoapods
- NewsApp
- flatMap
- popupView
- 라이선스 저작권
- Swift Package Manager
- OpenSource
- 동시(Concurrent)
- swift
Archives
- Today
- Total
IAM iOS
[RxSwift] MVVM-C with Building Memo App (2) Scene Coordinator 본문
해당 포스팅은 KxCoding RxSwift 강의를 참고한 포스팅입니다!
https://www.youtube.com/watch?v=m41N4czHGF4&list=PLziSvys01Oek7ANk4rzOYobnUU_FTu5ns&index=2
Scene 구성
ViewModelBindableType
Protocol
- MVVM 패턴으로 구현할 때는 ViewModel을 ViewController의 속성으로 추가
- ViewModel과 View를 Binding
- ViewModel의 타입은 ViewController마다 달라지기 때문에 Generic Protocol로 선언
- Protocol은 associatedtype 키워드를 사용해서 관련 타입(associatedtype)을 선언
- 관련 타입(associatedtype)은 제네릭 매개변수 절에 있는 타입 매개변수와 유사하다.
- Protocol 선언 부의 where 절에서 제네릭을 사용할 수 있다.
protocol ViewModelBindableType {
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
func bindViewModel()
}
extension
- ViewController에 추가된 ViewModel의 실제 속성을 저장
- bindViewModel 메서드를 자동으로 추가하는 메서드 구현
where Self
- associatedtype의 사용 시 재정의가 필요 없도록 where절에 준수해야 할 프로토콜 제약을 명시해서 associatedtype에 대한 제약을 주도록 할 수 있다.
- 해당 Protocol의 extension을 특정 Protocol을 상속했을 때만 사용될 수 있도록 하는 제약조건 추가 기능
- 즉, 해당 extension에서 만든 메서드와 프로퍼티는 UIViewController를 상속받지 않은 곳에서는 사용할 수가 없다.
extension ViewModelBindableType where Self: UIViewController {
mutating func bind(viewModel: Self.ViewModelType) {
self.viewModel = viewModel
loadViewIfNeeded()
bindViewModel()
}
}
MemoListViewController
- Memo List, Detail, Compose → ViewController, ViewModel 생성
- ViewModelBindableType 프로토콜 채택
import UIKit
class MemoListViewController: UIViewController, ViewModelBindableType {
var viewModel: MemoListViewModel!
override func viewDidLoad() {
super.viewDidLoad()
}
func bindViewModel() { /// ViewModelBindableType
}
}
Scene Coordinator
TransitionModel
/// 전환 방식 표현
enum TransitionStyle {
case root
case push
case modal
}
/// 전환 시 발생할 에러 타입
enum TransitionError: Error {
case navigationControllerMissing
case cannotPop
case unknown
}
Scene
- 앱에서 구현할 Scene
- Scene과 연관된 ViewModel을 연관 값으로 저장
enum Scene {
case list(MemoListViewModel)
case detail(MemoDetailViewModel)
case compose(MemoComposeViewModel)
}
/// 스토리보드에 있는 Scene을 생성
extension Scene {
func instantiate(from storyboard: String = "Main") -> UIViewController {
let storyboard = UIStoryboard(name: storyboard, bundle: nil)
/// 연관값에 저장된 ViewModel을 Binding해서 return
switch self {
case .list(let viewModel):
guard let nav = storyboard.instantiateViewController(
withIdentifier: "ListNav"
) as? UINavigationController else { fatalError() }
guard var listVC = nav.viewControllers.first as? MemoListViewController
else { fatalError() }
/// 실제 Scene과 ViewModel을 Binding하고 NavigationController를 return
listVC.bind(viewModel: viewModel)
return nav
case .detail(let viewModel):
guard var detailVC = storyboard.instantiateViewController(
withIdentifier: "DetailVC"
) as? MemoDetailViewController else { fatalError() }
detailVC.bind(viewModel: viewModel)
return detailVC
case .compose(let viewModel):
guard let nav = storyboard.instantiateViewController(
withIdentifier: "ComposeNav"
) as? UINavigationController else { fatalError() }
guard var composeVC = nav.viewControllers.first as? MemoComposeViewController
else { fatalError() }
composeVC.bind(viewModel: viewModel)
return nav
}
}
}
SceneCoordinatorType
- SceneCoordinator가 공통적으로 구현해야 하는 멤버 선언
Completable(Traits)
- 구독자를 추가하고, 화면 전환이 완료된 후에 원하는 작업을 구현할 수 있다.
- 이런 작업이 필요없다면 사용하지 않아도 된다.
- @discardableResult를 선언했기 때문에 경고는 표시되지 않는다.
import RxSwift
protocol SceneCoordinatorType {
/// 새로운 Scene을 표시
@discardableResult
func transition(
to scene: Scene,
using style: TransitionStyle,
animated: Bool
) -> Completable
/// 현재 Scene을 닫고 전 Scene으로 돌아가기
@discardableResult
func close(animated: Bool) -> Completable
}
SceneCoordinator
- SceneCoordinatorType 프로토콜을 채택
- SceneCoordinator는 화면 전환을 담당하기 때문에 window 인스턴스와 현재 화면에 표시되어 있는 Scene(currentVC)을 가지고 있어야 한다.
import RxSwift
import RxCocoa
class SceneCoordinator: SceneCoordinatorType {
private let disposeBag = DisposeBag()
/// SceneCoordinator는 화면 전환을 담당하기 때문에 window 인스턴스와
/// 현재 화면에 표시되어 있는 Scene을 가지고 있어야한다.
private var window: UIWindow
private var currentVC: UIViewController
required init(window: UIWindow) {
self.window = window
currentVC = window.rootViewController!
}
@discardableResult
func transition(
to scene: Scene,
using style: TransitionStyle,
animated: Bool
) -> Completable {
/// 전환 결과를 방출할 subject
let subject = PublishSubject<Void>()
/// Scene을 생성해서 상수에 저장
let target = scene.instantiate()
/// TransitionStyle에 따라 실제 전환 처리
switch style {
case .root:
/// rootViewController를 바꿔주면 된다.
currentVC = target
window.rootViewController = target
/// subject로 completed 이벤트 전달
subject.onCompleted()
case .push:
/// navigationController에 임베드되어있을 때만
/// 아니라면 error이벤트를 전달하고 중지
guard let nav = currentVC.navigationController else {
subject.onError(TransitionError.navigationControllerMissing)
break
}
/// navigationController에 임베드되어있다면
/// Scene을 push하고 completed 이벤트 전달
nav.pushViewController(target, animated: animated)
currentVC = target
subject.onCompleted()
case .modal:
currentVC.present(target, animated: animated) {
subject.onCompleted()
}
currentVC = target
}
return subject.ignoreElements().asCompletable()
}
@discardableResult
func close(animated: Bool) -> Completable {
return Completable.create { [unowned self] completable in
if let presentingVC = self.currentVC.presentingViewController {
self.currentVC.dismiss(animated: animated) {
self.currentVC = presentingVC
completable(.completed)
}
} else if let nav = self.currentVC.navigationController {
guard nav.popViewController(animated: animated) != nil else {
completable(.error(TransitionError.cannotPop))
return Disposables.create()
}
self.currentVC = nav.viewControllers.last!
completable(.completed)
} else {
completable(.error(TransitionError.unknown))
}
return Disposables.create()
}
}
}
GitHub - camosss/RxSwift: RxSwift 공부한 내용 정리
RxSwift 공부한 내용 정리. Contribute to camosss/RxSwift development by creating an account on GitHub.
github.com
[RxSwift] MVVM-C with Building Memo App (3) 메모 목록 구현
해당 포스팅은 KxCoding RxSwift 강의를 참고한 포스팅입니다! https://www.youtube.com/watch?v=m41N4czHGF4&list=PLziSvys01Oek7ANk4rzOYobnUU_FTu5ns&index=2 #4. 메모 목록 구현 CommonViewModel ViewModel 의..
llan.tistory.com
'RxSwift' 카테고리의 다른 글
| [RxSwift] MVVM-C with Building Memo App (4) 메모 쓰기 구현 (0) | 2022.05.12 |
|---|---|
| [RxSwift] MVVM-C with Building Memo App (3) 메모 목록 구현 (0) | 2022.05.12 |
| [RxSwift] MVVM-C with Building Memo App (1) MVVM-C(Clean Architecture), Model, 메모리 저장소(Memory Storage) (0) | 2022.05.12 |
| [RxSwift] MVVM with RxSwift (0) | 2022.04.09 |
| [RxSwift] Building Weather App Using RxCocoa (0) | 2022.04.09 |