ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ReactorKit#2] Example : Counter 코드작성 (스토리보드 없이)
    iOS Dev/ReactorKit 2020. 6. 7. 02:42
    반응형

    https://github.com/chongin12/Counter-Prac << 전체 소스코드 

    우선 ReactorKit, RxSwift, RxCocoa, SnapKit 을 pod을 이용하여 설치를 할 것이다. .xcodeproj가 있는 폴더에서 pod init 명령어를 실행시켜주고, 생성된 Podfile에 다음과 같이 적어준다.

    pod 'ReactorKit'
    pod 'RxSwift'
    pod 'RxCocoa'
    pod 'SnapKit'

    pod install 명령어로 설치를 한 다음 생성된 .xcworkspace 파일을 열어준다.

     

    우선 스토리보드 없이 코드를 작성하려면 스토리보드를 삭제해야 한다. Main.storyboard, Launch.storyboard 다 휴지통으로 보내주고, 프로젝트 정보 General - Deployment Info - Main Interface를 비워준다.

    Info.plist에 들어가서 Information Property List - Application Scene Manifest - Scene Configuration - Application Session Role - Item 0 (Default Configuration) 에 들어가서 Storyboard Name 부분 행을 아예 지워준다. (마우스 오버레이 후 -(빼기)버튼 눌러도 된다.)

    그 다음 SceneDelegate.swift로 가서 func scene() 부분을 고쳐주어야 한다.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    이렇게 생긴 거를

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(frame: windowScene.coordinateSpace.bounds)
        window?.windowScene = windowScene
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
    }

    이렇게 바꿔준다.

    커밋 추가 (message : storyboard 제거)

    이제 뷰를 하나 만들어야 한다.
    CounterView.swift 파일 하나 만들어주고, 다음 내용을 집어넣는다.

    //
    //  CounterView.swift
    //  Counter-Prac
    //
    //  Created by 정종인 on 2020/06/06.
    //  Copyright © 2020 swmaestro10th. All rights reserved.
    //
    
    import Foundation
    import UIKit
    import SnapKit
    
    class CounterView: UIView {
        private let backgroundView: UIView = {
            let v = UIView()
            v.backgroundColor = .white
            return v;
        }()
        
        private let valueLabel: UILabel = {
            let v = UILabel()
            v.text = "0"
            v.textAlignment = .center
            return v
        }()
        
        private let increaseButton: UIButton = {
            let v = UIButton(type: .roundedRect)
            v.backgroundColor = .brown
            return v
        }()
        
        private let decreaseButton: UIButton = {
            let v = UIButton(type: .roundedRect)
            v.backgroundColor = .brown
            return v
        }()
        
        private let activityIndicatorView: UIActivityIndicatorView = {
            let v = UIActivityIndicatorView(style: .medium)
            return v
        }()
        
        func setup() {
            setupUI()
            setBind()
        }
        
    }
    
    extension CounterView {
        private func setupUI() {
            addSubviews()
            setLayout()
        }
        
        private func setBind() {
            //delegate, datasource
        }
        
        private func addSubviews() {
            self.addSubview(self.backgroundView)
            self.backgroundView.addSubview(self.valueLabel)
            self.backgroundView.addSubview(self.increaseButton)
            self.backgroundView.addSubview(self.decreaseButton)
            self.backgroundView.addSubview(self.activityIndicatorView)
        }
        
        private func setLayout() {
            self.backgroundView.snp.makeConstraints {
                $0.top.left.bottom.right.equalTo(safeAreaLayoutGuide)
            }
            self.valueLabel.snp.makeConstraints {
                $0.center.equalToSuperview()
                $0.width.height.equalTo(45)
            }
            self.increaseButton.snp.makeConstraints {
                $0.centerY.equalToSuperview()
                $0.trailing.equalTo(-30)
                $0.width.height.equalTo(45)
            }
            self.decreaseButton.snp.makeConstraints {
                $0.centerY.equalToSuperview()
                $0.leading.equalTo(30)
                $0.width.height.equalTo(45)
            }
            self.activityIndicatorView.snp.makeConstraints {
                $0.centerX.equalToSuperview()
                $0.top.equalTo(valueLabel).offset(30)
                $0.width.height.equalTo(45)
            }
        }
    }
    

     

    이걸 보여지게 하려면 뷰 컨트롤러에서 이 뷰를 불러와야 한다.

    ViewController의 내용을 다음과 같이 바꾼다.

    //
    //  ViewController.swift
    //  Counter-Prac
    //
    //  Created by 정종인 on 2020/06/05.
    //  Copyright © 2020 swmaestro10th. All rights reserved.
    //
    
    import UIKit
    
    class ViewController: UIViewController {
        private var counterView = CounterView()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            counterView.setup()
        }
        
        override func loadView() {
            self.view = counterView
        }
    
    }
    

     

    커밋 추가 (message : view 구성)

     이제 본격적으로 ReactorKit을 활용해본다.

    CounterViewReactor.swift파일을 하나 만들고, Reactor 프로토콜을 추가해본다.

    Action, Mutation, State, mutate(), reduce()를 구현한다.

    코드 : 

    //
    //  CounterViewReactor.swift
    //  Counter-Prac
    //
    //  Created by 정종인 on 2020/06/06.
    //  Copyright © 2020 swmaestro10th. All rights reserved.
    //
    
    import Foundation
    import ReactorKit
    
    class CounterViewReactor: Reactor {
        
        enum Action {
            case increase
            case decrease
        }
        
        enum Mutation {
            case increaseValue
            case decreaseValue
            case isLoading(Bool)
        }
        
        struct State {
            var value: Int
            var isLoading: Bool
        }
        
        var initialState: State
        
        init() {
            self.initialState = State(value: 0, isLoading: false)
        }
        
        func mutate(action: Action) -> Observable<Mutation> {
            switch action {
            case .increase:
                return Observable.concat([
                    Observable.just(Mutation.isLoading(true)),
                    Observable.just(Mutation.increaseValue),
                    Observable.just(Mutation.isLoading(false))
                ])
            case .decrease:
                return Observable.concat([
                    Observable.just(Mutation.isLoading(true)),
                    Observable.just(Mutation.decreaseValue),
                    Observable.just(Mutation.isLoading(false))
                ])
            }
        }
        
        func reduce(state: State, mutation: Mutation) -> State {
            var state = state
            switch mutation {
            case .increaseValue:
                state.value += 1
            case .decreaseValue:
                state.value -= 1
            case .isLoading(let isLoading):
                state.isLoading = isLoading
            }
            return state
        }
    }
    

     

    커밋 추가(message : reactor 추가)

    이제 View 부분을 작성해야 한다. 다시 CounterView로 와서, disposeBag 변수를 하나 추가 해주고, View 프로토콜을 추가한 코드를 extension에 짜준다.

    //
    //  CounterView.swift
    //  Counter-Prac
    //
    //  Created by 정종인 on 2020/06/06.
    //  Copyright © 2020 swmaestro10th. All rights reserved.
    //
    
    import Foundation
    import UIKit
    import SnapKit
    import ReactorKit
    import RxCocoa
    
    class CounterView: UIView {
        weak var vc: CounterViewController?
        
        init(controlBy viewController: CounterViewController) {
            self.vc = viewController
            super.init(frame: UIScreen.main.bounds)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private let baseView: UIView = {
            let v = UIView()
            v.backgroundColor = .white
            return v
        }()
        
        private let backgroundView: UIView = {
            let v = UIView()
            v.backgroundColor = .white
            return v
        }()
        
        private let valueLabel: UILabel = {
            let v = UILabel()
            v.text = "0"
            v.textAlignment = .center
            return v
        }()
        
        private let increaseButton: UIButton = {
            let v = UIButton(type: .roundedRect)
            v.setTitle("+", for: .normal)
            return v
        }()
        
        private let decreaseButton: UIButton = {
            let v = UIButton(type: .roundedRect)
            v.setTitle("-", for: .normal)
            return v
        }()
        
        private let activityIndicatorView: UIActivityIndicatorView = {
            let v = UIActivityIndicatorView(style: .medium)
            return v
        }()
        
        var disposeBag: DisposeBag = DisposeBag()
        
        func setup() {
            setupUI()
            setBind()
        }
        
    }
    
    extension CounterView {
        private func setupUI() {
            addSubviews()
            setLayout()
        }
        
        private func setBind() {
            //delegate, datasource
        }
        
        private func addSubviews() {
            self.addSubview(self.baseView)
            self.baseView.addSubview(self.backgroundView)
            self.backgroundView.addSubview(self.valueLabel)
            self.backgroundView.addSubview(self.increaseButton)
            self.backgroundView.addSubview(self.decreaseButton)
            self.backgroundView.addSubview(self.activityIndicatorView)
        }
        
        private func setLayout() {
            self.baseView.snp.makeConstraints {
                $0.top.left.bottom.right.equalToSuperview()
            }
            self.backgroundView.snp.makeConstraints {
                $0.top.left.bottom.right.equalTo(safeAreaLayoutGuide)
            }
            self.valueLabel.snp.makeConstraints {
                $0.center.equalToSuperview()
                $0.width.height.equalTo(45)
            }
            self.increaseButton.snp.makeConstraints {
                $0.centerY.equalToSuperview()
                $0.trailing.equalTo(-30)
                $0.width.height.equalTo(45)
            }
            self.decreaseButton.snp.makeConstraints {
                $0.centerY.equalToSuperview()
                $0.leading.equalTo(30)
                $0.width.height.equalTo(45)
            }
            self.activityIndicatorView.snp.makeConstraints {
                $0.centerX.equalToSuperview()
                $0.top.equalTo(valueLabel).offset(30)
                $0.width.height.equalTo(45)
            }
        }
    }
    
    extension CounterView: View {
        func bind(reactor: CounterViewReactor) {
            increaseButton.rx.tap
                .map { Reactor.Action.increase }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            decreaseButton.rx.tap
                .map { Reactor.Action.decrease }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            reactor.state
                .map { $0.value }
                .distinctUntilChanged()
                .map { "\($0)" }
                .bind(to: valueLabel.rx.text)
                .disposed(by: disposeBag)
            
            reactor.state
                .map { $0.isLoading }
                .distinctUntilChanged()
                .bind(to: activityIndicatorView.rx.isAnimating)
                .disposed(by: disposeBag)
            
            
        }
    }
    

     

    새로운 CounterViewController.swift를 작성하고, view를 연결하고, counterView의 reactor도 정의를 해준다.

    //
    //  CounterViewController.swift
    //  Counter-Prac
    //
    //  Created by 정종인 on 2020/06/07.
    //  Copyright © 2020 swmaestro10th. All rights reserved.
    //
    
    import Foundation
    import UIKit
    
    class CounterViewController: UIViewController {
        private lazy var counterView = CounterView(controlBy: self)
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            counterView.setup()
            counterView.reactor = CounterViewReactor()
        }
        
        override func loadView() {
            self.view = counterView
        }
    }
    

    그 다음, SceneDelegate에 ViewController()부분도 CounterViewcontroller부분으로 바꿔주어야 한다.

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.windowScene = windowScene
        window?.rootViewController = CounterViewController()
        window?.makeKeyAndVisible()
    }

    그밖에도 delay추가 등 세부적인 코드 추가 후 빌드.

    커밋 추가(message: finish)

    https://github.com/chongin12/Counter-Prac

     

    chongin12/Counter-Prac

    ReactorKit Example Counter Practice. Contribute to chongin12/Counter-Prac development by creating an account on GitHub.

    github.com

     

    반응형

    댓글

Designed by Tistory.