Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

MainView

WooSeok Suh edited this page Jul 15, 2021 · 8 revisions

2021-07-05

by Seok

Step.1

  • MainView 하단의 ViewController 이동 Button을 UIButton으로 생성하고 Horizontal StackView에 배치
  • AutoLayout 확인 (iPod touch, 8, 11, 12)

Step.2

Button의 다형성 구현을 위하여 ButtonController 및 ButtonMapper 객체 생성

  • 각 Button Touch시 어떤 Button이 눌리는지 구분 기능 구현 완료
  • 해당 기능 바탕으로 이동할 ViewController와 Mapping 예정
import UIKit

enum ViewControllerType: CaseIterable {
    case giftVC
    case advertiseVC
    case rankVC
    case itemVC
}

final class ButtonMapper {
    private var map: [UIButton: ViewControllerType]
    
    init(from buttons: [UIButton]) {
        self.map = Dictionary(uniqueKeysWithValues: zip(buttons, ViewControllerType.allCases))
    }
    
    subscript(button: UIButton) -> ViewControllerType? {
        return map[button]
    }
}

final class ButtonController: NSObject {
    
    @IBOutlet var moveToVCButtons: [UIButton]!
    
    private var moveToVCMapper: ButtonMapper?
    private var buttonTouchedHandler: (ViewControllerType) -> ()
    
    override init() {
        buttonTouchedHandler = { _ in }
    }
    
    func setupButton() {
        self.moveToVCMapper = ButtonMapper(from: moveToVCButtons)
    }
    
    @IBAction func buttonTouched(sender: UIButton)  {
        guard let vc = moveToVCMapper?[sender] else { return }
        buttonTouchedHandler(vc)
    }
    
    func bind(action: @escaping (ViewControllerType) -> ()) {
        self.buttonTouchedHandler = action
    }
}

이때 SceneCoordinator 생성하여 진행할지 회의 필요 -> SceneCoordinator로 구현하기로 협의하여 객체생성

import Foundation
import RxSwift
import RxCocoa

final class SceneCoordinator: SceneCoordinatorType {
    
    private var window: UIWindow
    private var currentVC: UIViewController
    
    required init(window: UIWindow) {
        self.window = window
        currentVC = window.rootViewController!
    }
    
    @discardableResult
    func transition(to scene: Scene, using style: TransitionStyle, animated: Bool) -> Completable {
        let subject = PublishSubject<Void>()
        let target = scene.instantiate()
        
        switch style {
        case .root:
            currentVC = target
            window.rootViewController = target
            subject.onCompleted()
            
        case .fullScreen:
            target.modalPresentationStyle = .fullScreen
            currentVC.present(target, animated: animated) {
                subject.onCompleted()
            }
            currentVC = target
            
        case .modal:
            currentVC.present(target, animated: animated) {
                subject.onCompleted()
            }
            currentVC = target
        }
        return subject.ignoreElements().asCompletable()
    }
    
    @discardableResult
    func close(animated: Bool) -> Completable {
        let subject = PublishSubject<Void>()
        
        if let presentingVC = self.currentVC.presentingViewController {
            self.currentVC.dismiss(animated: animated) {
                self.currentVC = presentingVC
                subject.onCompleted()
            }
        } else {
            subject.onError(TransitionError.unknown)
        }
        
        return subject.ignoreElements().asCompletable()
    }
}

Step.3

ViewController가 ViewModel로 Button이 가지고있는 ViewControllerType을 넘기면 VC 이동기능 구현

  • CommonViewModel이 가지고 있는 SceneCoordinator 객체 활용
import Foundation

class MainViewModel:CommonViewModel {
    
