-
Notifications
You must be signed in to change notification settings - Fork 3
Up Down
Up & Down is simple view that has a number to be increased & decreased.
We'll make simple Mode & View & Controller here.
TBD
Assume that we already have a xib or storyboard scene.
UpDownModel
has simple functionalities.
-
Increase a number
-
Decrease a number
-
Reset a number to 0
Let's define these functionalities as Action
enum UpDownAction {
case Increase
case Decrease
case Reset
}
It's very simple. Action can be any type, but enum
will be enough for most of cases.
And Model is here.
struct UpDownModel: ReducerModel {
typealias Action = UpDownAction
typealias State = Int
let initialState = 0
func reduce(state: State, with action: Action) -> State {
switch action {
case .Increase:
return state + 1
case .Decrease:
return state - 1
case .Reset:
return initialState
}
}
}
It does simply dispatch the Actions to mutation of state.
By using RecuerModel
, we can remember previous state of model.
It uses
Observable.scan
and you can find an implementation at here.
We will implement UpDownView
as combination of View an UserInteractable. This combination is general for most of cases in GUI programs.
First, let's define Events that UpDownView
can occurs.
enum UpDownEvent {
case ClickUp
case ClickDown
case ClickReset
}
It describes user's interactions as enum
.
And now, define UpDownView
struct UpDownView: View, UserInteractable {
typealias State = Int
typealias Event = UpDownEvent
...
Set required State as Int
and output Event as UpDownEvent
.
let countLabel: UILabel
let upButton: UIButton
let downButton: UIButton
let resetButton: UIButton
And actual UI components it needs.
func update(stateStream: Observable<State>) -> Disposable {
return stateStream.subscribeNext { (number) in
self.countLabel.text = "\(number)"
}
}
This is implementation of updating. It just subscribes State stream and returns disposable.
func interact() -> Observable<Event> {
return [
self.upButton.rx_tap.map{_ in Event.ClickUp},
self.downButton.rx_tap.map{_ in Event.ClickDown},
self.resetButton.rx_tap.map{_ in Event.ClickReset},
].toObservable().merge()
}
}
And provides event stream captured from UI components.
Here's full source of UpDownView
struct UpDownView: View, UserInteractable {
typealias State = Int
typealias Event = UpDownEvent
let countLabel: UILabel
let upButton: UIButton
let downButton: UIButton
let resetButton: UIButton
func update(stateStream: Observable<State>) -> Disposable {
return stateStream.subscribeNext { (number) in
self.countLabel.text = "\(number)"
}
}
func interact() -> Observable<Event> {
return [
self.upButton.rx_tap.map{_ in Event.ClickUp},
self.downButton.rx_tap.map{_ in Event.ClickDown},
self.resetButton.rx_tap.map{_ in Event.ClickReset},
].toObservable().merge()
}
}
Controller is dispatcher that converts event to action. We can make a HTTP request, background jobs, and many things at here. But all we need is conversion of Event to Action for now.
struct UpDownController: MapController {
typealias Event = UpDownEvent
typealias Action = UpDownAction
func mapEventToAction(event: Event) -> Action {
switch event {
case .ClickUp:
return Action.Increase
case .ClickDown:
return Action.Decrease
case .ClickReset:
return Action.Reset
}
}
}
MapController
is a shortcut for
func use(eventStream: Observable<Event>) -> Observable<Self.Action> {
return eventStream.map({ (event) in
switch event {
...
}
})
}
And now combine Model, View, Controller into application.
A UIViewController
will help for do that.
class UpDownViewController: UIViewController {
@IBOutlet weak var countLabel: UILabel!
@IBOutlet weak var upButton: UIButton!
@IBOutlet weak var downButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let model = UpDownModel()
let view = UpDownView(countLabel: countLabel,
upButton: upButton,
downButton: downButton,
resetButton: resetButton)
let controller = UpDownController()
combineModel(model, withView: view, controller: controller).addDisposableTo(disposeBag)
}
}
combineModel(model, withView: view, controller: controller)
can be replaced with view.update(model.manipulate(controller.use(view.interact())))
. It makes a Disposable
, so we have to dispose()
in someday. disposeBag
will do that.
import UIKit
import RxSwift
import RxCocoa
import RxMVC
enum UpDownEvent {
case ClickUp
case ClickDown
case ClickReset
}
enum UpDownAction {
case Increase
case Decrease
case Reset
}
typealias State = Int
struct UpDownModel: ReducerModel {
typealias Action = UpDownAction
typealias State = Int
let initialState = 0
func reduce(state: State, with action: Action) -> State {
switch action {
case .Increase:
return state + 1
case .Decrease:
return state - 1
case .Reset:
return initialState
}
}
}
struct UpDownView: View, UserInteractable {
typealias State = Int
typealias Event = UpDownEvent
let countLabel: UILabel
let upButton: UIButton
let downButton: UIButton
let resetButton: UIButton
func update(stateStream: Observable<State>) -> Disposable {
return stateStream.subscribeNext { (number) in
self.countLabel.text = "\(number)"
}
}
func interact() -> Observable<Event> {
return [
self.upButton.rx_tap.map{_ in Event.ClickUp},
self.downButton.rx_tap.map{_ in Event.ClickDown},
self.resetButton.rx_tap.map{_ in Event.ClickReset},
].toObservable().merge()
}
}
struct UpDownController: MapController {
typealias Event = UpDownEvent
typealias Action = UpDownAction
func mapEventToAction(event: Event) -> Action {
switch event {
case .ClickUp:
return Action.Increase
case .ClickDown:
return Action.Decrease
case .ClickReset:
return Action.Reset
}
}
}
class UpDownViewController: UIViewController {
@IBOutlet weak var countLabel: UILabel!
@IBOutlet weak var upButton: UIButton!
@IBOutlet weak var downButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let model = UpDownModel()
let view = UpDownView(countLabel: countLabel,
upButton: upButton,
downButton: downButton,
resetButton: resetButton)
let controller = UpDownController()
combineModel(model, withView: view, controller: controller).addDisposableTo(disposeBag)
}
}
Override functions of Model and Controller has no side effects. It makes your application simple, and predictable.