티스토리 뷰

iOS

ModerRIBs_tutorial 2 - 2

Peppo 2022. 1. 26. 21:14
728x90

 

지난 튜토리얼 2-1에 이어서 가봅시다


LoggedIn 이 로드 될 때 OffGame attach 시키기

 

LoggedIn 은 viewless라고 했습니다. 하위 RIB 들을 조정만 할 수 있는데요.

LoggedIn의 하위 RIB인 OffGame 을 생성해 볼게요. 

OffGame "Start Game" 버튼을 보여주며, 버튼을 탭하면 작동하기 위한 역할을 할겁니다. 

 

OffGame은 RIB 생성시 view를 추가 해주세요! 

 

 

UI를 구현하기 위해 OffGameViewController 에 아래와 같이 작성 해주세요.

 

(친절하게 uber에서 UI 코드를 공유해줬습니다.

여러분의 시간은 소중하니까 아래꺼 복붙!)

 

// OffGameViewController

import ModernRIBs
import UIKit
import SnapKit


protocol OffGamePresentableListener: 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 OffGameViewController: UIViewController, OffGamePresentable, OffGameViewControllable {
    
    var uiviewController: UIViewController {
        return self
    }

    weak var listener: OffGamePresentableListener?
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("메소드가 지원되지 않습니다.")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor.yellow
        buildStartButton()
    }
    
    // MARK: - Private
    
    private func buildStartButton() {
        let startButton = UIButton()
        view.addSubview(startButton)
        startButton.snp.makeConstraints { make in
            make.center.equalTo(self.view.snp.center)
            make.leading.trailing.equalTo(self.view).inset(40)
            make.height.equalTo(100)
        }
        startButton.setTitle("Start Game", for: .normal)
        startButton.setTitleColor(UIColor.white, for: .normal)
        startButton.backgroundColor = UIColor.black
    }
}

 

이제 새로 만들었던 OffGame 을 상위 RIB인 LoggedIn 과 연결시킬거에요. 

 

LoggedIn  OffGame 을 하위로 build 할 수 있어야 하고,  attach 할 수 있어야 해요.

그렇게 해주기 위해서 LoggedInRouter 에 아래와 같이 작성해 줍니다. 

 

// LoggedInRouter

init(interactor: LoggedInInteractable, viewController: LoggedInViewControllable, offGameBuilder: OffGameBuildable) {
        self.viewController = viewController
        self.offGameBuilder = offGameBuilder
        super.init(interactor: interactor)
        interactor.router = self
    }

 

그럼 아래 에러가 뜰거에요.

 

당황하지 않고 

offGameBuilder를 참조할 새로운 private 상수를 선언해줍니다.

 

// LoggedInRouter
// MARK: - Private

    private let offGameBuilder: OffGameBuildable

 

LoggedInBuilder 에, OffGameBuilder 객체를 생성하여,

LoggedInRouter에 객체전달을 하기 위해 LoggedInBuilder build() 메소드를 업데이트 해줍니다.

// LoggedInBuilder 

    func build(withListener listener: LoggedInListener) -> LoggedInRouting {
        let component = LoggedInComponent(dependency: dependency)
        let interactor = LoggedInInteractor()
        interactor.listener = listener
        
        // 객체 생성 부분
        let offGameBuilder = OffGameBuilder(dependency: component) // 추가
        
        // 객체 전달 부분
        return LoggedInRouter(interactor: interactor, viewController: component.LoggedInViewController,
        offGameBuilder: offGameBuilder) 
    }

그럼 에러가 나죠.

에러가 나는 이유는

OffGameBuilder를 선언했지만 component가 OffGameDependency를 준수하게 해줍니다.

LoggedInComponent class에 아래와 같이 추가해주세요.

 

// LoggedInBuilder

final class LoggedInComponent: Component<LoggedInDependency>, OffGameDependency { // 추가
    
    fileprivate var loggedInViewController: LoggedInViewControllable {
        return dependency.loggedInViewController
    }
}
// (RIB의 종속과 컴포넌트는 tutorial 3에서 자세히 다룰거에요)

 

우리가 구현하려는게 사용자가 로그인한 직후에 시작화면을 보여주려고 하는건데요.

LoggedIn은 view가 없으니 LoggedIn이 로드 되자마자 view가 있는 OffGame을 갖고와야해요.

 

아래처럼 코드를 작성해주세요.

// LoggedInRouter

final class LoggedInRouter: Router<LoggedInInteractable>, LoggedInRouting {
	override func didLoad() {
    	super.didLoad()
	    attachOffGame()
	}
}

 

attachOffGame() 메소드는 상위 RIB (LoggedIn)에 빌드, attach 하는데 사용 될 거에요.

아래처럼 코드를 작성해주세요.

// LoggedInRouter

