Skip to content

Commit

Permalink
Toggle UIButton based on Model.selected value
Browse files Browse the repository at this point in the history
  • Loading branch information
rrodrigues committed Jan 15, 2019
1 parent 4cfb353 commit 4e31dd9
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 17 deletions.
8 changes: 6 additions & 2 deletions RxCollectionViewTester.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
72C204D821E0E021003F09A4 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72C204D721E0E021003F09A4 /* ViewModel.swift */; };
B330E16621EE6E7400CB161E /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B330E16521EE6E7300CB161E /* Repository.swift */; };
B3C3512521DEB56F009AC5A3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C3512421DEB56F009AC5A3 /* AppDelegate.swift */; };
B3C3512721DEB56F009AC5A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3C3512621DEB56F009AC5A3 /* ViewController.swift */; };
B3C3512A21DEB56F009AC5A3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B3C3512821DEB56F009AC5A3 /* Main.storyboard */; };
Expand All @@ -20,6 +21,7 @@
72C204D721E0E021003F09A4 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
972E1ADCA05CCA7395450042 /* Pods_RxCollectionViewTester.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxCollectionViewTester.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9782C688C2D76C1E1D0AB269 /* Pods-RxCollectionViewTester.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxCollectionViewTester.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxCollectionViewTester/Pods-RxCollectionViewTester.debug.xcconfig"; sourceTree = "<group>"; };
B330E16521EE6E7300CB161E /* Repository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = "<group>"; };
B3C3512121DEB56F009AC5A3 /* RxCollectionViewTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxCollectionViewTester.app; sourceTree = BUILT_PRODUCTS_DIR; };
B3C3512421DEB56F009AC5A3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
B3C3512621DEB56F009AC5A3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -72,12 +74,13 @@
isa = PBXGroup;
children = (
B3C3512421DEB56F009AC5A3 /* AppDelegate.swift */,
B330E16521EE6E7300CB161E /* Repository.swift */,
B3C3512621DEB56F009AC5A3 /* ViewController.swift */,
72C204D721E0E021003F09A4 /* ViewModel.swift */,
B3C3512821DEB56F009AC5A3 /* Main.storyboard */,
B3C3512B21DEB570009AC5A3 /* Assets.xcassets */,
B3C3512D21DEB570009AC5A3 /* LaunchScreen.storyboard */,
B3C3513021DEB570009AC5A3 /* Info.plist */,
B3C3512D21DEB570009AC5A3 /* LaunchScreen.storyboard */,
B3C3512821DEB56F009AC5A3 /* Main.storyboard */,
);
path = RxCollectionViewTester;
sourceTree = "<group>";
Expand Down Expand Up @@ -224,6 +227,7 @@
B3C3512721DEB56F009AC5A3 /* ViewController.swift in Sources */,
72C204D821E0E021003F09A4 /* ViewModel.swift in Sources */,
B3C3512521DEB56F009AC5A3 /* AppDelegate.swift in Sources */,
B330E16621EE6E7400CB161E /* Repository.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
29 changes: 29 additions & 0 deletions RxCollectionViewTester/Repository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Repository.swift
// RxCollectionViewTester
//
// Created by Rui Rodrigues on 15/01/2019.
// Copyright © 2019 brownie. All rights reserved.
//

import Foundation

struct Model {
let id = UUID()
var value: Int
var selected: Bool = Bool.random()

init(_ v: Int) {
self.value = v
}
}

class Repository {

func refreshValues() -> [Model] {
return (0..<20)
.map { _ in Int.random(in: 0..<100) }
.map { Model($0) }
}

}
21 changes: 18 additions & 3 deletions RxCollectionViewTester/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class ViewController: UIViewController {

let bag = DisposeBag()

let repository = Repository()

let generateButton = UIButton.withTitle("Generate")
let showAllButton = UIButton.withTitle("Show All")
let removeAllButton = UIButton.withTitle("Remove All")
Expand All @@ -45,6 +47,7 @@ class ViewController: UIViewController {
}()

let value = PublishSubject<(id: UUID, value: Int)>()
let selectedChanged = PublishSubject<(id: UUID, selected: Bool)>()
let delete = PublishSubject<UUID>()

override func viewDidLoad() {
Expand Down Expand Up @@ -83,23 +86,30 @@ class ViewController: UIViewController {
// the two lines below are so we can avoid dealing with self in the closure.
let value = self.value
let delete = self.delete

let selectedChanged = self.selectedChanged

let input = Input(
value: value,
selectedChanged: selectedChanged,
add: generateButton.rx.tap.asObservable(),
delete: delete
)
let viewModel = ViewModel(input, initialValues: [])
let viewModel = ViewModel(input, refreshTask: self.repository.refreshValues)

viewModel.counters
.bind(to: collectionView.rx.items(cellIdentifier: CollectionViewCell.identifier, cellType: CollectionViewCell.self)) { index, element, cell in
cell.configure(with: { input in
let vm = CellViewModel(input, initialValue: element.value)
let vm = CellViewModel(input, initialValue: element)
// Remember the value property tracks the current value of the counter
vm.value
.map { (id: element.id, value: $0) } // tell the main view model which counter's value this is
.bind(to: value)
.disposed(by: cell.bag)

vm.selectedChanged
.map { (id: element.id, selected: $0)}
.bind(to: selectedChanged)
.disposed(by: cell.bag)

vm.delete
.map { element.id } // tell the main view model which counter should be deleted
Expand Down Expand Up @@ -189,6 +199,7 @@ class CollectionViewCell: UICollectionViewCell {
let input = CellInput(
plus: plus.rx.tap.asObservable(),
minus: minus.rx.tap.asObservable(),
select: check.rx.tap.asObservable(),
delete: delete.rx.tap.asObservable()
)
// create the view model from the factory
Expand All @@ -197,6 +208,10 @@ class CollectionViewCell: UICollectionViewCell {
viewModel.label
.bind(to: label.rx.text)
.disposed(by: bag)

viewModel.selected
.bind(to: check.rx.isSelected)
.disposed(by: bag)
}

override func prepareForReuse() {
Expand Down
39 changes: 27 additions & 12 deletions RxCollectionViewTester/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,43 @@ import RxSwift

struct Input {
let value: Observable<(id: UUID, value: Int)>
let selectedChanged: Observable<(id: UUID, selected: Bool)>
let add: Observable<Void>
let delete: Observable<UUID>
}

struct ViewModel {
let counters: Observable<[(id: UUID, value: Int)]>
let counters: Observable<[Model]>
}

extension ViewModel {
private enum Action {
case add
case value(id: UUID, value: Int)
case selectedChanged(id: UUID, selected: Bool)
case delete(id: UUID)
}

init(_ input: Input, initialValues: [(id: UUID, value: Int)]) {
init(_ input: Input, refreshTask: @escaping () -> [Model]) {
let addAction = input.add.map { Action.add }
let valueAction = input.value.map(Action.value)
let selectedChangedAction = input.selectedChanged.map(Action.selectedChanged)
let deleteAction = input.delete.map(Action.delete)
counters = Observable.merge(addAction, valueAction, deleteAction)

counters = Observable.merge(addAction, valueAction, selectedChangedAction, deleteAction)
.startWith(.add)
.scan(into: initialValues) { model, new in
.scan(into: []) { model, new in
switch new {
case .add:
model = (0..<20)
.map { _ in Int.random(in: 0..<100) }
.map { (UUID(), $0) }
model = refreshTask()
case .value(let id, let value):
if let index = model.index(where: { $0.id == id }) {
model[index].value = value
}
case .selectedChanged(let id, let selected):
if let index = model.index(where: { $0.id == id }) {
model[index].selected = selected
}
case .delete(let id):
if let index = model.index(where: { $0.id == id }) {
model.remove(at: index)
Expand All @@ -54,26 +60,35 @@ extension ViewModel {
struct CellInput {
let plus: Observable<Void>
let minus: Observable<Void>
let select: Observable<Void>
let delete: Observable<Void>
}

struct CellViewModel {
let label: Observable<String>
let selected: Observable<Bool>

let selectedChanged: Observable<Bool>
let value: Observable<Int>
let delete: Observable<Void>
}

extension CellViewModel {
init(_ input: CellInput, initialValue: Int) {
init(_ input: CellInput, initialValue: Model) {
let add = input.plus.map { 1 } // plus adds one to the value
let subtract = input.minus.map { -1 } // minus subtracts one

selectedChanged = input.select
.scan(initialValue.selected, accumulator: { val, _ in !val })
selected = selectedChanged
.startWith(initialValue.selected)

value = Observable.merge(add, subtract)
.scan(initialValue, accumulator: +) // the logic is here
.scan(initialValue.value, accumulator: +) // the logic is here

label = value
.startWith(initialValue)
.map { "number is \($0)" } // create the string from the value
label = Observable.combineLatest(value, selectedChanged)
.startWith((initialValue.value, initialValue.selected))
.map { "number is \($0) | selected \($1)" } // create the string from the value
delete = input.delete // delete is just a passthrough in this case
}
}

0 comments on commit 4e31dd9

Please sign in to comment.