티스토리 뷰

Mobile/iOS

RIBs를 이용한 개발 - 4. NextPage

out of coding 2020. 10. 6. 10:57

이전 시간까지는 MainViewController를 화면에 표현하여 주고 Button Action을 받아올수 있도록 만들었습니다.

 

2020/10/04 - [Mobile/iOS] - RIBs를 이용한 개발 - 3. Main RIBs

 

RIBs를 이용한 개발 - 3. Main RIBs

이전까지 하게 되면 빌드는 되지만 검은 화면이 나오는 프로젝트를 만들게 될겁니다. 그러면 이제 하나씩 만들어 보도록 할게요. 이전글 2020/10/03 - [Mobile/iOS] - RIBs를 이용한 개발 - 2. RIBs Setting. RI

mrgamza.tistory.com

이번에는 만들어진 이것을 이용하여서 Click을 하게 되면 ViewController를 present하고 Component를 전달하도록 하겠습니다.

MainRIB를 고칩니다.

MainViewController

아래의 두가지 부분을 이렇게 고쳐줍시다.

// MARK: - ViewControllable

protocol MainViewControllable: ViewControllable {
    func present(_ viewController: ViewControllable, animated: Bool)
    func dismiss(_ viewController: ViewControllable, animated: Bool)
}
extension MainViewController: MainViewControllable {
    func present(_ viewController: ViewControllable, animated: Bool) {
        present(viewController.uiviewController, animated: animated)
    }
    
    func dismiss(_ viewController: ViewControllable, animated: Bool) {
        guard !viewController.uiviewController.isBeingDismissed else { return }
        viewController.uiviewController.dismiss(animated: animated)
    }
}

MainInteractor

이곳은 이전글에서 만든 function 이름이 마음에 안들어서 수정한 부분도 있으니 이것도 수정합니다.

// MARK: - Listener

protocol MainListener: class {
    
}

// MARK: - Routing

protocol MainRouting: ViewableRouting {
    func attachNext()
    func detachNext()
}
// MARK: - Interactor

final class MainInteractor: PresentableInteractor<MainPresentable>,
                            MainInteractable {
    private let buttonTextRelay: BehaviorRelay<String>
    
    var router: MainRouting?
    var listener: MainListener?
  
    init(title: String,
         presenter: MainPresentable) {
        buttonTextRelay = .init(value: title)
        super.init(presenter: presenter)
        presenter.handler = self
    }
    
    override func didBecomeActive() {
        super.didBecomeActive()
        setup()
    }
    
    private func setup() {
        guard let action = presenter.action else { return }
        action.didClickButton
            .bind { [weak self] in
                self?.router?.attachNext()
            }
            .disposeOnDeactivate(interactor: self)
    }
}

extension MainInteractor: MainPresentableHandler {
    var buttonText: Observable<String> {
        return buttonTextRelay.asObservable()
    }
}

extension MainInteractor: MainListener {
    func detachNext() {
        router?.detachNext()
    }
}

MainRouter

라우터도 수정을 하여 주어야 하는데요.

NextBuilder를 가지고 사용하는 방법으로 만들어 줍니다.

import RIBs

final class MainRouter: LaunchRouter<MainInteractable, MainViewControllable> {
    
    private let nextBuilder: NextBuilable
    private var nextRouter: NextRouting?
    
    init(nextBuilder: NextBuilable,
         interactor: MainInteractable,
         viewController: MainViewControllable) {
        self.nextBuilder = nextBuilder
        self.nextRouter = nil
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
}

extension MainRouter: MainRouting {
    func attachNext() {
        let time = "Now is \(Date().description)"
        let router = nextBuilder.build(with: interactor, title: time)
        nextRouter = router
        attachChild(router)
        viewController.present(router.viewControllable, animated: true)
    }
    
    func detachNext() {
        guard let router = nextRouter else { return }
        viewController.dismiss(router.viewControllable, animated: true)
        nextRouter = nil
        detachChild(router)
    }
}

MainBuilder

이곳에서 Builder를 이용하여서 Router를 만들어 주도록 할게요

import RIBs

// MARK: - Component

protocol MainDependency: Dependency {}

final class MainComponent: Component<MainDependency>, NextDependency {
    var title: String = ""
}

// MARK: - Builder

protocol MainBuilable: Buildable {
    func build() -> LaunchRouting
}

final class MainBuilder: Builder<MainDependency>, MainBuilable {

    func build() -> LaunchRouting {
        let component = MainComponent(dependency: dependency)
        let viewController = MainViewController()
        let interactor = MainInteractor(title: "Hello, RIBs\nClick Button!",
                                        presenter: viewController)
        
        let nextBuilder = NextBuilder(dependency: component)
        
        return MainRouter(nextBuilder: nextBuilder,
                          interactor: interactor,
                          viewController: viewController)
    }
}

Group을 만들어 줍시다.

저는 NextRIB를 만들었는데 자신의 상황에 맞도록 만들어 주세요.

NextViewController

이전의 MainViewController와 거의 흡사합니다.

import RIBs
import SnapKit
import RxSwift
import RxCocoa

// MARK: - Presenter

protocol NextPresentableAction: class {
    var didClickButton: Observable<Void> { get }
}

protocol NextPresentableHandler: class {
    var timeText: Observable<String> { get }
}

protocol NextPresentable: Presentable {
    var action: NextPresentableAction? { get set }
    var handler: NextPresentableHandler? { get set }
}

// MARK: - ViewControllable

protocol NextViewControllable: ViewControllable {
    
}

// MARK: - ViewController

final class NextViewController: UIViewController, NextPresentable {
    