// MARK: - Private

private var currentChild: ViewableRouting? // 추가

private func attachOffGame() {             // 추가
    let offGame = offGameBuilder.build(withListener: interactor)
    self.currentChild = offGame
    attachChild(offGame)
    viewController.present(viewController: offGame.viewControllable)
}

 

attachOffGame() 메소드 안에 OffGameBuilder를 인스턴스화 하려면

LoggedInInteractable 에 OffGameListener를 추가해 줘야 합니다.

추가하게 되면 상수 offGame은 offGame에서 일어나는 이벤트들을 상위 RIB으로 전달하는 역할을 합니다.

// LoggedInRouter

protocol LoggedInInteractable: Interactable, OffGameListener { // 추가
    weak var router: LoggedInRouting? { get set }
    weak var listener: LoggedInListener? { get set }
}

여기까지 하게되면, LoggedIn 은 로드 후에 OffGame 을 attach하고 이벤트들을 전달 받게 될겁니다.

 


LoggedIn 이 detached 되었을때, 보여지는 view 정리

 

LoggedIn 은 자체 뷰가 없어 수정한 뷰 내용을 상위 뷰 계층인 Root에서는 자동적으로 지우거나 수정할 수 없습니다.

 

다행히 위에 언급한 상황에서 RIB이 detach 되는 상황이면 Xcode가 알아서 처리해줘요. 

아래와 같이 선언해줍니다. 

// LoggedInRouter

protocol LoggedInViewControllable: ViewControllable {
    func present(viewController: ViewControllable)
    func dismiss(viewController: ViewControllable)
}

다른곳에서 protocol안에 선언을 했던것과 비슷해요. 

LoggedIn 이 ViewControllable을 해제(dismiss)하는 기능이 필요함을 선언합니다.

 

그리고 현재 하위 RIB의 뷰 컨트롤러를 해제하기 위해 cleanupViews 메소드를 아래와 같이 업데이트 해줍니다.

// LoggedInRouter

func cleanupViews() {
    if let currentChild = currentChild {
        viewController.dismiss(viewController: currentChild.viewControllable)
    }
}

cleanupViews 메소드는 상위뷰가 LoggedIn을 detach할때 LoggedInInteractor에 의해 호출됩니다. 

 


"Start Game" 버튼을 누르면 TicTacToe로 전환

 

사용자가 OffGame에서 "Start Game" 버튼을 누르면 TicTacToe로전환해보려고 합니다.



LoggedOutViewController에서 "Login" 버튼을 눌렀을때 처럼 비슷하게 하면되는데 다시 해봅시다.

 

일단 TicTacToe 를 만들어주시고 

여기 에서 각 TicTacToe 파일의 코드들을 복사, 붙여넣기 해주세요. 

뷰컨트롤러, 인터렉터가 이미 구현되어 있습니다.

 

"Start Game" 버튼이 눌렸을때 기능 구현입니다.

프로토콜 OffGamePresentableListener에 startGame() 이라는 메소드를 만들고

// OffGameViewController

protocol OffGamePresentableListener: AnyObject {
    func startGame()
}

