ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RIBs) tutorial1
    iOS 2021. 6. 25. 23:58
    728x90
    uber 공식 튜토리얼을 따라가면서 제 나름대로 정리한 내용입니다. 
    보다 정확한 내용은 원문을 참고해주시고 오류나 수정사항이 있으면 알려주시면 감사하겠습니다. 🙇🏻‍♂️
    전체 코드를 보시려면 여기로 ;)

     

    목표

    간단한 이 튜토리얼들은 간단한 tictactoe 게임을 만들어보는 것입니다.

    튜토리얼을 따라가보면서 RIB 에 대해 이해하고 서로가 어떻게 상호작용하는지 알아봅니다 :)

     

    Project 구조

    먼저 튜토리얼 템플릿을 다운받습니다.

     

    기본 템플릿에는 Root, LoggedOut 2개의 RIB 폴더가 만들어져 있네요.

    제일 먼저 AppDelegate 에서 RIB을 생성해주면서 시작되는데요, Root RIB은 이미 만들어져 있고, LoggedOut은 DELETE_ME 라는 파일만 있습니다. 원하는 대로 DELETE_ME를 제거하고 LoggedOut RIB을 만들어 봅시다.

    LoggedOut RIB 생성

    기본 템플릿에는 이미 tooling이 설치되어 있어서 새 파일 만들기(cmd+N)를 보면 RIB 파일을 확인할 수 있으실거에요. 눌러서 LoggedOut RIB을 만들어줍니다.

    RIB은 뷰를 가질 수도 있고 가지고 있지 않을 수도 있는데요,

    저렇게 Owns corresponding view 를 체크해서 생성하면 View (ViewController) 를 가진 RIB 파일들이 생성됩니다.

    생성된 코드 이해하기

    무슨 파일들이 만들어졌는지 살펴봅시다.

     

    RIB은 Router, Interactor, Builder를 뜻합니다.

    • LoggedOutBuilder: LoggedOutBuildable 프로토콜을 채택해서 각 요소들을 만듭니다.
    // MARK: - Builder
    
    protocol LoggedOutBuildable: Buildable {
        func build(withListener listener: LoggedOutListener) -> LoggedOutRouting
    }
    
    func build(withListener listener: LoggedOutListener) -> LoggedOutRouting {
        let component = LoggedOutComponent(dependency: dependency)
        let viewController = LoggedOutViewController()
        let interactor = LoggedOutInteractor(presenter: viewController)
        interactor.listener = listener
        return LoggedOutRouter(interactor: interactor, viewController: viewController)
    }
    • LoggedOutInteractor: Interactor는 RIBs의 핵심입니다. 위 그림에서 볼 수 있듯이 다른 RIB으로 전환시키기도 하고 뷰와 상호작용 합니다. 따라서 이를 위한 객체들을 가지고 있습니다. 이때 구현된 객체를 대신 프로토콜을 사용해 의존성을 역전시켜줍니다.

    • LoggedOutRouter: 앞선 LoggedOutRouting을 채택해서 구현된 구현체입니다. 실제로 구체적인 Route 로직을 작성하는 부분인데요, interactor와 viewController를 주입받아서 통신할 수 있도록 합니다.

    • LoggedOutViewController: RIB을 생성할 때 Owns corresponding view를 체크해줬기 때문에 자동으로 생성된 뷰컨트롤러 입니다. listener를 통해서 사용자 이벤트를 처리합니다. 이때도 역시 프로토콜을 사용해 의존성을 역전시켜줍니다
     protocol LoggedOutPresentableListener: AnyObject {
        
    }
    
    final class LoggedOutViewController: UIViewController, LoggedOutPresentable, LoggedOutViewControllable {
    
        weak var listener: LoggedOutPresentableListener?
    }

     

     

    LoggedOut UI

    로그인이 안 되어있는 상태니 간단한 로그인 화면이 필요합니다. 여기선 뷰가 중요한 게 아니니 우버에서 친절히 만들어주신 코드를 고대로 복붙 하고 넘어가죠 ;)

    아래 코드로 LoggedOutViewController를 채워줍니다.코드보기

    import RIBs
    import RxSwift
    import UIKit
    import SnapKit
    
    protocol LoggedOutPresentableListener: 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.
    }
    
    final class LoggedOutViewController: UIViewController, LoggedOutPresentable, LoggedOutViewControllable {
    
        weak var listener: LoggedOutPresentableListener?
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = UIColor.white
            let playerFields = buildPlayerFields()
            buildLoginButton(withPlayer1Field: playerFields.player1Field, player2Field: playerFields.player2Field)
        }
    
        // MARK: - Private
    
        private var player1Field: UITextField?
        private var player2Field: UITextField?
    
        private func buildPlayerFields() -> (player1Field: UITextField, player2Field: UITextField) {
            let player1Field = UITextField()
            self.player1Field = player1Field
            player1Field.borderStyle = UITextBorderStyle.line
            view.addSubview(player1Field)
            player1Field.placeholder = "Player 1 name"
            player1Field.snp.makeConstraints { (maker: ConstraintMaker) in
                maker.top.equalTo(self.view).offset(100)
                maker.leading.trailing.equalTo(self.view).inset(40)
                maker.height.equalTo(40)
            }
    
            let player2Field = UITextField()
            self.player2Field = player2Field
            player2Field.borderStyle = UITextBorderStyle.line
            view.addSubview(player2Field)
            player2Field.placeholder = "Player 2 name"
            player2Field.snp.makeConstraints { (maker: ConstraintMaker) in
                maker.top.equalTo(player1Field.snp.bottom).offset(20)
                maker.left.right.height.equalTo(player1Field)
            }
    
            return (player1Field, player2Field)
        }
    
        private func buildLoginButton(withPlayer1Field player1Field: UITextField, player2Field: UITextField) {
            let loginButton = UIButton()
            view.addSubview(loginButton)
            loginButton.snp.makeConstraints { (maker: ConstraintMaker) in
                maker.top.equalTo(player2Field.snp.bottom).offset(20)
                maker.left.right.height.equalTo(player1Field)
            }
            loginButton.setTitle("Login", for: .normal)
            loginButton.setTitleColor(UIColor.white, for: .normal)
            loginButton.backgroundColor = UIColor.black
            loginButton.addTarget(self, action: #selector(didTapLoginButton), for: .touchUpInside)
        }
    
        @objc private func didTapLoginButton() {
    
        }
    }

     

    LoggedOut logic

    이제 login 하는 로직을 작성해볼 차례입니다!

    위에서 잠깐 말씀드렸듯이 뷰에서는 리스너를 사용해 사용자 이벤트를 처리합니다.

    지금 상황에서는 Login인 버튼이 눌렸을 때 사용자 이름을 넘겨서 로그인하는 함수가 필요합니다. 뷰컨에서 위에 있는 리스너 프로토콜에 아래와 같이 메소드를 정의해줍니다.

    뷰에서는 리스너에 정의한 메소드를 호출하는 것까지가 자신의 할 일입니다. 뷰에는 리스너 프로토콜을 객체로 들고 호출해줍니다.

    protocol LoggedOutPresentableListener: class {
        func login(withPlayer1Name player1Name: String?, player2Name: String?)
    }
    
    // ... in LoggedOutViewController
    
    weak var listener: LoggedOutPresentableListener?
    
    @objc private func didTapLoginButton() {
    	listener?.login(withPlayer1Name: player1Field?.text, player2Name: player2Field?.text)
    }

     

    실제 로직은 Interactor에서 처리하게 됩니다. LoggedOutInteractor에서 뷰의 리스너인 LoggedOutPresentableListener 을 채택하고(이미 되어있음), 아래 코드를 구현합니다.

    여기선 player들의 이름이 잘 프린트 되는지까지만 확인해보고 2탄에서 만나요 🙋🏼‍♂️

    // MARK: - LoggedOutPresentableListener
    
    func login(withPlayer1Name player1Name: String?, player2Name: String?) {
        let player1NameWithDefault = playerName(player1Name, withDefaultName: "Player 1")
        let player2NameWithDefault = playerName(player2Name, withDefaultName: "Player 2")
    
        print("\(player1NameWithDefault) vs \(player2NameWithDefault)")
    }
    
    private func playerName(_ name: String?, withDefaultName defaultName: String) -> String {
        if let name = name {
            return name.isEmpty ? defaultName : name
        } else {
            return defaultName
        }
    }

     

    실행화면

     

    'iOS' 카테고리의 다른 글

    RIBs) tutorial2-2  (0) 2021.06.26
    RIBs) tutorial2  (1) 2021.06.26
    CALayer 그리고 View와의 관계  (0) 2021.01.31
    Intrinsic Content Size, Content Hugging, Content Compression Resistance  (0) 2021.01.24
    GCD, Dispatch  (0) 2021.01.19

    댓글

Designed by Tistory.