RIBs - 프로젝트 구성편 - 2, 간단 로그인 뷰 연결

김재형_LittleTale·2025년 9월 22일
1

RIBs

목록 보기
3/4
post-thumbnail

들어가기에 앞서

꼭 1편과 2편을 보고 글을 읽어주시면 좋겠습니다.

Login RIBs 세팅

이전 시간에 배운 방법으로 똑같이 템플릿을 통해서
Login Ribs를 세팅해주시면 됩니다.
View가 존재하는 Rib이기 때문에 첫번째를 체크하고 진행 하셔야 합니다.

UI 세팅

빠른 진행을 위해 간단하게 설계 했습니다.

import UIKit

final class LoginView: UIView {
    // 시작 버튼
    let button = UIButton().after { // after 는 Then 라이브러리 같이 설계되어 있습니다.
        var config = UIButton.Configuration.filled()
        config.title = "로그인"
        config.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 20, bottom: 12, trailing: 20)
        $0.configuration = config
        $0.translatesAutoresizingMaskIntoConstraints = false
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: UI Layer
extension LoginView {
    
    private func setUI() {
        self.addSubview(button)
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
        ])
    }
}

UI Event 연결해보기

UI 를 구성했으니 위와같은 버튼을 Interactor 에게 알려주어야 하겠죠?
RIBs 내부에 RxSwift가 있어서 RxSwift 를 사용해 보겠습니다.

Listener Setting

protocol LoginPresentableListener: AnyObject {
    // TODO: Declare properties and methods that the view controller can invoke to perform
    // business logic, such as signIn(). This protocol is implemented by the corresponding
    // interactor class.
    
  	// 로그인 버튼 클릭
    func touchToLogin(name: String)
}

ViewController Setting

final class LoginViewController: UIViewController, LoginPresentable, LoginViewControllable {
    
    private let baseView = LoginView()
    
    weak var listener: LoginPresentableListener?
    
    override func loadView() {
        super.loadView()
        self.view = baseView
        subscribe()
    }
}

extension LoginViewController {
    
    private func subscribe() {
        baseView.button.rx.tap
            .bind(with: self) { owner, _ in
                owner.listener?.touchToLogin(name: "테스트를 위함")
            }
            .disposed(by: rx.disposeBag)
    }
}

Interactor Setting

자 프로토콜을 통해 저희는 저런 이벤트를 구성하겠다고 약속을 한거죠?
그것을 구현해야 합니다.

Interactor Listener Setting

로그인이 완료 되면 상위에게 뷰를 옮겨달라 라는 메시지를 전달하고자 합니다.
일단은요. -> 다음편에서는 구조가 바뀔 예정입니다.

protocol LoginListener: AnyObject {
    // TODO: Declare methods the interactor can invoke to communicate with other RIBs.
    
    func didLogin(name: String)
}

final class LoginInteractor: PresentableInteractor<LoginPresentable>, LoginInteractable, LoginPresentableListener {
    
    weak var router: LoginRouting?
    weak var listener: LoginListener?

    // TODO: Add additional dependencies to constructor. Do not perform any logic
    // in constructor.
    override init(presenter: LoginPresentable) {
        super.init(presenter: presenter)
        presenter.listener = self
    }

    override func didBecomeActive() {
        super.didBecomeActive()
        // TODO: Implement business logic here.
    }

    override func willResignActive() {
        super.willResignActive()
        // TODO: Pause any business logic.
    }
    
    // 로그인 버튼 클릭시
    func touchToLogin(name: String) {
        let name = name
        // 비즈니스 로직 거친후 (생략) -> Root 에게 Home으로 가라고 명령
        listener?.didLogin(name: name)
    }
}

LoginBuilder Setting

자 이제 구성한 이벤트들이나, 의존성등을 구성하는 친구를 구현을 해야합니다.
그래야 Init() 을 해서 사용할 수 있겠죠?

protocol LoginBuildable: Buildable {
    func build(withListener listener: LoginListener) -> LoginRouting
}

final class LoginBuilder: Builder<LoginDependency>, LoginBuildable {

    override init(dependency: LoginDependency) {
        super.init(dependency: dependency)
    }

    func build(withListener listener: LoginListener) -> LoginRouting {
        let component = LoginComponent(dependency: dependency)
        let viewController = LoginViewController()
        let interactor = LoginInteractor(presenter: viewController)
        interactor.listener = listener
        return LoginRouter(interactor: interactor, viewController: viewController)
    }
}

