[ReactorKit#2] Example : Counter 코드작성 (스토리보드 없이)
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