    // MARK: - UI Components
    
    private lazy var button: UIButton = {
        let button = UIButton(type: .system)
        button.setTitleColor(.blue, for: .normal)
        button.titleLabel?.numberOfLines = 1
        button.titleLabel?.textAlignment = .center
        button.setTitle("Close", for: .normal)
        return button
    }()
    
    private lazy var label: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.numberOfLines = 0
        return label
    }()
    
    // MARK: - Properties
    
    private let disposeBag = DisposeBag()
    
    // MARK: - Presentable
    
    var action: NextPresentableAction?
    var handler: NextPresentableHandler?
    
    init() {
        super.init(nibName: nil, bundle: nil)
        action = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
        bindPresentable()
    }
    
    private func setupView() {
        view.backgroundColor = .white
        view.addSubview(button)
        button.snp.makeConstraints { maker in
            maker.center.equalToSuperview()
        }
        
        view.addSubview(label)
        label.snp.makeConstraints { maker in
            maker.centerX.equalToSuperview()
            maker.bottom.equalTo(button.snp.top).offset(-30)
        }
    }
    
    private func bindPresentable() {
        guard let handler = handler else { return }
        handler.timeText
            .bind(to: label.rx.text)
            .disposed(by: disposeBag)
    }
}

extension NextViewController: NextPresentableAction {
    var didClickButton: Observable<Void> {
        return button.rx.tap.asObservable()
    }
}

extension NextViewController: NextViewControllable {
    
}

NextInteractor

Main과 다른점은 listener를 통해서 detachNext를 호출하는 것이 다릅니다.

import RIBs
import RxSwift
import RxCocoa

// MARK: - Listener

protocol NextListener: class {
    func detachNext()
}

// MARK: - Routing

protocol NextRouting: ViewableRouting {
    
}

// MARK: - Interactable

protocol NextInteractable: Interactable {
    var router: NextRouting? { get set }
    var listener: NextListener? { get set }
}

// MARK: - Interactor

final class NextInteractor: PresentableInteractor<NextPresentable>,
                            NextInteractable {
    private let timeTextRelay: BehaviorRelay<String>
    
    var router: NextRouting?
    var listener: NextListener?
  
    init(component: NextComponent,
         presenter: NextPresentable) {
        timeTextRelay = .init(value: component.dependency.title)
        super.init(presenter: presenter)
        presenter.handler = self
    }
    
    override func didBecomeActive() {
        super.didBecomeActive()
        setup()
    }
    
    private func setup() {
        presenter.action?.didClickButton
            .bind { [weak self] in
                self?.listener?.detachNext()
            }
            .disposeOnDeactivate(interactor: self)
    }
}

extension NextInteractor: NextPresentableHandler {
    var timeText: Observable<String> {
        return timeTextRelay.asObservable()
    }
}

NextRouter

다음 화면 정의같은 부분들이 없어서 별다른 동작이 없습니다.

import RIBs

final class NextRouter: ViewableRouter<NextInteractable, NextViewControllable> {
    override init(interactor: NextInteractable,
                  viewController: NextViewControllable) {
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
}

extension NextRouter: NextRouting {
    
}

NextBuilder

import RIBs

// MARK: - Component

protocol NextDependency: Dependency {
    var title: String { get set }
}

final class NextComponent: Component<NextDependency> {
    
}

// MARK: - Builder

protocol NextBuilable: Buildable {
    func build(with listener: NextListener, title: String) -> NextRouting
}

final class NextBuilder: Builder<NextDependency>, NextBuilable {

    func build(with listener: NextListener, title: String) -> NextRouting {
        dependency.title = title
        let component = NextComponent(dependency: dependency)
        let viewController = NextViewController()
        let interactor = NextInteractor(component: component,
                                        presenter: viewController)
        interactor.listener = listener
        
        return NextRouter(interactor: interactor, viewController: viewController)
    }
}

다른분들과 특이하게 만들어진 부분은 NextBuilder를 build할때 title을 받아서 이전 MainRIB에서 전달하는 값을 dependency로 전달하여서 사용하도록 하였습니다.

종합?

저는 MVVM을 하기 때문에 다소 불편한 부분이 많습니다.

결론은 저는 아마도 쓰지 않을거 같긴 합니다만

하지만 이정도만 알아둬도 나중에 이 패턴을 이용하여서 더 좋은 패턴을 만들수 있지 않을까요?

Dependency 부분을 어떻게 해야지 주입하기 좋을까에 대한 부분은 조금 생각을 해봐야할거 같습니다.

protocol에 set까지 같이 주면 외부에서 값이 변경이 되기 때문에 좋지 않은것 같기 때문이죠.

 

소스는 git에 올려두었습니다.

github.com/outofcode-example/iOS-RIBsExample/tree/NextPage

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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 31
글 보관함