private func buildStartButton() {
        ...
        startButton.addTarget(self, action: #selector(didTapStartButton), for: .touchUpInside)
    }
    
    @objc private func didTapStartButton() {
        listener?.startGame()
    }

 

 

OffGamePresentableListener에 startGame() 메소드가 추가 되었으니 

OffGameInteractor 쪽에 메소드 구현을 해줘야 합니다.

 

// OffGameInteractor

protocol OffGameListener: AnyObject {
    func didStartGame()
}

func startGame() {
        listener?.didStartGame()
    }

 

위에 OffGameListener는 상위 RIB인 LoggedInInteractable이 채택을 하고 있으니 거기에 맞춰줘야 합니다.

LoggedInInteractordidStartGame() 메소드를 구현 해줍시다.

 

TicTacToe도 route 시켜줘야하니 LoggedInRouting 프로토콜 부분에 routeToTicTacToe() 메소드를 추가해줍니다.

// LoggedInInteractor

protocol LoggedInRouting: Routing {
    func routeToTicTacToe()
    func cleanupViews()
 }
 
 final class LoggedInInteractor: Interactor, LoggedInInteractable {
 	 ...
 
	 func didStartGame() {
        router?.routeToTicTacToe()
     }
}

 

LoggedInRouting에 routeToTicTacToe() 메소드를 추가함으로써,

LoggedInRouting을 채택하고 있는 LoggedInRouter 에 TicTacToe를 연결 시켜줘야 하니 TicTacToeBuilder를 만들어 줍니다.

 

// LoggedInRouter

final class LoggedInRouter: Router<LoggedInInteractable>, LoggedInRouting {

	init(interactor: LoggedInInteractable,
	...
         ticTacToeBuilder: TicTacToeBuildable) {
         ...
        self.ticTacToeBuilder = ticTacToeBuilder
        super.init(interactor: interactor)
        interactor.router = self
    }
    
    // MARK: - Private
    
	private let ticTacToeBuilder: TicTacToeBuildable
}

 

TicTacToeBuildable 를 손 봤으니 Builder로 가서 수정 해줍시다.

// LoggedInBuilder

final class LoggedInBuilder: Builder<LoggedInDependency>, LoggedInBuildable {


	func build(withListener listener: LoggedInListener) -> LoggedInRouting {

		...
        interactor.listener = listener
        // 객체 생성 부분        
        let offGameBuilder = OffGameBuilder(dependency: component)
        let ticTacToeBuilder = TicTacToeBuilder(dependency: component)
        
        // 객체 전달 부분
        return LoggedInRouter(interactor: interactor,
                              viewController: component.loggedInViewController,
                              offGameBuilder: offGameBuilder,
                              ticTacToeBuilder: ticTacToeBuilder)
    }
}

 

build를 해줄 때 Listener를 넣어줘야하니 LoggedInInteractable에 TicTacToeListener를 채택해 줍니다.

// LoggedInInteractable

protocol LoggedInInteractable: Interactable, OffGameListener, TicTacToeListener { // 추가
     var router: LoggedInRouting? { get set }
     var listener: LoggedInListener? { get set }
}

 

근데 TicTacToeListener에는 뭐가 있다? 

// TicTacToeInteractor

protocol TicTacToeListener: AnyObject {
    func gameDidEnd()  // <--- 이 친구
}

 

gamdDidEnd() 메소드가 있네요.. 채택을 했으니 준수를 해줘야죠 

구현은 나중에 해줄게요 이렇게만 해주세요.

// LoggedInInteractor

final class LoggedInInteractor: Interactor, LoggedInInteractable {

	...
    
    func gameDidEnd() {
    
    }
}

 

여기까지 하면 "Start Game" 버튼을 누르면 TicTacToe 게임이 열릴거에요.

 

 

 

 


승자가 나오면 OffGame 붙여주고 TicTacToe 떼기

 

게임이 끝났을때 다시 OffGame 을 보여주는걸 구현해볼게요.

화면 이동은 TicTacToe -> OffGame 으로 가는거니 

Routing 부분에 routeToOffGame() 메소드를 만들어주고

좀전에 gameDidEnd() 메소드의 구현부도 채워줍시다.

 

// LoggedInInteractor

protocol LoggedInRouting: Routing {
    func routeToTicTacToe()
    func cleanupViews()
    func routeToOffGame() // 추가
}

final class LoggedInInteractor: Interactor, LoggedInInteractable {

    weak var router: LoggedInRouting?
    weak var listener: LoggedInListener?

	 ...
     
     // MARK: TicTacToeListener
     
	 func gameDidEnd() {
        router?.routeToOffGame()  // 추가
    }

}

 

Routing 부분에 연결(route)을 해줬으니 Router부분에 구현을 해줘야겠죠?

// LoggedInRouter

final class LoggedInRouter: Router<LoggedInInteractable>, LoggedInRouting {

	...
    
	 func routeToOffGame() {
        detachCurrentChild()
        attachOffGame()
    }
    
    // MARK: - Private
    
    private func attachOffGame() {
        let offGame = offGameBuilder.build(withListener: interactor)
        self.currentChild = offGame
        attachChild(offGame)
        viewController.present(viewController: offGame.viewControllable)
    }

}

 

 

여기까지 하고 틱택토 게임이 끝나면 ??

Close Game 터치
OffGame attach!!


튜토리얼 2는 attach 하고 detach하는 방법을 연습하는 용도 였던것 같아요.

Builder, Interactor, Router의 역할도 조금은 감이 잡히는것 같습니다.

 

각자 서로 연결되어있다보니 어디에 하나 추가하면 

Builder에는 추가했는데 여긴 외않헤! 
protocol에 선언해놓고 여긴 외않헤!
빼애애액 

 

하는걸 달래주다보면 흐름이 좀 잡히는것 같습니다. 

 

몇번 더 해봐야 숙달 될것 같긴하네요. 

고생하셨습니다 ~

튜토리얼 3 해보러 가보겠습니다 !! 

 

728x90

'iOS' 카테고리의 다른 글

[Swift] 클로저 (Closure) (2) - Escaping Closure, Autoclosure  (0) 2022.02.02
[Swift] 클로저 (Closure) (1)  (0) 2022.01.30
ModernRIBs_tutorial2 - 1  (0) 2022.01.23
[Swift] 함수 (Functions) (2)  (0) 2022.01.21
[Swift] 함수 (Functions) (1)  (0) 2022.01.19