ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ReactorKit 으로 로그인 페이지 만들기
    Swift/RxSwift 2022. 10. 14. 01:58

    Login Controller

    //
    //  ViewController.swift
    //  ReactorPractice
    //
    //  Created by Mac mini on 2022/10/13.
    //
    
    import Foundation
    import UIKit
    import RxSwift
    import RxCocoa
    import Then
    import ReactorKit
    import RxViewController
    import RxGesture
    import SnapKit
    
    class ViewController: UIViewController, View {
        
        internal var disposeBag = DisposeBag()
        
        private let behaviorRelay = BehaviorRelay<Void>(value: ())
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .white
            setupLayout()
        }
        
        init(reactor: LoginReactor) {
            super.init(nibName: nil, bundle: nil)
            self.reactor = reactor
        }
        
        func bind(reactor: LoginReactor) {
            
            // MARK: -  View -> Reactor
            
            // initialize state when viewWillAppear called
            self.rx.viewWillAppear
                .map { _ in Reactor.Action.initialize }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
    
            emailTF.rx.text.orEmpty
                .map { Reactor.Action.typeEmail($0)}
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            passwordTF.rx.text.orEmpty
                .map { Reactor.Action.typePassword($0)}
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            loginBtn.rx.tap
                .map { Reactor.Action.login }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            
            // MARK: - Reactor -> View
            reactor.state.map { $0.email }
                .distinctUntilChanged()
                .bind(to: emailTF.rx.text )
                .disposed(by: disposeBag)
    
            reactor.state.map { $0.password }
                .distinctUntilChanged()
                .bind(to: passwordTF.rx.text )
                .disposed(by: disposeBag)
            
            
            // spinner
            reactor.state.map { $0.isSpinnerRunning }
                .distinctUntilChanged()
                .subscribe(onNext: { [weak self] shouldRun in
                    guard let self = self else { return }
                    shouldRun ? self.spinner.startAnimating() : self.spinner.stopAnimating()
                })
                .disposed(by: disposeBag)
            
            // login
            reactor.state.map { $0.shouldLogin }
                .distinctUntilChanged()
                .subscribe(onNext: { [weak self] shouldLogin in
                    guard let self = self else { return }
                    if shouldLogin {
                        self.moveToNextPage()
                    }
                })
                .disposed(by: disposeBag)
            
            // hide keyboard when empty view tapped
            self.view.rx.tapGesture()
                .subscribe(onNext: { [weak self] _ in
                    self?.hideKeyboard()
                })
                .disposed(by: disposeBag)
        }
        
        private func hideKeyboard() {
            self.view.endEditing(true)
        }
        
        private func setupLayout() {
            [
             titleLabel,
             emailTF, passwordTF,
             loginBtn, spinner
            ].forEach { self.view.addSubview($0)}
            
            titleLabel.snp.makeConstraints { make in
                make.top.equalToSuperview().offset(200)
                make.centerX.equalToSuperview()
                make.width.equalToSuperview()
                make.height.equalTo(50)
            }
            
            emailTF.snp.makeConstraints { make in
                make.top.equalTo(titleLabel.snp.bottom).offset(36)
                make.leading.equalToSuperview().inset(48)
                make.trailing.equalToSuperview().inset(48)
                make.height.equalTo(56)
            }
            
            passwordTF.snp.makeConstraints { make in
                make.top.equalTo(emailTF.snp.bottom).offset(24)
                make.leading.equalToSuperview().inset(48)
                make.trailing.equalToSuperview().inset(48)
                make.height.equalTo(56)
            }
            
            loginBtn.snp.makeConstraints { make in
                make.bottom.equalToSuperview().inset(70)
                make.height.equalTo(56)
                make.leading.equalToSuperview().inset(48)
                make.trailing.equalToSuperview().inset(48)
            }
            
            spinner.transform = CGAffineTransform(scaleX: 2, y: 2)
            
            spinner.snp.makeConstraints { make in
                make.center.equalToSuperview()
                make.width.height.equalTo(100)
            }
        }
        
        
        private func moveToNextPage() {
            let secondController = SecondViewController()
            navigationController?.pushViewController(secondController, animated: true)
        }
        
        
        private let titleLabel = UILabel().then {
            $0.textColor = .black
            $0.text = "Login Page"
            $0.textAlignment = .center
            $0.font = UIFont.systemFont(ofSize: 20)
            $0.numberOfLines = 2
        }
        
        private let emailTF = UITextField().then {
            $0.placeholder = "E-mail"
            $0.backgroundColor = .gray
            $0.keyboardType = .emailAddress
            $0.autocapitalizationType = .none
            $0.autocorrectionType = .no
        }
        
        private let passwordTF = UITextField().then {
            $0.placeholder = "Password"
            $0.backgroundColor = .gray
            $0.isSecureTextEntry = true
        }
        
        private let loginBtn = UIButton().then {
            $0.setTitle("Login", for: .normal)
            $0.setTitleColor(.white, for: .normal)
            $0.backgroundColor = .red
            $0.layer.cornerRadius = 8
        }
        
        var spinner = UIActivityIndicatorView().then {
            $0.hidesWhenStopped = true
            $0.color = .magenta
        }
    
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }

     

     

    LoginReactor

    //
    //  LoginReactor.swift
    //  ReactorPractice
    //
    //  Created by Mac mini on 2022/10/13.
    //
    
    import Foundation
    import RxCocoa
    import RxSwift
    import ReactorKit
    
    
    // TODO: make UserService.
    
    class LoginReactor: Reactor {
    
        private let tempId = "id@gmail.com"
        private let tempPassword = "password!"
        
        public enum Action {
            case login
            case typeEmail(String)
            case typePassword(String)
            case initialize
        }
    
        public enum Mutation {
            case login
            case updateEmail(String)
            case updatePassword(String)
            case shouldShowSpinner(Bool)
            case shouldInitialize
        }
    
        public struct State {
            
            var email: String
            var password: String
            var isSpinnerRunning: Bool
            var shouldLogin: Bool
            var errorMsg: String
            
            init() {
                self.email = ""
                self.password = ""
                self.isSpinnerRunning = false
                self.shouldLogin = false
                self.errorMsg = ""
            }
        }
        
        public var initialState: State = State()
    
        func mutate(action: Action) -> Observable<Mutation> {
            switch action {
    
            case .login:
                return Observable.concat([
                    Observable.just(Mutation.shouldShowSpinner(true)),
                    // delay 2 sec to show spinner clearly
                    Observable.just(Mutation.login).delay(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance),
                    
                    Observable.just(Mutation.shouldShowSpinner(false))
                ])
                
            case .typeEmail(let email):
                return .just(.updateEmail(email))
                
            case .typePassword(let password):
                return .just(.updatePassword(password))
                
            case .initialize:
                return .just(.shouldInitialize)
            }
        }
    
        func reduce(state: State, mutation: Mutation) -> State {
            var state = state
    
            switch mutation {
                
            case .login:
                if self.tempId == state.email && self.tempPassword == state.password {
                    state.shouldLogin = true
                }
                
            case .updateEmail(let email):
                state.email = email
                
            case .updatePassword(let password):
                state.password = password
    
            case .shouldShowSpinner(let shouldShow):
                state.isSpinnerRunning = shouldShow
    
                if shouldShow == false {
                    state.shouldLogin = false
                }
    
            case .shouldInitialize:
                state = State()
            }
    
            return state
        }
        
        
        
        deinit {
            print("loginReactor deinit")
        }
        
        public init() {
            print("loginReactor initialized.")
        }
    }

    'Swift > RxSwift' 카테고리의 다른 글

    ReactorKit 에서 API 활용하기  (0) 2022.10.15
    RxAlamofire  (0) 2022.10.03
    RxSwift Observable, Subjects and Relays  (0) 2022.10.01
    UITableView (with RxSwift)  (2) 2022.09.30
    Login Validation (simple..) RxSwift 구현  (0) 2022.09.29
Designed by Tistory.