반응형
앞선 글은 아래에 있다.
VIPER에서 화면 전환인 View -> Router 에 대해 알아보려 한다.
아래 화면은 새롭게 추가한 시작 화면이다.
이곳에서 사용자는 Candy Store 뷰를 클릭하면, 다음 화면으로 넘어가도록 구성했다.
화면 이동은 Router담당이니 View에서 바로 Router로 보내버린다.
새롭게 추가된 화면은 Main이다.
MainEntity
struct StoreEntity{
let StoreName : String
let StoreImage : String
}
MainView
import UIKit
protocol MainViewProtocol : class {
var presenter: MainPresenterProtocol? {get set}
func setCandyStore(viewmodel: StoreViewModel)
}
class MainView : UIViewController {
@IBOutlet weak var candyView: UIView!
@IBOutlet weak var candyName: UILabel!
@IBOutlet weak var candyImage: UIImageView!
var presenter: MainPresenterProtocol? // 프레센터 연결
override func viewDidLoad() {
super.viewDidLoad()
presenter?.fetch()
}
}
extension MainView : MainViewProtocol {
func setCandyStore(viewmodel: StoreViewModel) {
let candyGesture : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(touchCandyView(sender:)))
candyName.text = viewmodel.name
candyImage.image = UIImage(named: viewmodel.imageName)
self.candyView.addGestureRecognizer(candyGesture)
}
@objc func touchCandyView(sender: UITapGestureRecognizer){
// 화면 전환 코드
}
}
MainPresenter
protocol MainPresenterProtocol : class {
func fetch()
func mergeInteractionCandy(_ interactor : MainInteractorProtocol, FetchCandy storeObject: StoreEntity)
}
struct StoreViewModel {
let name: String
let imageName: String
}
class MainPresenter {
weak var view : MainViewProtocol? // 뷰 연결
var router : RouterProtocol? // 라우터 연결
var interactor : MainInteractorProtocol? // 인터렉터 연결
}
extension MainPresenter : MainPresenterProtocol {
func fetch() {
interactor?.fetchStore()
}
func mergeInteractionCandy(_ interactor: MainInteractorProtocol, FetchCandy storeObject: StoreEntity) {
let viewModel = StoreViewModel(name: storeObject.StoreName, imageName: storeObject.StoreImage)
view?.setCandyStore(viewmodel: viewModel)
}
}
MainInteractor
protocol MainInteractorProtocol {
func fetchStore()
}
class MainInteractor : MainInteractorProtocol {
private var candyStore : StoreEntity?
private let storeAPIWorker : APIStoreWorkerProtocol
var presenter : MainPresenterProtocol? // 프레센터 연결
required init(withAPIWorker apiworker : APIStoreWorkerProtocol) {
self.storeAPIWorker = apiworker
}
func fetchStore() {
storeAPIWorker.fetchCandyStore{ [unowned self] (candyStore) in
self.candyStore = candyStore
self.presenter?.mergeInteractionCandy(self, FetchCandy: candyStore)
}
}
}
MainBuilder
Builder는 Router의 무게를 줄이기 위한 클래스로 UIKit을 사용할 수 있다.
Router의 한 종류라고 생각하면 된다.
import UIKit
class MainBuilder {
func buildModule(arroundView view: MainViewProtocol) {
let presenter = MainPresenter()
let interactor = MainInteractor(withAPIWorker: StoreAPIWorker())
let router = MainRouter()
view.presenter = presenter
presenter.view = view
presenter.router = router as RouterProtocol
presenter.interactor = interactor
interactor.presenter = presenter
}
}
MainRouter
MainRouter에서는 전의 CandyBuilder을 통해 CandyView를 제작하고 넘겨주는 코드를 포함하고 있다.
이러한 흐름을 가능하게 한 것은 Viewable 이다.
import UIKit
protocol RouterProtocol {
func setCandyview() -> CandyView
func push(from:Viewable)
func present(from:Viewable)
}
class MainRouter : RouterProtocol {
func setCandyview()-> CandyView {
let storeView = CandyView(nibName: "CandyView",bundle: nil)
CandyBuilder.buildModule(arroundView: storeView)
return storeView
}
func push(from:Viewable) {
let view = self.setCandyview()
from.push(view, animated: true)
}
func present(from:Viewable) {
let nav = UINavigationController(rootViewController: setCandyview())
from.present(nav, animated: true)
}
}
Viewable.swift
import Foundation
import UIKit
protocol Viewable: AnyObject {
func push(_ vc: UIViewController, animated: Bool)
func present(_ vc: UIViewController, animated: Bool)
func pop(animated: Bool)
func dismiss(animated: Bool)
func dismiss(animated: Bool, _completion: @escaping (() -> Void))
}
extension Viewable where Self: UIViewController {
func push(_ vc: UIViewController, animated: Bool) {
self.navigationController?.pushViewController(vc, animated: animated)
}
func present(_ vc: UIViewController, animated: Bool) {
self.present(vc, animated: animated, completion: nil)
}
func pop(animated: Bool) {
self.navigationController?.popViewController(animated: animated)
}
func dismiss(animated: Bool) {
self.dismiss(animated: animated, completion: nil)
}
func dismiss(animated: Bool, _completion: @escaping (() -> Void)) {
self.dismiss(animated: animated, completion: _completion)
}
var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
}
다시 MainView로 넘어와, 비어있던 화면 전환 코드에 이 코드를 넣어주면 정상적으로 작동하는 것을 볼 수 있다.
MainView
extension MainView : MainViewProtocol {
...
@objc func touchCandyView(sender: UITapGestureRecognizer){
MainRouter().push(from: self)
}
}
extension MainView : Viewable {}
시작 위치가 바뀌었으니, AppDelegate 파일에도 수정을 해준다.
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window : UIWindow?
var navController : UINavigationController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let mainView = MainView(nibName: "MainView", bundle: nil)
MainBuilder().buildModule(arroundView: mainView)
navController = UINavigationController(rootViewController: mainView)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
}
Viewable 출처
반응형
'개발 > 디자인 패턴' 카테고리의 다른 글
[iOS] 디자인패턴 - MVP (0) | 2020.03.20 |
---|---|
[iOS] 디자인패턴 - VIPER (4) | 2020.02.24 |