Swift/RxSwift
ReactorKit 으로 로그인 페이지 만들기
iosswift
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.")
}
}