Root에서 Login 이벤트 감지하기

자 위에서 말했듯이 상위에게 이벤트를 전달하고자
Listener를 세팅했죠?
상위가 바로 루트이기 때문에 다음과 같이 이벤트를 감지 하고자 합니다.

protocol RootInteractable: Interactable, LoginListener { // <- 이부분
    var router: RootRouting? { get set }
    var listener: RootListener? { get set }
}
final class RootInteractor: PresentableInteractor<RootPresentable>, RootInteractable, RootPresentableListener {

    weak var router: RootRouting?
    weak var listener: RootListener?

    // TODO: Add additional dependencies to constructor. Do not perform any logic
    // in constructor.
    override init(presenter: RootPresentable) {
        super.init(presenter: presenter)
        presenter.listener = self
    }

   ...
    
    func didLogin(name: String) { // <- 이부분
        router?.routToHome(name: name)
    }

}

Root Route Setting

protocol RootRouting: ViewableRouting {
    // TODO: Declare methods the interactor can invoke to manage sub-tree via the router.
    func routToLogin()
}
final class RootRouter: LaunchRouter<RootInteractable, RootViewControllable>, RootRouting {
   
    private let loginBuilder: LoginBuildable
    private let homeBuilder: HomeBuildable
    
    private var currentChild: ViewableRouting?
    private var loginRouter: LoginRouting?
    private var homeRouter: HomeRouting?

    init(interactor: RootInteractable,
         viewController: RootViewControllable,
         loginBuilder: LoginBuildable,
         homeBuilder: HomeBuildable) {
        self.loginBuilder = loginBuilder
        self.homeBuilder = homeBuilder
        super.init(interactor: interactor, viewController: viewController)
        interactor.router = self
    }
    
    override func didLoad() {
        super.didLoad()
        
        routToLogin()
    }

    func routToLogin() {
        if let child = currentChild {
            detachChild(child)
        }
        
        let login = loginBuilder.build(withListener: interactor)
        
        attachChild(login)
        currentChild = login
        loginRouter = login
        
        viewController.setRoot(login.viewControllable, animated: true)
    }
    
    func routToHome(name: String) { // 다음 시간에 
        if let child = currentChild {
            detachChild(child)
        }
        
        let home = homeBuilder.build(withListener: interactor, name: name)
        
        attachChild(home)
        currentChild = home
        homeRouter = home
        
        viewController.setRoot(home.viewControllable, animated: true)
    }
}

Root ViewController 수정

final class RootViewController: UIViewController, RootPresentable, RootViewControllable {
    
    private let container = UIView()
    private weak var current: UINavigationController?
    
    weak var listener: RootPresentableListener?
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        super.loadView()
        view = container
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
    
    func setRoot(_ vc: ViewControllable, animated: Bool) {
        let rootVC = vc.uiviewController
        let nav = UINavigationController()
        nav.setViewControllers([rootVC], animated: false) // 초기 세팅은 false 권장
        
        // 기존 제거
        if let current {
            current.willMove(toParent: nil)
            current.view.removeFromSuperview()
            current.removeFromParent()
        }
        
        addChild(nav)
        container.addSubview(nav.view)
        nav.view.frame = container.bounds
        nav.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        nav.didMove(toParent: self)
        current = nav
        
        // 페이드 인
        if animated {
            container.alpha = 0
            UIView.animate(withDuration: 0.2) { [weak self] in
                self?.container.alpha = 1
            }
        }
    }
}

마치면서

이번편은 자식 RIBs가 하나 생겨서 연결하는 작업을 했습니다.
사실 Home뷰도 만들어서 왔다갔다 하는거 까지를 이번편으로 하려 했는데
사실 Home같은 경우는 Home 안에서도 뷰가 많겠죠? 이동하는 뷰들이

그래서 HomeFlow 라고 해서 View가 없는 RIB을 생성해서 Home의 뷰들을 통제하는
중계소? 같은 것으로 구성을 또 하게 되서 루즈해지니
Login만 설명을 했구요 다음편은 HomeView 빠르게 뷰와 라우터 만 설명을 한후
FlowRIB 설명을 끝으로 RIBs 편을 마무리 할까 고민중입니다.

궁금하신 사항 있으시면 꼭 말씀 부탁드립니다.
감사합니다.

github

Little-Tale/HowRibs

profile
IOS 개발자 새싹이, 작은 이야기로부터

0개의 댓글