-
RIBs) tutorial1iOS 2021. 6. 25. 23:58728x90
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