    func makeMoveAction(to viewController: ViewControllerType) {
        switch viewController {
        case .giftVC:
            let giftViewModel = GiftViewModel(sceneCoordinator: self.sceneCoordinator)
            let giftScene = Scene.gift(giftViewModel)
            self.sceneCoordinator.transition(to: giftScene, using: .modal, animated: true)
            
        case .advertiseVC:
            let advertiseViewModel = AdvertiseViewModel(sceneCoordinator: self.sceneCoordinator)
            let advertiseScene = Scene.ad(advertiseViewModel)
            self.sceneCoordinator.transition(to: advertiseScene, using: .fullScreen, animated: true)
            
        case .rankVC:
            let rankViewModel = RankViewModel(sceneCoordinator: self.sceneCoordinator)
            let rankScene = Scene.rank(rankViewModel)
            self.sceneCoordinator.transition(to: rankScene, using: .modal, animated: true)
            
        case .itemVC:
            let itemViewModel = ItemViewModel(sceneCoordinator: self.sceneCoordinator)
            let itemScene = Scene.item(itemViewModel)
            self.sceneCoordinator.transition(to: itemScene, using: .modal, animated: true)
        }
    }
    
}

추후 로직에 따라 Storage 등의 추가 객체가 필요하면 CommonViewModel의 init에 주입하여 SceneDelegate에서 넣어주면 쉽게 코드 변경 가능

  • 이때도 마찬가지로 Protocol활용하여 다형성 구현가능
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    let coordinator = SceneCoordinator(window: window!)
    let mainViewModel = MainViewModel(sceneCoordinator: coordinator)
    let mainScene = Scene.main(mainViewModel)
    coordinator.transition(to: mainScene, using: .root, animated: false)
}

2021-07-06

by Seok

Step.3

GiftVC & ViewModel 객체 생성 및 CancelButton 기능구현

  • init 시점에서 action을 주입하여 변화에 열려있도록 설계
import Foundation
import Action

class GiftViewModel: CommonViewModel {
    
    let confirmAction: Action<String, Void>
    let cancelAction: CocoaAction
    
    init(sceneCoordinator: SceneCoordinatorType, storage: ItemStorageType, confirmAction: Action<String, Void>? = nil ,cancelAction: CocoaAction? = nil) {
        
        self.confirmAction = Action<String, Void> { input in
            if let action = confirmAction {
                action.execute(input)
            }
            return sceneCoordinator.close(animated: true).asObservable().map{ _ in }
        }
        
        self.cancelAction = CocoaAction {
            if let action = cancelAction {
                action.execute(())
            }
            return sceneCoordinator.close(animated: true).asObservable().map{ _ in }
        }
        
        super.init(sceneCoordinator: sceneCoordinator, storage: storage)
    }
}

2021-07-15

by Seok

Step.4

SceneCoordinator Transition Method Refactoring

import Foundation
import RxSwift

protocol SceneCoordinatorType {
    
    @discardableResult
    func transition(to scene: Scene, using style: TransitionStyle, with type: StoryboardType, animated: Bool) -> Completable
    
    @discardableResult
    func close(animated: Bool) -> Completable
    
    @discardableResult
    func toMain(animated: Bool) -> Completable
}
  • 기존에 없던 StoryboardType을 파라미터로 추가
  • Scene에서 불필요하게 중복으로 Storyboard 생성했던 코드 삭제

Disable SwiftLint Waring

// swiftlint:disable:next cyclomatic_complexity
func instantiate(from storyboard: String) -> UIViewController
  • Swith Case에서 처리해야하는 VC 이동 코드 증가로 인하여 cyclomatic_complexity 경고
  • 일단 Lint 경고를 무시하도록 처리하였지만, 추후 sceneCoordinator 또는 Method 분리를 고려하여 수정해야 함

StoryboardType enum타입 생성

import Foundation

enum StoryboardType {
    case main
    case game
    
    var name: String {
        switch self {
        case .main:
            return "Main"
        case .game:
            return "Game"
        }
    }
}
  • 파라미터 타입을 한정적(안정성 고려)으로 사용하기 위해 static let이 아닌 case로 구현

Step.5

GhostTypewriter 라이브러리 추가

import UIKit
import GhostTypewriter

final class MainViewController: UIViewController, ViewModelBindableType {
    
    var viewModel: MainViewModel!
    @IBOutlet var buttonController: MainButtonController!
    @IBOutlet weak var titleLabel: TypewriterLabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        titleLabel.startTypewritingAnimation()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        titleLabel.restartTypewritingAnimation()
    }
}
  • titleLabel animation 효과 추가
  • 다른 VC로 이동 후, mainVC로 돌아올때 animationRestart 호출(viewWillAppear)

Clone this wiki locally

Morty Proxy This is a proxified and sanitized view of the page, visit original site.