diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Bond-App/AppDelegate.swift b/Bond-App/AppDelegate.swift index a272a07b..36cc993f 100644 --- a/Bond-App/AppDelegate.swift +++ b/Bond-App/AppDelegate.swift @@ -6,20 +6,18 @@ // Copyright © 2016 Swift Bond. All rights reserved. // -import UIKit import Bond import ReactiveKit +import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UIViewController() window?.makeKeyAndVisible() return true } } - diff --git a/Extensions/Bond+Realm.swift b/Extensions/Bond+Realm.swift index 4c4b97e9..2cb2f9c6 100644 --- a/Extensions/Bond+Realm.swift +++ b/Extensions/Bond+Realm.swift @@ -28,44 +28,42 @@ import RealmSwift /* -// Get the default Realm -let realm = try! Realm() + // Get the default Realm + let realm = try! Realm() -// Convert realm results into a changeset signal -let puppies = realm.objects(Dog.self).toChangesetSignal() + // Convert realm results into a changeset signal + let puppies = realm.objects(Dog.self).toChangesetSignal() -// Changeset signals can then be bound to table or collection views -puppies.suppressError(logging: true).bind(to: tableView, cellType: UITableViewCell.self) { (cell, dog) in - cell.textLabel?.text = dog.name -} + // Changeset signals can then be bound to table or collection views + puppies.suppressError(logging: true).bind(to: tableView, cellType: UITableViewCell.self) { (cell, dog) in + cell.textLabel?.text = dog.name + } -// Adding something to the results will cause the table view to insert the respective row -DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - try! realm.write { - realm.add(myDog) - } -} + // Adding something to the results will cause the table view to insert the respective row + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + try! realm.write { + realm.add(myDog) + } + } -*/ + */ public extension RealmCollectionChange where CollectionType: Swift.Collection, CollectionType.Index == Int { - - public func toOrderedCollectionChangeset() throws -> OrderedCollectionChangeset { + func toOrderedCollectionChangeset() throws -> OrderedCollectionChangeset { switch self { - case .initial(let collection): + case let .initial(collection): return OrderedCollectionChangeset(collection: collection, diff: OrderedCollectionDiff()) - case .update(let collection, let deletions, let insertions, let modifications): + case let .update(collection, deletions, insertions, modifications): let diff = OrderedCollectionDiff(inserts: insertions, deletes: deletions, updates: modifications, moves: []) return OrderedCollectionChangeset(collection: collection, diff: diff) - case .error(let error): + case let .error(error): throw error } } } public extension Results { - - public func toChangesetSignal() -> Signal>, NSError> { + func toChangesetSignal() -> Signal>, NSError> { return Signal { observer in let token = self.observe { change in do { @@ -82,16 +80,15 @@ public extension Results { } public extension Results: QueryableSectionedDataSourceProtocol { - - public var numberOfSections: Int { + var numberOfSections: Int { return 1 } - public func numberOfItems(inSection section: Int) -> Int { + func numberOfItems(inSection _: Int) -> Int { return count } - public func item(at indexPath: IndexPath) -> Element { + func item(at indexPath: IndexPath) -> Element { return self[indexPath.row] } } diff --git a/Playground-iOS.playground/Pages/Key Value Observing.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/Key Value Observing.xcplaygroundpage/Contents.swift index dd6ee191..265f91cb 100644 --- a/Playground-iOS.playground/Pages/Key Value Observing.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/Key Value Observing.xcplaygroundpage/Contents.swift @@ -1,12 +1,12 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. -import UIKit import Bond -import ReactiveKit import PlaygroundSupport +import ReactiveKit +import UIKit class Contact: NSObject { @objc dynamic var name: String? = "n/a" diff --git a/Playground-iOS.playground/Pages/Observable Collections.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/Observable Collections.xcplaygroundpage/Contents.swift index 7c098e00..cbfaad3f 100644 --- a/Playground-iOS.playground/Pages/Observable Collections.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/Observable Collections.xcplaygroundpage/Contents.swift @@ -1,21 +1,21 @@ //: Playground - noun: a place where people can play -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. import Bond -import ReactiveKit import PlaygroundSupport +import ReactiveKit // Array let a = MutableObservableArray([1, 2, 3]) -a.observeNext { (cs) in +a.observeNext { cs in print(cs.diff, cs.patch) } -a.batchUpdate { (a) in +a.batchUpdate { a in a.append(1) a.append(2) } @@ -26,7 +26,7 @@ let array2D = MutableObservableArray2D(Array2D(sectionsWithItems ("Cities", ["Paris", "Berlin"]) ])) -array2D.observeNext { (cs) in +array2D.observeNext { cs in print(cs.collection, cs.diff, cs.patch) } @@ -39,7 +39,7 @@ array2D.appendItem("France", toSectionAt: 1) let s = MutableObservableSet(Set([1, 4, 3])) -s.sortedCollection().mapCollection { $0 * 2 }.filterCollection { $0 > 2 }.observeNext { (changeset) in +s.sortedCollection().mapCollection { $0 * 2 }.filterCollection { $0 > 2 }.observeNext { changeset in print(changeset.collection, changeset.diff, changeset.patch) } @@ -49,7 +49,7 @@ s.insert(5) let dictionary = MutableObservableDictionary(["A": 1]) -dictionary.sortedCollection(by: { $0.key < $1.key }).mapCollection({ "\($0.key): \($0.value)"}).observeNext { (changeset) in +dictionary.sortedCollection(by: { $0.key < $1.key }).mapCollection { "\($0.key): \($0.value)" }.observeNext { changeset in print(changeset.collection, changeset.diff, changeset.patch) } @@ -59,7 +59,7 @@ dictionary["B"] = 2 let data = MutableObservableCollection(Data([0x0A, 0x0B])) -data.observeNext { (changeset) in +data.observeNext { changeset in print(changeset.collection, changeset.diff, changeset.patch) } @@ -75,20 +75,20 @@ var t = TreeArray([ TreeNode("Child 002", [ TreeNode("Child 0020"), TreeNode("Child 0021") - ]) - ]), + ]) + ]), TreeNode("Child 01") - ]) +]) let ot = MutableObservableTree(t) -ot.observeNext { (cs) in +ot.observeNext { cs in print(cs.collection, cs.diff, cs.patch) } ot.insert(TreeNode("New"), at: [0, 0]) -ot.batchUpdate { (ot) in +ot.batchUpdate { ot in ot.remove(at: [0, 0]) ot.remove(at: [0, 2, 1]) } diff --git a/Playground-iOS.playground/Pages/Trees.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/Trees.xcplaygroundpage/Contents.swift index 1b12e60f..f8e48eba 100644 --- a/Playground-iOS.playground/Pages/Trees.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/Trees.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import UIKit -import Bond // Tree node is a tree with a single root @@ -48,7 +48,6 @@ print(t.depthFirst.randomElement()!) // Custom trees extension UIView: TreeProtocol { - public var children: [UIView] { return subviews } @@ -61,5 +60,4 @@ print(v.breadthFirst.map { type(of: $0) }) print(v[childAt: [0, 0]]) - //: [Next](@next) diff --git a/Playground-iOS.playground/Pages/UICollectionView+ObservableArray2D.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/UICollectionView+ObservableArray2D.xcplaygroundpage/Contents.swift index 7e66fb0f..9899be73 100644 --- a/Playground-iOS.playground/Pages/UICollectionView+ObservableArray2D.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/UICollectionView+ObservableArray2D.xcplaygroundpage/Contents.swift @@ -1,16 +1,15 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import PlaygroundSupport -import UIKit -import Bond import ReactiveKit +import UIKit class Cell: UICollectionViewCell { - let titleLabel = UILabel() override init(frame: CGRect) { @@ -20,7 +19,7 @@ class Cell: UICollectionViewCell { contentView.backgroundColor = .white } - required init?(coder aDecoder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -31,13 +30,12 @@ class Cell: UICollectionViewCell { } class SectionHeader: UICollectionReusableView { - override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .red } - required init?(coder aDecoder: NSCoder) { + required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } } @@ -59,7 +57,6 @@ PlaygroundPage.current.needsIndefiniteExecution = true // Using custom binder to provide table view header titles class CustomBinder: CollectionViewBinderDataSource, UICollectionViewDelegateFlowLayout where Changeset.Collection == Array2D { - override var collectionView: UICollectionView? { didSet { collectionView?.delegate = self @@ -68,7 +65,7 @@ class CustomBinder: CollectionViewBinde // Due to a bug in Swift related to generic subclases, we have to specify ObjC delegate method name // if it's different than Swift name (https://bugs.swift.org/browse/SR-2817). - @objc (collectionView:viewForSupplementaryElementOfKind:atIndexPath:) + @objc(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { switch kind { case UICollectionView.elementKindSectionHeader: @@ -79,7 +76,7 @@ class CustomBinder: CollectionViewBinde } } - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + func collectionView(_: UICollectionView, layout _: UICollectionViewLayout, referenceSizeForHeaderInSection _: Int) -> CGSize { return CGSize(width: 200, height: 20) } } @@ -91,12 +88,11 @@ class CustomBinder: CollectionViewBinde let initialData = Array2D(sectionsWithItems: [ ("A", [1, 2]), ("B", [10, 20]) - ]) +]) let data = MutableObservableArray2D(initialData) - -data.bind(to: collectionView, cellType: Cell.self, using: CustomBinder()) { (cell, item) in +data.bind(to: collectionView, cellType: Cell.self, using: CustomBinder()) { cell, item in cell.titleLabel.text = "\(item)" } @@ -105,7 +101,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 1) { } DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - data.batchUpdate { (data) in + data.batchUpdate { data in data.appendItem(4, toSectionAt: 0) data.insert(section: "Aa", at: 1) data.appendItem(100, toSectionAt: 1) @@ -120,4 +116,5 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { DispatchQueue.main.asyncAfter(deadline: .now() + 4) { data.replaceItems(ofSectionAt: 1, with: [1, 100, 20], performDiff: true) } + //: [Next](@next) diff --git a/Playground-iOS.playground/Pages/UIPickerView+ObservableArray2D.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/UIPickerView+ObservableArray2D.xcplaygroundpage/Contents.swift index 67492dab..3e47af93 100644 --- a/Playground-iOS.playground/Pages/UIPickerView+ObservableArray2D.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/UIPickerView+ObservableArray2D.xcplaygroundpage/Contents.swift @@ -1,13 +1,13 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import PlaygroundSupport -import UIKit -import Bond import ReactiveKit +import UIKit let pickerView = UIPickerView() pickerView.frame.size = CGSize(width: 300, height: 300) @@ -31,7 +31,7 @@ data.bind(to: pickerView) // Handle cell selection let selectedRow = pickerView.reactive.selectedRow -selectedRow.observeNext { (row, component) in +selectedRow.observeNext { row, component in print("selected", row, component) } @@ -41,7 +41,7 @@ let selectedPair = selectedRow.scan([0, 0]) { (pair, rowAndComponent) -> [Int] i return pair } -selectedPair.observeNext { (pair) in +selectedPair.observeNext { pair in print("selected indices", pair) let items = pair.enumerated().map { data[itemAt: [$0.offset, $0.element]] diff --git a/Playground-iOS.playground/Pages/UITableView+ObservableArray.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/UITableView+ObservableArray.xcplaygroundpage/Contents.swift index b42ed122..68a7930b 100644 --- a/Playground-iOS.playground/Pages/UITableView+ObservableArray.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/UITableView+ObservableArray.xcplaygroundpage/Contents.swift @@ -1,13 +1,13 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import PlaygroundSupport -import UIKit -import Bond import ReactiveKit +import UIKit let tableView = UITableView() tableView.frame.size = CGSize(width: 300, height: 600) @@ -18,7 +18,7 @@ PlaygroundPage.current.needsIndefiniteExecution = true let data = MutableObservableArray(["A", "B", "C"]) -data.bind(to: tableView, cellType: UITableViewCell.self) { (cell, string) in +data.bind(to: tableView, cellType: UITableViewCell.self) { cell, string in cell.textLabel?.text = string } @@ -27,7 +27,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 1) { } DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - data.batchUpdate { (data) in + data.batchUpdate { data in data.remove(at: 0) data[0] = "W" } @@ -38,7 +38,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { } // Handle cell selection -tableView.reactive.selectedRowIndexPath.observeNext { (indexPath) in +tableView.reactive.selectedRowIndexPath.observeNext { indexPath in print("selected row", indexPath) } diff --git a/Playground-iOS.playground/Pages/UITableView+ObservableArray2D.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/UITableView+ObservableArray2D.xcplaygroundpage/Contents.swift index fc57b9f2..a756c79d 100644 --- a/Playground-iOS.playground/Pages/UITableView+ObservableArray2D.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/UITableView+ObservableArray2D.xcplaygroundpage/Contents.swift @@ -1,13 +1,13 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import PlaygroundSupport -import UIKit -import Bond import ReactiveKit +import UIKit let tableView = UITableView() tableView.frame.size = CGSize(width: 300, height: 600) @@ -19,9 +19,8 @@ PlaygroundPage.current.needsIndefiniteExecution = true // Using custom binder to provide table view header titles class CustomBinder: TableViewBinderDataSource where Changeset.Collection == Array2D { - // Important: Annotate UITableViewDataSource methods with `@objc` in the subclass, otherwise UIKit will not see your method! - @objc func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + @objc func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { return changeset?.collection[sectionAt: section].metadata } } @@ -37,7 +36,7 @@ let initialData = Array2D(sectionsWithItems: [ let data = MutableObservableArray2D(initialData) -data.bind(to: tableView, cellType: UITableViewCell.self, using: CustomBinder()) { (cell, item) in +data.bind(to: tableView, cellType: UITableViewCell.self, using: CustomBinder()) { cell, item in cell.textLabel?.text = "\(item)" } @@ -46,7 +45,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 1) { } DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - data.batchUpdate { (data) in + data.batchUpdate { data in data.appendItem(4, toSectionAt: 0) data.insert(section: "Aa", at: 1) data.appendItem(100, toSectionAt: 1) diff --git a/Playground-iOS.playground/Pages/UITableView+Signal+Diff.xcplaygroundpage/Contents.swift b/Playground-iOS.playground/Pages/UITableView+Signal+Diff.xcplaygroundpage/Contents.swift index 58c1d901..e3aae4d3 100644 --- a/Playground-iOS.playground/Pages/UITableView+Signal+Diff.xcplaygroundpage/Contents.swift +++ b/Playground-iOS.playground/Pages/UITableView+Signal+Diff.xcplaygroundpage/Contents.swift @@ -1,13 +1,13 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" -///: targets with any iOS Simulator as a destination. +/// : Before running the playground, make sure to build "Bond-iOS" and "PlaygroundSupport" +/// : targets with any iOS Simulator as a destination. +import Bond import Foundation import PlaygroundSupport -import UIKit -import Bond import ReactiveKit +import UIKit let tableView = UITableView() tableView.frame.size = CGSize(width: 300, height: 300) @@ -21,15 +21,15 @@ let pulse = SafeSignal(sequence: 0..., interval: 1) // A signal of [String] let data = SafeSignal(sequence: [ - ["A"], - ["A", "B", "C"], - ["A", "C"], - ["C", "A"] - ]) + ["A"], + ["A", "B", "C"], + ["A", "C"], + ["C", "A"] +]) .zip(with: pulse) { data, _ in data } // add 1 second delay between events .diff() // diff each new array against the previous one -data.bind(to: tableView, cellType: UITableViewCell.self) { (cell, string) in +data.bind(to: tableView, cellType: UITableViewCell.self) { cell, string in cell.textLabel?.text = string } diff --git a/Playground-macOS.playground/Pages/NSOutlineView Bindings Simpler.xcplaygroundpage/Contents.swift b/Playground-macOS.playground/Pages/NSOutlineView Bindings Simpler.xcplaygroundpage/Contents.swift index 716448d3..58b024d5 100644 --- a/Playground-macOS.playground/Pages/NSOutlineView Bindings Simpler.xcplaygroundpage/Contents.swift +++ b/Playground-macOS.playground/Pages/NSOutlineView Bindings Simpler.xcplaygroundpage/Contents.swift @@ -1,12 +1,12 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" -///: targets with Mac as a destination. +/// : Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" +/// : targets with Mac as a destination. +import Bond +import Cocoa import Foundation import PlaygroundSupport -import Cocoa -import Bond import ReactiveKit let outlineView = NSOutlineView() @@ -30,7 +30,6 @@ outlineView.outlineTableColumn = column // Let's make NSView a tree... extension NSView: TreeProtocol { - public var children: [NSView] { return subviews } diff --git a/Playground-macOS.playground/Pages/NSOutlineView Bindings.xcplaygroundpage/Contents.swift b/Playground-macOS.playground/Pages/NSOutlineView Bindings.xcplaygroundpage/Contents.swift index 6111d795..2f7a8a77 100644 --- a/Playground-macOS.playground/Pages/NSOutlineView Bindings.xcplaygroundpage/Contents.swift +++ b/Playground-macOS.playground/Pages/NSOutlineView Bindings.xcplaygroundpage/Contents.swift @@ -1,12 +1,12 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" -///: targets with Mac as a destination. +/// : Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" +/// : targets with Mac as a destination. +import Bond +import Cocoa import Foundation import PlaygroundSupport -import Cocoa -import Bond import ReactiveKit let outlineView = NSOutlineView() @@ -58,7 +58,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 4) { } DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - data.batchUpdate { (data) in + data.batchUpdate { data in data.move(from: [2], to: [2, 0, 0]) data.insert(TreeNode("G"), at: [2]) data.insert(TreeNode("Dd"), at: [1, 0]) diff --git a/Playground-macOS.playground/Pages/NSTableView Bindings.xcplaygroundpage/Contents.swift b/Playground-macOS.playground/Pages/NSTableView Bindings.xcplaygroundpage/Contents.swift index de85653f..ef208b5d 100644 --- a/Playground-macOS.playground/Pages/NSTableView Bindings.xcplaygroundpage/Contents.swift +++ b/Playground-macOS.playground/Pages/NSTableView Bindings.xcplaygroundpage/Contents.swift @@ -1,13 +1,13 @@ //: [Previous](@previous) -///: Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" -///: targets with Mac as a destination. +/// : Before running the playground, make sure to build "Bond-macOS" and "PlaygroundSupport" +/// : targets with Mac as a destination. import Foundation -import PlaygroundSupport -import Cocoa import Bond +import Cocoa +import PlaygroundSupport import ReactiveKit let tableView = NSTableView() @@ -41,7 +41,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 2) { } DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - data.batchUpdate { (data) in + data.batchUpdate { data in data.remove(at: 0) data[0] = "Jerry" data.append("Jenne") @@ -49,7 +49,7 @@ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { } DispatchQueue.main.asyncAfter(deadline: .now() + 4) { - data.replace(with: ["Ann", "Jerry"], performDiff: true) + data.replace(with: ["Ann", "Jerry"], performDiff: true) } //: [Next](@next) diff --git a/Sources/Bond/AppKit/NSAppearanceCustomization.swift b/Sources/Bond/AppKit/NSAppearanceCustomization.swift index b02fd554..2b4a3acd 100644 --- a/Sources/Bond/AppKit/NSAppearanceCustomization.swift +++ b/Sources/Bond/AppKit/NSAppearanceCustomization.swift @@ -24,14 +24,13 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSObject, Base: NSAppearanceCustomization { - - public var appearance: Bond { - return bond(context: .immediateOnMain) { $0.appearance = $1 } + extension ReactiveExtensions where Base: NSObject, Base: NSAppearanceCustomization { + public var appearance: Bond { + return bond(context: .immediateOnMain) { $0.appearance = $1 } + } } -} #endif diff --git a/Sources/Bond/AppKit/NSButton.swift b/Sources/Bond/AppKit/NSButton.swift index 77f278a2..6e5ad5bd 100644 --- a/Sources/Bond/AppKit/NSButton.swift +++ b/Sources/Bond/AppKit/NSButton.swift @@ -24,102 +24,101 @@ #if os(macOS) -import AppKit -import ReactiveKit - -extension ReactiveExtensions where Base: NSButton { - - public var state: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - get: { $0.state }, - set: { $0.state = $1 } - ) - } - - public var title: Bond { - return bond { $0.title = $1 } - } - - public var alternateTitle: Bond { - return bond { $0.alternateTitle = $1 } - } - - public var image: Bond { - return bond { $0.image = $1 } - } - - public var alternateImage: Bond { - return bond { $0.alternateImage = $1 } - } - - public var imagePosition: Bond { - return bond { $0.imagePosition = $1 } - } - - public var imageScaling: Bond { - return bond { $0.imageScaling = $1 } - } - - @available(macOS 10.12, *) - public var imageHugsTitle: Bond { - return bond { $0.imageHugsTitle = $1 } - } - - public var isBordered: Bond { - return bond { $0.isBordered = $1 } - } - - public var isTransparent: Bond { - return bond { $0.isTransparent = $1 } - } - - public var keyEquivalent: Bond { - return bond { $0.keyEquivalent = $1 } - } - - public var keyEquivalentModifierMask: Bond { - return bond { $0.keyEquivalentModifierMask = $1 } - } - - @available(macOS 10.10.3, *) - public var isSpringLoaded: Bond { - return bond { $0.isSpringLoaded = $1 } - } - - @available(macOS 10.10.3, *) - public var maxAcceleratorLevel: Bond { - return bond { $0.maxAcceleratorLevel = $1 } - } - - @available(macOS 10.12.2, *) - public var bezelColor: Bond { - return bond { $0.bezelColor = $1 } - } - - public var attributedTitle: Bond { - return bond { $0.attributedTitle = $1 } - } - - public var attributedAlternateTitle: Bond { - return bond { $0.attributedAlternateTitle = $1 } - } - - public var bezelStyle: Bond { - return bond { $0.bezelStyle = $1 } - } - - public var allowsMixedState: Bond { - return bond { $0.allowsMixedState = $1 } - } - - public var showsBorderOnlyWhileMouseInside: Bond { - return bond { $0.showsBorderOnlyWhileMouseInside = $1 } - } - - public var sound: Bond { - return bond { $0.sound = $1 } + import AppKit + import ReactiveKit + + extension ReactiveExtensions where Base: NSButton { + public var state: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + get: { $0.state }, + set: { $0.state = $1 } + ) + } + + public var title: Bond { + return bond { $0.title = $1 } + } + + public var alternateTitle: Bond { + return bond { $0.alternateTitle = $1 } + } + + public var image: Bond { + return bond { $0.image = $1 } + } + + public var alternateImage: Bond { + return bond { $0.alternateImage = $1 } + } + + public var imagePosition: Bond { + return bond { $0.imagePosition = $1 } + } + + public var imageScaling: Bond { + return bond { $0.imageScaling = $1 } + } + + @available(macOS 10.12, *) + public var imageHugsTitle: Bond { + return bond { $0.imageHugsTitle = $1 } + } + + public var isBordered: Bond { + return bond { $0.isBordered = $1 } + } + + public var isTransparent: Bond { + return bond { $0.isTransparent = $1 } + } + + public var keyEquivalent: Bond { + return bond { $0.keyEquivalent = $1 } + } + + public var keyEquivalentModifierMask: Bond { + return bond { $0.keyEquivalentModifierMask = $1 } + } + + @available(macOS 10.10.3, *) + public var isSpringLoaded: Bond { + return bond { $0.isSpringLoaded = $1 } + } + + @available(macOS 10.10.3, *) + public var maxAcceleratorLevel: Bond { + return bond { $0.maxAcceleratorLevel = $1 } + } + + @available(macOS 10.12.2, *) + public var bezelColor: Bond { + return bond { $0.bezelColor = $1 } + } + + public var attributedTitle: Bond { + return bond { $0.attributedTitle = $1 } + } + + public var attributedAlternateTitle: Bond { + return bond { $0.attributedAlternateTitle = $1 } + } + + public var bezelStyle: Bond { + return bond { $0.bezelStyle = $1 } + } + + public var allowsMixedState: Bond { + return bond { $0.allowsMixedState = $1 } + } + + public var showsBorderOnlyWhileMouseInside: Bond { + return bond { $0.showsBorderOnlyWhileMouseInside = $1 } + } + + public var sound: Bond { + return bond { $0.sound = $1 } + } } -} #endif diff --git a/Sources/Bond/AppKit/NSCollectionView+DataSource.swift b/Sources/Bond/AppKit/NSCollectionView+DataSource.swift index d576343c..59807662 100644 --- a/Sources/Bond/AppKit/NSCollectionView+DataSource.swift +++ b/Sources/Bond/AppKit/NSCollectionView+DataSource.swift @@ -24,225 +24,217 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -private var CollectionViewBinderDataSourceAssociationKey = "CollectionViewBinderDataSource" + private var CollectionViewBinderDataSourceAssociationKey = "CollectionViewBinderDataSource" -open class CollectionViewBinderDataSource: NSObject, NSCollectionViewDataSource { + open class CollectionViewBinderDataSource: NSObject, NSCollectionViewDataSource { + public var createCell: ((Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem)? - public var createCell: ((Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem)? - - public var changeset: Changeset? { - didSet { - if let changeset = changeset, oldValue != nil { - applyChangeset(changeset) - } else { - collectionView?.animator().reloadData() + public var changeset: Changeset? { + didSet { + if let changeset = changeset, oldValue != nil { + applyChangeset(changeset) + } else { + collectionView?.animator().reloadData() + } } } - } - public weak var collectionView: NSCollectionView? { - didSet { - guard let collectionView = collectionView else { return } - associateWithCollectionView(collectionView) + public weak var collectionView: NSCollectionView? { + didSet { + guard let collectionView = collectionView else { return } + associateWithCollectionView(collectionView) + } } - } - public override init() { - self.createCell = nil - super.init() - } - - /// - parameter createCell: A closure that creates cell for a given collection view and configures it with the given data source at the given index path. - public init(_ createCell: @escaping (Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem) { - self.createCell = createCell - } - - // MARK: - NSCollectionViewDataSource + public override init() { + createCell = nil + super.init() + } - public func numberOfSections(in collectionView: NSCollectionView) -> Int { - return changeset?.collection.numberOfSections ?? 0 - } + /// - parameter createCell: A closure that creates cell for a given collection view and configures it with the given data source at the given index path. + public init(_ createCell: @escaping (Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem) { + self.createCell = createCell + } - public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { - return changeset?.collection.numberOfItems(inSection: section) ?? 0 - } + // MARK: - NSCollectionViewDataSource - public func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { - guard let changeset = changeset else { fatalError() } - if let createCell = createCell { - return createCell(changeset.collection, indexPath, collectionView) - } else { - fatalError("Subclass of CollectionViewBinderDataSource should override and implement \(#function) method if they do not initialize a `createCell` closure.") + public func numberOfSections(in _: NSCollectionView) -> Int { + return changeset?.collection.numberOfSections ?? 0 } - } - open func applyChangeset(_ changeset: Changeset) { - guard let collectionView = collectionView else { return } - let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } - if diff.isEmpty { - collectionView.animator().reloadData() - } else { - collectionView.animator() - .performBatchUpdates({ - self.applyChangesetDiff(diff) - }, completionHandler: nil) + public func collectionView(_: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return changeset?.collection.numberOfItems(inSection: section) ?? 0 } - } - open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { - guard let collectionView = collectionView else { return } - let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } - if !insertedSections.isEmpty { - collectionView.animator().insertSections(IndexSet(insertedSections)) - } - let insertedItems = Set(diff.inserts.filter { $0.count == 2 }) - if !insertedItems.isEmpty { - collectionView.animator().insertItems(at: insertedItems) - } - let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } - if !deletedSections.isEmpty { - collectionView.animator().deleteSections(IndexSet(deletedSections)) - } - let deletedItems = Set(diff.deletes.filter { $0.count == 2 }) - if !deletedItems.isEmpty { - collectionView.animator().deleteItems(at: deletedItems) - } - let updatedItems = Set(diff.updates.filter { $0.count == 2 }) - if !updatedItems.isEmpty { - collectionView.animator().reloadItems(at: updatedItems) - } - let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } - if !updatedSections.isEmpty { - collectionView.animator().reloadSections(IndexSet(updatedSections)) - } - for move in diff.moves { - if move.from.count == 2, move.to.count == 2 { - collectionView.animator().moveItem(at: move.from, to: move.to) - } else if move.from.count == 1, move.to.count == 1 { - collectionView.animator().moveSection(move.from[0], toSection: move.to[0]) + public func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + guard let changeset = changeset else { fatalError() } + if let createCell = createCell { + return createCell(changeset.collection, indexPath, collectionView) + } else { + fatalError("Subclass of CollectionViewBinderDataSource should override and implement \(#function) method if they do not initialize a `createCell` closure.") } } - } - private func associateWithCollectionView(_ collectionView: NSCollectionView) { - objc_setAssociatedObject(collectionView, &CollectionViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if collectionView.reactive.hasProtocolProxy(for: NSCollectionViewDataSource.self) { - collectionView.reactive.dataSource.forwardTo = self - } else { - collectionView.dataSource = self + open func applyChangeset(_ changeset: Changeset) { + guard let collectionView = collectionView else { return } + let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } + if diff.isEmpty { + collectionView.animator().reloadData() + } else { + collectionView.animator() + .performBatchUpdates({ + self.applyChangesetDiff(diff) + }, completionHandler: nil) + } } - } - -} -extension CollectionViewBinderDataSource { - - public class ReloadingBinder: CollectionViewBinderDataSource { - public override func applyChangeset(_ changeset: Changeset) { - collectionView?.animator().reloadData() + open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { + guard let collectionView = collectionView else { return } + let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } + if !insertedSections.isEmpty { + collectionView.animator().insertSections(IndexSet(insertedSections)) + } + let insertedItems = Set(diff.inserts.filter { $0.count == 2 }) + if !insertedItems.isEmpty { + collectionView.animator().insertItems(at: insertedItems) + } + let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } + if !deletedSections.isEmpty { + collectionView.animator().deleteSections(IndexSet(deletedSections)) + } + let deletedItems = Set(diff.deletes.filter { $0.count == 2 }) + if !deletedItems.isEmpty { + collectionView.animator().deleteItems(at: deletedItems) + } + let updatedItems = Set(diff.updates.filter { $0.count == 2 }) + if !updatedItems.isEmpty { + collectionView.animator().reloadItems(at: updatedItems) + } + let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } + if !updatedSections.isEmpty { + collectionView.animator().reloadSections(IndexSet(updatedSections)) + } + for move in diff.moves { + if move.from.count == 2, move.to.count == 2 { + collectionView.animator().moveItem(at: move.from, to: move.to) + } else if move.from.count == 1, move.to.count == 1 { + collectionView.animator().moveSection(move.from[0], toSection: move.to[0]) + } + } } - } - -} - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { - /// Binds the signal of data source elements to the given collection view. - /// - /// - parameters: - /// - collectionView: A collection view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. - @discardableResult - public func bind(to collectionView: NSCollectionView, createCell: @escaping (Element.Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem) -> Disposable { - let binder = CollectionViewBinderDataSource(createCell) - return bind(to: collectionView, using: binder) + private func associateWithCollectionView(_ collectionView: NSCollectionView) { + objc_setAssociatedObject(collectionView, &CollectionViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if collectionView.reactive.hasProtocolProxy(for: NSCollectionViewDataSource.self) { + collectionView.reactive.dataSource.forwardTo = self + } else { + collectionView.dataSource = self + } + } } - /// Binds the signal of data source elements to the given collection view. - /// - /// - parameters: - /// - collectionView: A collection view that should display the data from the data source. - /// - binder: A `CollectionViewBinder` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. - @discardableResult - public func bind(to collectionView: NSCollectionView, using binder: CollectionViewBinderDataSource) -> Disposable { - binder.collectionView = collectionView - return bind(to: collectionView) { _, changeset in - binder.changeset = changeset.asSectionedDataSourceChangeset + extension CollectionViewBinderDataSource { + public class ReloadingBinder: CollectionViewBinderDataSource { + public override func applyChangeset(_: Changeset) { + collectionView?.animator().reloadData() + } } } -} - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { - - /// Binds the signal of data source elements to the given collection view. - /// - /// - parameters: - /// - collectionView: A collectionView view that should display the data from the data source. - /// - itemType: A type of the cells that should display the data. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to collectionView: NSCollectionView, itemType: Item.Type, configureItem: @escaping (Item, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifierString = String(describing: Item.self) - let identifier = NSUserInterfaceItemIdentifier(rawValue: identifierString) - let bundle = Bundle(for: Item.self) - if bundle.path(forResource: identifierString, ofType: "nib") != nil { - let nib = NSNib(nibNamed: identifierString, bundle: bundle) - collectionView.register(nib, forItemWithIdentifier: identifier) - } else { - collectionView.register(itemType as AnyClass, forItemWithIdentifier: identifier) + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given collection view. + /// + /// - parameters: + /// - collectionView: A collection view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. + @discardableResult + public func bind(to collectionView: NSCollectionView, createCell: @escaping (Element.Changeset.Collection, IndexPath, NSCollectionView) -> NSCollectionViewItem) -> Disposable { + let binder = CollectionViewBinderDataSource(createCell) + return bind(to: collectionView, using: binder) + } + + /// Binds the signal of data source elements to the given collection view. + /// + /// - parameters: + /// - collectionView: A collection view that should display the data from the data source. + /// - binder: A `CollectionViewBinder` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. + @discardableResult + public func bind(to collectionView: NSCollectionView, using binder: CollectionViewBinderDataSource) -> Disposable { + binder.collectionView = collectionView + return bind(to: collectionView) { _, changeset in + binder.changeset = changeset.asSectionedDataSourceChangeset + } } - return bind(to: collectionView, createCell: { (dataSource, indexPath, collectionView) -> NSCollectionViewItem in - let viewItem = collectionView.makeItem(withIdentifier: identifier, for: indexPath) as! Item - let item = dataSource.item(at: indexPath) - configureItem(viewItem, item) - return viewItem - }) } - /// Binds the signal of data source elements to the given collection view. - /// - /// - parameters: - /// - collectionView: A collection view that should display the data from the data source. - /// - itemType: A type of the cells that should display the data. Cell type name will be used as reusable identifier and the binding will automatically dequeue cell. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to collectionView: NSCollectionView, itemType: Item.Type, using binderDataSource: CollectionViewBinderDataSource, configureItem: @escaping (Item, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifierString = String(describing: Item.self) - let identifier = NSUserInterfaceItemIdentifier(rawValue: identifierString) - let bundle = Bundle(for: Item.self) - if bundle.path(forResource: identifierString, ofType: "nib") != nil { - let nib = NSNib(nibNamed: identifierString, bundle: bundle) - collectionView.register(nib, forItemWithIdentifier: identifier) - } else { - collectionView.register(itemType as AnyClass, forItemWithIdentifier: identifier) - } - binderDataSource.createCell = { (dataSource, indexPath, collectionView) -> NSCollectionViewItem in - let viewItem = collectionView.makeItem(withIdentifier: identifier, for: indexPath) as! Item - let item = dataSource.item(at: indexPath) - configureItem(viewItem, item) - return viewItem + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { + /// Binds the signal of data source elements to the given collection view. + /// + /// - parameters: + /// - collectionView: A collectionView view that should display the data from the data source. + /// - itemType: A type of the cells that should display the data. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to collectionView: NSCollectionView, itemType: Item.Type, configureItem: @escaping (Item, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifierString = String(describing: Item.self) + let identifier = NSUserInterfaceItemIdentifier(rawValue: identifierString) + let bundle = Bundle(for: Item.self) + if bundle.path(forResource: identifierString, ofType: "nib") != nil { + let nib = NSNib(nibNamed: identifierString, bundle: bundle) + collectionView.register(nib, forItemWithIdentifier: identifier) + } else { + collectionView.register(itemType as AnyClass, forItemWithIdentifier: identifier) + } + return bind(to: collectionView, createCell: { (dataSource, indexPath, collectionView) -> NSCollectionViewItem in + let viewItem = collectionView.makeItem(withIdentifier: identifier, for: indexPath) as! Item + let item = dataSource.item(at: indexPath) + configureItem(viewItem, item) + return viewItem + }) + } + + /// Binds the signal of data source elements to the given collection view. + /// + /// - parameters: + /// - collectionView: A collection view that should display the data from the data source. + /// - itemType: A type of the cells that should display the data. Cell type name will be used as reusable identifier and the binding will automatically dequeue cell. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the collection view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to collectionView: NSCollectionView, itemType: Item.Type, using binderDataSource: CollectionViewBinderDataSource, configureItem: @escaping (Item, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifierString = String(describing: Item.self) + let identifier = NSUserInterfaceItemIdentifier(rawValue: identifierString) + let bundle = Bundle(for: Item.self) + if bundle.path(forResource: identifierString, ofType: "nib") != nil { + let nib = NSNib(nibNamed: identifierString, bundle: bundle) + collectionView.register(nib, forItemWithIdentifier: identifier) + } else { + collectionView.register(itemType as AnyClass, forItemWithIdentifier: identifier) + } + binderDataSource.createCell = { (dataSource, indexPath, collectionView) -> NSCollectionViewItem in + let viewItem = collectionView.makeItem(withIdentifier: identifier, for: indexPath) as! Item + let item = dataSource.item(at: indexPath) + configureItem(viewItem, item) + return viewItem + } + return bind(to: collectionView, using: binderDataSource) } - return bind(to: collectionView, using: binderDataSource) } -} - #endif diff --git a/Sources/Bond/AppKit/NSCollectionView.swift b/Sources/Bond/AppKit/NSCollectionView.swift index 6bfd7404..7b09597b 100644 --- a/Sources/Bond/AppKit/NSCollectionView.swift +++ b/Sources/Bond/AppKit/NSCollectionView.swift @@ -24,19 +24,17 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -public extension ReactiveExtensions where Base: NSCollectionView { + public extension ReactiveExtensions where Base: NSCollectionView { + var delegate: ProtocolProxy { + return protocolProxy(for: NSCollectionViewDelegate.self, keyPath: \.delegate) + } - var delegate: ProtocolProxy { - return protocolProxy(for: NSCollectionViewDelegate.self, keyPath: \.delegate) + var dataSource: ProtocolProxy { + return protocolProxy(for: NSCollectionViewDataSource.self, keyPath: \.dataSource) + } } - var dataSource: ProtocolProxy { - return protocolProxy(for: NSCollectionViewDataSource.self, keyPath: \.dataSource) - } - -} - #endif diff --git a/Sources/Bond/AppKit/NSColorWell.swift b/Sources/Bond/AppKit/NSColorWell.swift index 08d2ad35..4731ddb1 100644 --- a/Sources/Bond/AppKit/NSColorWell.swift +++ b/Sources/Bond/AppKit/NSColorWell.swift @@ -24,23 +24,22 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSColorWell { + extension ReactiveExtensions where Base: NSColorWell { + public var color: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + triggerEventOnSetting: false, + get: { $0.color }, + set: { $0.color = $1 } + ) + } - public var color: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - triggerEventOnSetting: false, - get: { $0.color }, - set: { $0.color = $1 } - ) + public var isBordered: Bond { + return bond { $0.isBordered = $1 } + } } - public var isBordered: Bond { - return bond { $0.isBordered = $1 } - } -} - #endif diff --git a/Sources/Bond/AppKit/NSControl.swift b/Sources/Bond/AppKit/NSControl.swift index 30a9b751..abf40eae 100644 --- a/Sources/Bond/AppKit/NSControl.swift +++ b/Sources/Bond/AppKit/NSControl.swift @@ -24,134 +24,130 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -private extension NSControl { + private extension NSControl { + struct AssociatedKeys { + static var ControlHelperKey = "bnd_ControlHelperKey" + } - struct AssociatedKeys { - static var ControlHelperKey = "bnd_ControlHelperKey" - } + @objc class BondHelper: NSObject { + weak var control: NSControl? + let subject = PassthroughSubject() - @objc class BondHelper: NSObject - { - weak var control: NSControl? - let subject = PassthroughSubject() + init(control: NSControl) { + self.control = control + super.init() - init(control: NSControl) { - self.control = control - super.init() + control.target = self + control.action = #selector(eventHandler) + } - control.target = self - control.action = #selector(eventHandler) - } + @objc func eventHandler(_ sender: NSControl?) { + subject.send(sender) + } - @objc func eventHandler(_ sender: NSControl?) { - subject.send(sender) - } - - deinit { - control?.target = nil - control?.action = nil - subject.send(completion: .finished) + deinit { + control?.target = nil + control?.action = nil + subject.send(completion: .finished) + } } } -} - -extension ReactiveExtensions where Base: NSControl { - public var controlEvent: SafeSignal { - if let controlHelper = objc_getAssociatedObject(base, &NSControl.AssociatedKeys.ControlHelperKey) as AnyObject? { - return (controlHelper as! NSControl.BondHelper).subject.map { $0 as! Base }.toSignal() - } else { - let controlHelper = NSControl.BondHelper(control: base) - objc_setAssociatedObject(base, &NSControl.AssociatedKeys.ControlHelperKey, controlHelper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return controlHelper.subject.map { $0 as! Base }.toSignal() + extension ReactiveExtensions where Base: NSControl { + public var controlEvent: SafeSignal { + if let controlHelper = objc_getAssociatedObject(base, &NSControl.AssociatedKeys.ControlHelperKey) as AnyObject? { + return (controlHelper as! NSControl.BondHelper).subject.map { $0 as! Base }.toSignal() + } else { + let controlHelper = NSControl.BondHelper(control: base) + objc_setAssociatedObject(base, &NSControl.AssociatedKeys.ControlHelperKey, controlHelper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return controlHelper.subject.map { $0 as! Base }.toSignal() + } } - } - public func controlEvent(_ read: @escaping (Base) -> T) -> SafeSignal { - return controlEvent.map(read) - } + public func controlEvent(_ read: @escaping (Base) -> T) -> SafeSignal { + return controlEvent.map(read) + } - public var isEnabled: DynamicSubject { - return dynamicSubject( - signal: keyPath(#keyPath(NSControl.isEnabled), ofType: Bool.self).eraseType(), - triggerEventOnSetting: false, - get: { $0.isEnabled }, - set: { $0.isEnabled = $1 } - ) - } + public var isEnabled: DynamicSubject { + return dynamicSubject( + signal: keyPath(#keyPath(NSControl.isEnabled), ofType: Bool.self).eraseType(), + triggerEventOnSetting: false, + get: { $0.isEnabled }, + set: { $0.isEnabled = $1 } + ) + } - public var isHighlighted: DynamicSubject { - return dynamicSubject( - signal: keyPath(#keyPath(NSControl.isHighlighted), ofType: Bool.self).eraseType(), - triggerEventOnSetting: false, - get: { $0.isHighlighted }, - set: { $0.isHighlighted = $1 } - ) - } + public var isHighlighted: DynamicSubject { + return dynamicSubject( + signal: keyPath(#keyPath(NSControl.isHighlighted), ofType: Bool.self).eraseType(), + triggerEventOnSetting: false, + get: { $0.isHighlighted }, + set: { $0.isHighlighted = $1 } + ) + } - public var objectValue: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - triggerEventOnSetting: false, - get: { $0.objectValue }, - set: { $0.objectValue = $1 } - ) - } + public var objectValue: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + triggerEventOnSetting: false, + get: { $0.objectValue }, + set: { $0.objectValue = $1 } + ) + } - public var stringValue: DynamicSubject { - return dynamicSubject( - signal: objectValue.eraseType(), - triggerEventOnSetting: false, - get: { $0.stringValue }, - set: { $0.stringValue = $1 } - ) - } + public var stringValue: DynamicSubject { + return dynamicSubject( + signal: objectValue.eraseType(), + triggerEventOnSetting: false, + get: { $0.stringValue }, + set: { $0.stringValue = $1 } + ) + } - public var attributedStringValue: DynamicSubject { - return dynamicSubject( - signal: objectValue.eraseType(), - triggerEventOnSetting: false, - get: { $0.attributedStringValue }, - set: { $0.attributedStringValue = $1 } - ) - } + public var attributedStringValue: DynamicSubject { + return dynamicSubject( + signal: objectValue.eraseType(), + triggerEventOnSetting: false, + get: { $0.attributedStringValue }, + set: { $0.attributedStringValue = $1 } + ) + } - public var integerValue: DynamicSubject { - return dynamicSubject( - signal: objectValue.eraseType(), - triggerEventOnSetting: false, - get: { $0.integerValue }, - set: { $0.integerValue = $1 } - ) - } + public var integerValue: DynamicSubject { + return dynamicSubject( + signal: objectValue.eraseType(), + triggerEventOnSetting: false, + get: { $0.integerValue }, + set: { $0.integerValue = $1 } + ) + } - public var floatValue: DynamicSubject { - return dynamicSubject( - signal: objectValue.eraseType(), - triggerEventOnSetting: false, - get: { $0.floatValue }, - set: { $0.floatValue = $1 } - ) - } + public var floatValue: DynamicSubject { + return dynamicSubject( + signal: objectValue.eraseType(), + triggerEventOnSetting: false, + get: { $0.floatValue }, + set: { $0.floatValue = $1 } + ) + } - public var doubleValue: DynamicSubject { - return dynamicSubject( - signal: objectValue.eraseType(), - triggerEventOnSetting: false, - get: { $0.doubleValue }, - set: { $0.doubleValue = $1 } - ) + public var doubleValue: DynamicSubject { + return dynamicSubject( + signal: objectValue.eraseType(), + triggerEventOnSetting: false, + get: { $0.doubleValue }, + set: { $0.doubleValue = $1 } + ) + } } -} - -extension NSControl: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.objectValue.bind(signal: signal) + extension NSControl: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.objectValue.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSGestureRecognizer.swift b/Sources/Bond/AppKit/NSGestureRecognizer.swift index 9688c73a..8f536340 100644 --- a/Sources/Bond/AppKit/NSGestureRecognizer.swift +++ b/Sources/Bond/AppKit/NSGestureRecognizer.swift @@ -24,112 +24,112 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension NSGestureRecognizer: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} + extension NSGestureRecognizer: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } + } -extension ReactiveExtensions where Base: NSGestureRecognizer { - public var isEnabled: Bond { - return bond { $0.isEnabled = $1 } + extension ReactiveExtensions where Base: NSGestureRecognizer { + public var isEnabled: Bond { + return bond { $0.isEnabled = $1 } + } } -} - -extension ReactiveExtensions where Base: NSView { - public func addGestureRecognizer(_ gestureRecognizer: T) -> SafeSignal { - let base = self.base - return Signal { [weak base] observer in - guard let base = base else { - observer.receive(completion: .finished) - return NonDisposable.instance - } - let target = BNDGestureTarget(view: base, gestureRecognizer: gestureRecognizer) { recog in - // swiftlint:disable:next force_cast - observer.receive(recog as! T) - } - return MainBlockDisposable { - target.unregister() + + extension ReactiveExtensions where Base: NSView { + public func addGestureRecognizer(_ gestureRecognizer: T) -> SafeSignal { + let base = self.base + return Signal { [weak base] observer in + guard let base = base else { + observer.receive(completion: .finished) + return NonDisposable.instance + } + let target = BNDGestureTarget(view: base, gestureRecognizer: gestureRecognizer) { recog in + // swiftlint:disable:next force_cast + observer.receive(recog as! T) + } + return MainBlockDisposable { + target.unregister() + } } + .prefix(untilOutputFrom: base.deallocated) } - .prefix(untilOutputFrom: base.deallocated) - } - public func clickGesture(numberOfClicks: Int = 1, numberOfTouches: Int = 1) -> SafeSignal { - let gesture = NSClickGestureRecognizer() - gesture.numberOfClicksRequired = numberOfClicks + public func clickGesture(numberOfClicks: Int = 1, numberOfTouches: Int = 1) -> SafeSignal { + let gesture = NSClickGestureRecognizer() + gesture.numberOfClicksRequired = numberOfClicks - if #available(macOS 10.12.2, *) { - gesture.numberOfTouchesRequired = numberOfTouches + if #available(macOS 10.12.2, *) { + gesture.numberOfTouchesRequired = numberOfTouches + } + + return addGestureRecognizer(gesture) } - return addGestureRecognizer(gesture) - } + public func magnificationGesture(magnification: CGFloat = 1.0, numberOfTouches _: Int = 1) -> SafeSignal { + let gesture = NSMagnificationGestureRecognizer() + gesture.magnification = magnification + return addGestureRecognizer(gesture) + } - public func magnificationGesture(magnification: CGFloat = 1.0, numberOfTouches: Int = 1) -> SafeSignal { - let gesture = NSMagnificationGestureRecognizer() - gesture.magnification = magnification - return addGestureRecognizer(gesture) - } + public func panGesture(buttonMask: Int = 0x01, numberOfTouches: Int = 1) -> SafeSignal { + let gesture = NSPanGestureRecognizer() + gesture.buttonMask = buttonMask - public func panGesture(buttonMask: Int = 0x01, numberOfTouches: Int = 1) -> SafeSignal { - let gesture = NSPanGestureRecognizer() - gesture.buttonMask = buttonMask + if #available(macOS 10.12.2, *) { + gesture.numberOfTouchesRequired = numberOfTouches + } - if #available(macOS 10.12.2, *) { - gesture.numberOfTouchesRequired = numberOfTouches + return addGestureRecognizer(gesture) } - return addGestureRecognizer(gesture) - } + public func pressGesture(buttonMask: Int = 0x01, minimumPressDuration: TimeInterval = NSEvent.doubleClickInterval, allowableMovement: CGFloat = 10.0, numberOfTouches: Int = 1) -> SafeSignal { + let gesture = NSPressGestureRecognizer() + gesture.buttonMask = buttonMask + gesture.minimumPressDuration = minimumPressDuration + gesture.allowableMovement = allowableMovement - public func pressGesture(buttonMask: Int = 0x01, minimumPressDuration: TimeInterval = NSEvent.doubleClickInterval, allowableMovement: CGFloat = 10.0, numberOfTouches: Int = 1) -> SafeSignal { - let gesture = NSPressGestureRecognizer() - gesture.buttonMask = buttonMask - gesture.minimumPressDuration = minimumPressDuration - gesture.allowableMovement = allowableMovement + if #available(macOS 10.12.2, *) { + gesture.numberOfTouchesRequired = numberOfTouches + } - if #available(macOS 10.12.2, *) { - gesture.numberOfTouchesRequired = numberOfTouches + return addGestureRecognizer(gesture) } - return addGestureRecognizer(gesture) - } - - public func rotationGesture() -> SafeSignal { - return addGestureRecognizer(NSRotationGestureRecognizer()) + public func rotationGesture() -> SafeSignal { + return addGestureRecognizer(NSRotationGestureRecognizer()) + } } -} -@objc private class BNDGestureTarget: NSObject { - private weak var view: NSView? - private let observer: (NSGestureRecognizer) -> Void - private let gestureRecognizer: NSGestureRecognizer + @objc private class BNDGestureTarget: NSObject { + private weak var view: NSView? + private let observer: (NSGestureRecognizer) -> Void + private let gestureRecognizer: NSGestureRecognizer - fileprivate init(view: NSView, gestureRecognizer: NSGestureRecognizer, observer: @escaping (NSGestureRecognizer) -> Void) { - self.view = view - self.gestureRecognizer = gestureRecognizer - self.observer = observer + fileprivate init(view: NSView, gestureRecognizer: NSGestureRecognizer, observer: @escaping (NSGestureRecognizer) -> Void) { + self.view = view + self.gestureRecognizer = gestureRecognizer + self.observer = observer - super.init() + super.init() - gestureRecognizer.target = self - gestureRecognizer.action = #selector(actionHandler(recogniser:)) - view.addGestureRecognizer(gestureRecognizer) - } + gestureRecognizer.target = self + gestureRecognizer.action = #selector(actionHandler(recogniser:)) + view.addGestureRecognizer(gestureRecognizer) + } - @objc private func actionHandler(recogniser: NSGestureRecognizer) { - observer(recogniser) - } + @objc private func actionHandler(recogniser: NSGestureRecognizer) { + observer(recogniser) + } - fileprivate func unregister() { - view?.removeGestureRecognizer(gestureRecognizer) - } + fileprivate func unregister() { + view?.removeGestureRecognizer(gestureRecognizer) + } - deinit { - unregister() + deinit { + unregister() + } } -} #endif diff --git a/Sources/Bond/AppKit/NSImageView.swift b/Sources/Bond/AppKit/NSImageView.swift index 611f8b89..04666fb8 100644 --- a/Sources/Bond/AppKit/NSImageView.swift +++ b/Sources/Bond/AppKit/NSImageView.swift @@ -24,45 +24,43 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSImageView { + extension ReactiveExtensions where Base: NSImageView { + public var image: Bond { + return bond { $0.image = $1 } + } - public var image: Bond { - return bond { $0.image = $1 } - } - - public var imageAlignment: Bond { - return bond { $0.imageAlignment = $1 } - } + public var imageAlignment: Bond { + return bond { $0.imageAlignment = $1 } + } - public var imageScaling: Bond { - return bond { $0.imageScaling = $1 } - } + public var imageScaling: Bond { + return bond { $0.imageScaling = $1 } + } - public var imageFrameStyle: Bond { - return bond { $0.imageFrameStyle = $1 } - } + public var imageFrameStyle: Bond { + return bond { $0.imageFrameStyle = $1 } + } - public var isEditable: Bond { - return bond { $0.isEditable = $1 } - } + public var isEditable: Bond { + return bond { $0.isEditable = $1 } + } - public var animates: Bond { - return bond { $0.animates = $1 } - } + public var animates: Bond { + return bond { $0.animates = $1 } + } - public var allowsCutCopyPaste: Bond { - return bond { $0.allowsCutCopyPaste = $1 } + public var allowsCutCopyPaste: Bond { + return bond { $0.allowsCutCopyPaste = $1 } + } } -} - -extension NSImageView { - public func bind(signal: Signal) -> Disposable { - return reactive.image.bind(signal: signal) + extension NSImageView { + public func bind(signal: Signal) -> Disposable { + return reactive.image.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSMenuItem.swift b/Sources/Bond/AppKit/NSMenuItem.swift index 6452562c..87722e07 100644 --- a/Sources/Bond/AppKit/NSMenuItem.swift +++ b/Sources/Bond/AppKit/NSMenuItem.swift @@ -24,22 +24,21 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension NSMenuItem: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: NSMenuItem { - - public var state: Bond { - return bond { $0.state = $1 } + extension NSMenuItem: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } } - public var isEnabled: Bond { - return bond { $0.isEnabled = $1 } + extension ReactiveExtensions where Base: NSMenuItem { + public var state: Bond { + return bond { $0.state = $1 } + } + + public var isEnabled: Bond { + return bond { $0.isEnabled = $1 } + } } -} #endif diff --git a/Sources/Bond/AppKit/NSOutlineView+Changeset.swift b/Sources/Bond/AppKit/NSOutlineView+Changeset.swift index 94d01f30..0b30589b 100644 --- a/Sources/Bond/AppKit/NSOutlineView+Changeset.swift +++ b/Sources/Bond/AppKit/NSOutlineView+Changeset.swift @@ -24,234 +24,231 @@ #if canImport(AppKit) -import AppKit -import ReactiveKit - -open class OutlineViewBinder: NSObject, NSOutlineViewDataSource { - - // NSOutlineView display a tree data structure that does not have a single root. - // - // "Items" of type `Any` that NSOutlineView asks for in the data source methods are tree nodes themselves, in our case `TreeNode`. - // Note that we can't use value types here because NSOutlineView is a ObjC type so we need to use TreeArray.Object and TreeNode.Object variants. - - public typealias IsItemExpandable = (Changeset.Collection.Children.Element, NSOutlineView) -> Bool - public typealias ObjectValueForItem = (Changeset.Collection.Children.Element) -> Any? - - public var isItemExpandable: IsItemExpandable? = nil - public var objectValueForItem: ObjectValueForItem - - /// Local clone of the bound data tree wrapped into a class based tree type. - public var rootNode = ObjectTreeArray() - - public var changeset: Changeset? = nil { - didSet { - if let changeset = changeset { - if oldValue != nil { - applyChangeset(changeset) - } else { - rootNode = clone(changeset.collection) + import AppKit + import ReactiveKit + + open class OutlineViewBinder: NSObject, NSOutlineViewDataSource { + // NSOutlineView display a tree data structure that does not have a single root. + // + // "Items" of type `Any` that NSOutlineView asks for in the data source methods are tree nodes themselves, in our case `TreeNode`. + // Note that we can't use value types here because NSOutlineView is a ObjC type so we need to use TreeArray.Object and TreeNode.Object variants. + + public typealias IsItemExpandable = (Changeset.Collection.Children.Element, NSOutlineView) -> Bool + public typealias ObjectValueForItem = (Changeset.Collection.Children.Element) -> Any? + + public var isItemExpandable: IsItemExpandable? + public var objectValueForItem: ObjectValueForItem + + /// Local clone of the bound data tree wrapped into a class based tree type. + public var rootNode = ObjectTreeArray() + + public var changeset: Changeset? { + didSet { + if let changeset = changeset { + if oldValue != nil { + applyChangeset(changeset) + } else { + rootNode = clone(changeset.collection) + outlineView?.reloadData() + } + } else { + rootNode.children = [] outlineView?.reloadData() } - } else { - rootNode.children = [] - outlineView?.reloadData() } } - } - public weak var outlineView: NSOutlineView? = nil { - didSet { - guard let outlineView = outlineView else { return } - associate(with: outlineView) + public weak var outlineView: NSOutlineView? { + didSet { + guard let outlineView = outlineView else { return } + associate(with: outlineView) + } } - } - open var itemInsertionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp] - open var itemDeletionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp] + open var itemInsertionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp] + open var itemDeletionAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp] - /// Default initializer. - /// - /// - parameters: - /// - objectValueForItem: A closure that returns object value for the given node to be used in NSOutlineViewDataSource. - public init(objectValueForItem: @escaping ObjectValueForItem) { - self.objectValueForItem = objectValueForItem - super.init() - } + /// Default initializer. + /// + /// - parameters: + /// - objectValueForItem: A closure that returns object value for the given node to be used in NSOutlineViewDataSource. + public init(objectValueForItem: @escaping ObjectValueForItem) { + self.objectValueForItem = objectValueForItem + super.init() + } - public func item(at indexPath: IndexPath) -> ObjectTreeNode { - return rootNode[childAt: indexPath] - } + public func item(at indexPath: IndexPath) -> ObjectTreeNode { + return rootNode[childAt: indexPath] + } - public func treeNode(forItem item: Any) -> Changeset.Collection.Children.Element? { - return (item as? ObjectTreeNode)?.value - } + public func treeNode(forItem item: Any) -> Changeset.Collection.Children.Element? { + return (item as? ObjectTreeNode)?.value + } - // MARK: - NSOutlineViewDataSource + // MARK: - NSOutlineViewDataSource - @objc(outlineView:isItemExpandable:) - open func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - guard let item = item as? ObjectTreeNode else { return false } - return isItemExpandable?(item.value, outlineView) ?? item.children.isEmpty == false - } + @objc(outlineView:isItemExpandable:) + open func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { + guard let item = item as? ObjectTreeNode else { return false } + return isItemExpandable?(item.value, outlineView) ?? item.children.isEmpty == false + } - @objc(outlineView:numberOfChildrenOfItem:) - open func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - if let item = item as? ObjectTreeNode { - return item.children.count - } else { - return rootNode.children.count + @objc(outlineView:numberOfChildrenOfItem:) + open func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { + if let item = item as? ObjectTreeNode { + return item.children.count + } else { + return rootNode.children.count + } } - } - @objc(outlineView:child:ofItem:) - open func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - if let item = item as? ObjectTreeNode { - return item[[index]] - } else { - return rootNode[childAt: [index]] + @objc(outlineView:child:ofItem:) + open func outlineView(_: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { + if let item = item as? ObjectTreeNode { + return item[[index]] + } else { + return rootNode[childAt: [index]] + } } - } - @objc(outlineView:objectValueForTableColumn:byItem:) - open func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? { - if let item = item as? ObjectTreeNode { - return objectValueForItem(item.value) - } else { - return nil + @objc(outlineView:objectValueForTableColumn:byItem:) + open func outlineView(_: NSOutlineView, objectValueFor _: NSTableColumn?, byItem item: Any?) -> Any? { + if let item = item as? ObjectTreeNode { + return objectValueForItem(item.value) + } else { + return nil + } } - } - open func applyChangeset(_ changeset: Changeset) { - guard let outlineView = outlineView else { return } - let patch = changeset.patch - if patch.isEmpty { - rootNode = clone(changeset.collection) - outlineView.reloadData() - } else { - outlineView.beginUpdates() - patch.forEach { applyOperation($0.asOrderedCollectionOperation) } - outlineView.endUpdates() + open func applyChangeset(_ changeset: Changeset) { + guard let outlineView = outlineView else { return } + let patch = changeset.patch + if patch.isEmpty { + rootNode = clone(changeset.collection) + outlineView.reloadData() + } else { + outlineView.beginUpdates() + patch.forEach { applyOperation($0.asOrderedCollectionOperation) } + outlineView.endUpdates() + } } - } - open func applyOperation(_ operation: OrderedCollectionOperation) { - guard let outlineView = outlineView else { return } + open func applyOperation(_ operation: OrderedCollectionOperation) { + guard let outlineView = outlineView else { return } - switch operation { - case .insert(_, let at): - rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) - let parent = parentNode(forPath: at) // parent after the tree is patched - outlineView.insertItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemInsertionAnimation) - case .delete(let at): - let parent = parentNode(forPath: at) // parent before the tree is patched - rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) - outlineView.removeItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemDeletionAnimation) - case .update(let at, _): - let parent = parentNode(forPath: at) // parent before the tree is patched - rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) - outlineView.removeItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemDeletionAnimation) - outlineView.insertItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemInsertionAnimation) - case .move(let from, let to): - let fromParent = parentNode(forPath: from) // parent before the tree is patched - rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) - let toParent = parentNode(forPath: to) // parent after the tree is patched - outlineView.moveItem(at: from.last!, inParent: fromParent, to: to.last!, inParent: toParent) + switch operation { + case let .insert(_, at): + rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) + let parent = parentNode(forPath: at) // parent after the tree is patched + outlineView.insertItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemInsertionAnimation) + case let .delete(at): + let parent = parentNode(forPath: at) // parent before the tree is patched + rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) + outlineView.removeItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemDeletionAnimation) + case let .update(at, _): + let parent = parentNode(forPath: at) // parent before the tree is patched + rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) + outlineView.removeItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemDeletionAnimation) + outlineView.insertItems(at: IndexSet(integer: at.last!), inParent: parent, withAnimation: itemInsertionAnimation) + case let .move(from, to): + let fromParent = parentNode(forPath: from) // parent before the tree is patched + rootNode.apply(operation.asOrderedCollectionOperation.mapElement { clone($0) }) + let toParent = parentNode(forPath: to) // parent after the tree is patched + outlineView.moveItem(at: from.last!, inParent: fromParent, to: to.last!, inParent: toParent) + } } - } - public func parentNode(forPath path: IndexPath) -> ObjectTreeNode? { - guard path.count > 1 else { - return nil + public func parentNode(forPath path: IndexPath) -> ObjectTreeNode? { + guard path.count > 1 else { + return nil + } + return rootNode[childAt: path.dropLast()] } - return rootNode[childAt: path.dropLast()] - } - private func associate(with outlineView: NSOutlineView) { - objc_setAssociatedObject(outlineView, &OutlineViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + private func associate(with outlineView: NSOutlineView) { + objc_setAssociatedObject(outlineView, &OutlineViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if outlineView.reactive.hasProtocolProxy(for: NSOutlineViewDataSource.self) { - outlineView.reactive.dataSource.forwardTo = self - } else { - outlineView.dataSource = self + if outlineView.reactive.hasProtocolProxy(for: NSOutlineViewDataSource.self) { + outlineView.reactive.dataSource.forwardTo = self + } else { + outlineView.dataSource = self + } } - } - private func clone(_ array: Changeset.Collection) -> ObjectTreeArray { - return ObjectTreeArray(array.children.map { clone($0) }) - } + private func clone(_ array: Changeset.Collection) -> ObjectTreeArray { + return ObjectTreeArray(array.children.map { clone($0) }) + } - private func clone(_ node: Changeset.Collection.Children.Element) -> ObjectTreeNode { - var newNode = ObjectTreeNode(node) - for index in node.breadthFirst.indices.dropFirst() { - newNode.insert(ObjectTreeNode(node[childAt: index]), at: index) + private func clone(_ node: Changeset.Collection.Children.Element) -> ObjectTreeNode { + var newNode = ObjectTreeNode(node) + for index in node.breadthFirst.indices.dropFirst() { + newNode.insert(ObjectTreeNode(node[childAt: index]), at: index) + } + return newNode } - return newNode } -} -private var OutlineViewBinderDataSourceAssociationKey = "OutlineViewBinderDataSource" + private var OutlineViewBinderDataSourceAssociationKey = "OutlineViewBinderDataSource" -extension OutlineViewBinder { - - public class ReloadingBinder: OutlineViewBinder { - public override func applyChangeset(_ changeset: Changeset) { - rootNode = clone(changeset.collection) - outlineView?.reloadData() + extension OutlineViewBinder { + public class ReloadingBinder: OutlineViewBinder { + public override func applyChangeset(_ changeset: Changeset) { + rootNode = clone(changeset.collection) + outlineView?.reloadData() + } } } -} -extension SignalProtocol where Element: OutlineChangesetConvertible, Error == Never { - /// Binds the signal of data source elements to the given outline view. - /// - /// - parameters: - /// - outlineView: An outline view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideUp]`. - /// - objectValueForItem: A closure that returns object value for the given node to be used in NSOutlineViewDataSource. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to outlineView: NSOutlineView, animated: Bool = true, rowAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp], objectValueForItem: @escaping (Element.Changeset.Collection.Children.Element) -> Any?) -> Disposable { - if animated { - let binder = OutlineViewBinder(objectValueForItem: objectValueForItem) - binder.itemInsertionAnimation = rowAnimation - binder.itemDeletionAnimation = rowAnimation - binder.objectValueForItem = objectValueForItem - return bind(to: outlineView, using: binder) - } else { - let binder = OutlineViewBinder.ReloadingBinder(objectValueForItem: objectValueForItem) - return bind(to: outlineView, using: binder) + extension SignalProtocol where Element: OutlineChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given outline view. + /// + /// - parameters: + /// - outlineView: An outline view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideUp]`. + /// - objectValueForItem: A closure that returns object value for the given node to be used in NSOutlineViewDataSource. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to outlineView: NSOutlineView, animated: Bool = true, rowAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp], objectValueForItem: @escaping (Element.Changeset.Collection.Children.Element) -> Any?) -> Disposable { + if animated { + let binder = OutlineViewBinder(objectValueForItem: objectValueForItem) + binder.itemInsertionAnimation = rowAnimation + binder.itemDeletionAnimation = rowAnimation + binder.objectValueForItem = objectValueForItem + return bind(to: outlineView, using: binder) + } else { + let binder = OutlineViewBinder.ReloadingBinder(objectValueForItem: objectValueForItem) + return bind(to: outlineView, using: binder) + } } - } - /// Binds the signal of data source elements to the given outline view. - /// - /// - parameters: - /// - outlineView: An outline view that should display the data from the data source. - /// - binder: A `OutlineViewBinder` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the outline view is deallocated. - @discardableResult - public func bind(to outlineView: NSOutlineView, using binder: OutlineViewBinder) -> Disposable { - binder.outlineView = outlineView - return bind(to: outlineView) { (_, changeset) in - binder.changeset = changeset.asTreeArrayChangeset + /// Binds the signal of data source elements to the given outline view. + /// + /// - parameters: + /// - outlineView: An outline view that should display the data from the data source. + /// - binder: A `OutlineViewBinder` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the outline view is deallocated. + @discardableResult + public func bind(to outlineView: NSOutlineView, using binder: OutlineViewBinder) -> Disposable { + binder.outlineView = outlineView + return bind(to: outlineView) { _, changeset in + binder.changeset = changeset.asTreeArrayChangeset + } } } -} - -extension SignalProtocol where Element: OutlineChangesetConvertible, Element.Changeset.Collection.Children.Element: TreeNodeWithValueProtocol, Error == Never { - /// Binds the signal of data source elements to the given outline view. - /// - /// - parameters: - /// - outlineView: An outline view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideUp]`. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to outlineView: NSOutlineView, animated: Bool = true, rowAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp]) -> Disposable { - return bind(to: outlineView, animated: animated, rowAnimation: rowAnimation, objectValueForItem: { $0.value }) + extension SignalProtocol where Element: OutlineChangesetConvertible, Element.Changeset.Collection.Children.Element: TreeNodeWithValueProtocol, Error == Never { + /// Binds the signal of data source elements to the given outline view. + /// + /// - parameters: + /// - outlineView: An outline view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideUp]`. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to outlineView: NSOutlineView, animated: Bool = true, rowAnimation: NSOutlineView.AnimationOptions = [.effectFade, .slideUp]) -> Disposable { + return bind(to: outlineView, animated: animated, rowAnimation: rowAnimation, objectValueForItem: { $0.value }) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSOutlineView.swift b/Sources/Bond/AppKit/NSOutlineView.swift index e9101bc8..35d164f8 100644 --- a/Sources/Bond/AppKit/NSOutlineView.swift +++ b/Sources/Bond/AppKit/NSOutlineView.swift @@ -23,47 +23,47 @@ // #if canImport(AppKit) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSOutlineView { - public var delegate: ProtocolProxy { - return protocolProxy(for: NSOutlineViewDelegate.self, keyPath: \.delegate) - } + extension ReactiveExtensions where Base: NSOutlineView { + public var delegate: ProtocolProxy { + return protocolProxy(for: NSOutlineViewDelegate.self, keyPath: \.delegate) + } - public var dataSource: ProtocolProxy { - return protocolProxy(for: NSOutlineViewDataSource.self, keyPath: \.dataSource) - } + public var dataSource: ProtocolProxy { + return protocolProxy(for: NSOutlineViewDataSource.self, keyPath: \.dataSource) + } - public var selectionIsChanging: SafeSignal { - return NotificationCenter.default.reactive.notification(name: NSOutlineView.selectionIsChangingNotification, object: base).eraseType() - } + public var selectionIsChanging: SafeSignal { + return NotificationCenter.default.reactive.notification(name: NSOutlineView.selectionIsChangingNotification, object: base).eraseType() + } - public var selectionDidChange: SafeSignal { - return NotificationCenter.default.reactive.notification(name: NSOutlineView.selectionDidChangeNotification, object: base).eraseType() - } + public var selectionDidChange: SafeSignal { + return NotificationCenter.default.reactive.notification(name: NSOutlineView.selectionDidChangeNotification, object: base).eraseType() + } - public var selectedRowIndexes: Bond { - return bond { $0.selectRowIndexes($1, byExtendingSelection: false) } - } + public var selectedRowIndexes: Bond { + return bond { $0.selectRowIndexes($1, byExtendingSelection: false) } + } - public var selectedColumnIndexes: Bond { - return bond { $0.selectColumnIndexes($1, byExtendingSelection: false) } - } + public var selectedColumnIndexes: Bond { + return bond { $0.selectColumnIndexes($1, byExtendingSelection: false) } + } - public var selectedItems: DynamicSubject<[Any]> { - return dynamicSubject( - signal: self.selectionDidChange, - triggerEventOnSetting: false, - get: { outlineView in - outlineView.selectedRowIndexes.compactMap { outlineView.item(atRow: $0) } - }, - set: { outlineView, items in - let indexes = IndexSet(items.map { outlineView.row(forItem: $0) }) - outlineView.selectRowIndexes(indexes, byExtendingSelection: false) - } - ) + public var selectedItems: DynamicSubject<[Any]> { + return dynamicSubject( + signal: selectionDidChange, + triggerEventOnSetting: false, + get: { outlineView in + outlineView.selectedRowIndexes.compactMap { outlineView.item(atRow: $0) } + }, + set: { outlineView, items in + let indexes = IndexSet(items.map { outlineView.row(forItem: $0) }) + outlineView.selectRowIndexes(indexes, byExtendingSelection: false) + } + ) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSPopUpButton.swift b/Sources/Bond/AppKit/NSPopUpButton.swift index 0645c500..d76c6191 100644 --- a/Sources/Bond/AppKit/NSPopUpButton.swift +++ b/Sources/Bond/AppKit/NSPopUpButton.swift @@ -24,49 +24,47 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSPopUpButton { + extension ReactiveExtensions where Base: NSPopUpButton { + public var selectedItem: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + get: { $0.selectedItem }, + set: { $0.select($1) } + ) + } - public var selectedItem: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - get: { $0.selectedItem }, - set: { $0.select($1) } - ) - } + public var indexOfSelectedItem: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + get: { $0.indexOfSelectedItem }, + set: { $0.selectItem(at: $1 ?? -1) } + ) + } - public var indexOfSelectedItem: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - get: { $0.indexOfSelectedItem }, - set: { $0.selectItem(at: $1 ?? -1) } - ) - } + public var titleOfSelectedItem: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + get: { $0.titleOfSelectedItem }, + set: { $0.selectItem(withTitle: $1 ?? "") } + ) + } - public var titleOfSelectedItem: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - get: { $0.titleOfSelectedItem }, - set: { $0.selectItem(withTitle: $1 ?? "") } - ) + public var tagOfSelectedItem: DynamicSubject { + return dynamicSubject( + signal: controlEvent.eraseType(), + get: { $0.selectedItem?.tag }, + set: { $0.selectItem(withTag: $1 ?? -1) } + ) + } } - public var tagOfSelectedItem: DynamicSubject { - return dynamicSubject( - signal: controlEvent.eraseType(), - get: { $0.selectedItem?.tag }, - set: { $0.selectItem(withTag: $1 ?? -1) } - ) - } -} - -extension NSPopUpButton { - - public func bind(signal: Signal) -> Disposable { - return reactive.selectedItem.bind(signal: signal) + extension NSPopUpButton { + public func bind(signal: Signal) -> Disposable { + return reactive.selectedItem.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSProgressIndicator.swift b/Sources/Bond/AppKit/NSProgressIndicator.swift index a2965a01..e52bb86e 100644 --- a/Sources/Bond/AppKit/NSProgressIndicator.swift +++ b/Sources/Bond/AppKit/NSProgressIndicator.swift @@ -24,59 +24,57 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSProgressIndicator { - - public var isAnimating: Bond { - return bond { - if $1 { - $0.startAnimation(nil) - } else { - $0.stopAnimation(nil) + extension ReactiveExtensions where Base: NSProgressIndicator { + public var isAnimating: Bond { + return bond { + if $1 { + $0.startAnimation(nil) + } else { + $0.stopAnimation(nil) + } } } - } - public var doubleValue: Bond { - return bond { $0.doubleValue = $1 } - } + public var doubleValue: Bond { + return bond { $0.doubleValue = $1 } + } - public var isIndeterminate: Bond { - return bond { $0.isIndeterminate = $1 } - } + public var isIndeterminate: Bond { + return bond { $0.isIndeterminate = $1 } + } - public var minValue: Bond { - return bond { $0.minValue = $1 } - } + public var minValue: Bond { + return bond { $0.minValue = $1 } + } - public var maxValue: Bond { - return bond { $0.maxValue = $1 } - } + public var maxValue: Bond { + return bond { $0.maxValue = $1 } + } - public var controlTint: Bond { - return bond { $0.controlTint = $1 } - } + public var controlTint: Bond { + return bond { $0.controlTint = $1 } + } - public var controlSize: Bond { - return bond { $0.controlSize = $1 } - } + public var controlSize: Bond { + return bond { $0.controlSize = $1 } + } - public var style: Bond { - return bond { $0.style = $1 } - } + public var style: Bond { + return bond { $0.style = $1 } + } - public var isDisplayedWhenStopped: Bond { - return bond { $0.isDisplayedWhenStopped = $1 } + public var isDisplayedWhenStopped: Bond { + return bond { $0.isDisplayedWhenStopped = $1 } + } } -} -extension NSProgressIndicator: BindableProtocol { - - public func bind(signal: Signal) -> Disposable { - return reactive.doubleValue.bind(signal: signal) + extension NSProgressIndicator: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.doubleValue.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSSegmentedControl.swift b/Sources/Bond/AppKit/NSSegmentedControl.swift index 7929e76d..c7aed9c0 100644 --- a/Sources/Bond/AppKit/NSSegmentedControl.swift +++ b/Sources/Bond/AppKit/NSSegmentedControl.swift @@ -24,44 +24,42 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSSegmentedControl { + extension ReactiveExtensions where Base: NSSegmentedControl { + public var segmentCount: Bond { + return bond { $0.segmentCount = $1 } + } - public var segmentCount: Bond { - return bond { $0.segmentCount = $1 } - } + public var selectedSegment: Bond { + return bond { $0.selectedSegment = $1 } + } - public var selectedSegment: Bond { - return bond { $0.selectedSegment = $1 } - } + public var segmentStyle: Bond { + return bond { $0.segmentStyle = $1 } + } - public var segmentStyle: Bond { - return bond { $0.segmentStyle = $1 } - } + @available(macOS 10.10.3, *) + public var isSpringLoaded: Bond { + return bond { $0.isSpringLoaded = $1 } + } - @available(macOS 10.10.3, *) - public var isSpringLoaded: Bond { - return bond { $0.isSpringLoaded = $1 } - } + @available(macOS 10.10.3, *) + public var trackingMode: Bond { + return bond { $0.trackingMode = $1 } + } - @available(macOS 10.10.3, *) - public var trackingMode: Bond { - return bond { $0.trackingMode = $1 } + @available(macOS 10.12.2, *) + public var selectedSegmentBezelColor: Bond { + return bond { $0.selectedSegmentBezelColor = $1 } + } } - @available(macOS 10.12.2, *) - public var selectedSegmentBezelColor: Bond { - return bond { $0.selectedSegmentBezelColor = $1 } - } -} - -extension NSSegmentedControl { - - public func bind(signal: Signal) -> Disposable { - return reactive.selectedSegment.bind(signal: signal) + extension NSSegmentedControl { + public func bind(signal: Signal) -> Disposable { + return reactive.selectedSegment.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSSlider.swift b/Sources/Bond/AppKit/NSSlider.swift index 68b86142..26933800 100644 --- a/Sources/Bond/AppKit/NSSlider.swift +++ b/Sources/Bond/AppKit/NSSlider.swift @@ -24,51 +24,49 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSSlider { + extension ReactiveExtensions where Base: NSSlider { + public var minValue: Bond { + return bond { $0.minValue = $1 } + } - public var minValue: Bond { - return bond { $0.minValue = $1 } - } + public var maxValue: Bond { + return bond { $0.maxValue = $1 } + } - public var maxValue: Bond { - return bond { $0.maxValue = $1 } - } + public var altIncrementValue: Bond { + return bond { $0.altIncrementValue = $1 } + } - public var altIncrementValue: Bond { - return bond { $0.altIncrementValue = $1 } - } + @available(macOS 10.12, *) + public var isVertical: Bond { + return bond { $0.isVertical = $1 } + } - @available(macOS 10.12, *) - public var isVertical: Bond { - return bond { $0.isVertical = $1 } - } + @available(macOS 10.12.2, *) + public var trackFillColor: Bond { + return bond { $0.trackFillColor = $1 } + } - @available(macOS 10.12.2, *) - public var trackFillColor: Bond { - return bond { $0.trackFillColor = $1 } - } + public var numberOfTickMarks: Bond { + return bond { $0.numberOfTickMarks = $1 } + } - public var numberOfTickMarks: Bond { - return bond { $0.numberOfTickMarks = $1 } - } - - public var tickMarkPosition: Bond { - return bond { $0.tickMarkPosition = $1 } - } + public var tickMarkPosition: Bond { + return bond { $0.tickMarkPosition = $1 } + } - public var allowsTickMarkValuesOnly: Bond { - return bond { $0.allowsTickMarkValuesOnly = $1 } + public var allowsTickMarkValuesOnly: Bond { + return bond { $0.allowsTickMarkValuesOnly = $1 } + } } -} - -extension NSSlider { - public func bind(signal: Signal) -> Disposable { - return reactive.doubleValue.bind(signal: signal) + extension NSSlider { + public func bind(signal: Signal) -> Disposable { + return reactive.doubleValue.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSStatusBarButton.swift b/Sources/Bond/AppKit/NSStatusBarButton.swift index e023ad54..66edf5b8 100644 --- a/Sources/Bond/AppKit/NSStatusBarButton.swift +++ b/Sources/Bond/AppKit/NSStatusBarButton.swift @@ -24,14 +24,13 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSStatusBarButton { - - public var appearsDisabled: Bond { - return bond { $0.appearsDisabled = $1 } + extension ReactiveExtensions where Base: NSStatusBarButton { + public var appearsDisabled: Bond { + return bond { $0.appearsDisabled = $1 } + } } -} #endif diff --git a/Sources/Bond/AppKit/NSTableView+DataSource.swift b/Sources/Bond/AppKit/NSTableView+DataSource.swift index 48715318..0e28f393 100644 --- a/Sources/Bond/AppKit/NSTableView+DataSource.swift +++ b/Sources/Bond/AppKit/NSTableView+DataSource.swift @@ -24,144 +24,142 @@ #if canImport(AppKit) -import AppKit -import ReactiveKit - -private var TableViewBinderDataSourceAssociationKey = "TableViewBinderDataSource" - -open class TableViewBinderDataSource: NSObject, NSTableViewDataSource { - - open var rowInsertionAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft] - open var rowRemovalAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft] - - /// Local clone of the bound collection - public var collection: [Changeset.Collection.Item] = [] - - public var changeset: Changeset? = nil { - didSet { - if let changeset = changeset { - if oldValue != nil { - apply(changeset: changeset) + import AppKit + import ReactiveKit + + private var TableViewBinderDataSourceAssociationKey = "TableViewBinderDataSource" + + open class TableViewBinderDataSource: NSObject, NSTableViewDataSource { + open var rowInsertionAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft] + open var rowRemovalAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft] + + /// Local clone of the bound collection + public var collection: [Changeset.Collection.Item] = [] + + public var changeset: Changeset? { + didSet { + if let changeset = changeset { + if oldValue != nil { + apply(changeset: changeset) + } else { + collection = clone(changeset.collection) + tableView?.reloadData() + } } else { - collection = clone(changeset.collection) + collection = [] tableView?.reloadData() } - } else { - collection = [] - tableView?.reloadData() } } - } - public weak var tableView: NSTableView? = nil { - didSet { - guard let tableView = tableView else { return } - associateWithTableView(tableView) + public weak var tableView: NSTableView? { + didSet { + guard let tableView = tableView else { return } + associateWithTableView(tableView) + } } - } - // MARK: - NSTableViewDataSource + // MARK: - NSTableViewDataSource - @objc(numberOfRowsInTableView:) - open func numberOfRows(in tableView: NSTableView) -> Int { - return collection.count - } + @objc(numberOfRowsInTableView:) + open func numberOfRows(in _: NSTableView) -> Int { + return collection.count + } - @objc(tableView:objectValueForTableColumn:row:) - open func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { - return collection[row] - } + @objc(tableView:objectValueForTableColumn:row:) + open func tableView(_: NSTableView, objectValueFor _: NSTableColumn?, row: Int) -> Any? { + return collection[row] + } - open func apply(changeset: Changeset) { - guard let tableView = tableView else { return } - let patch = changeset.patch - if patch.isEmpty { - collection = clone(changeset.collection) - tableView.reloadData() - } else { - tableView.beginUpdates() - patch.forEach(apply) - tableView.endUpdates() + open func apply(changeset: Changeset) { + guard let tableView = tableView else { return } + let patch = changeset.patch + if patch.isEmpty { + collection = clone(changeset.collection) + tableView.reloadData() + } else { + tableView.beginUpdates() + patch.forEach(apply) + tableView.endUpdates() + } } - } - open func apply(operation: Changeset.Operation) { - guard let tableView = tableView else { return } - - switch operation.asOrderedCollectionOperation { - case .insert(let element, let index): - collection.apply(.insert(element, at: index.asFlatDataIndex)) - tableView.insertRows(at: IndexSet([index.asFlatDataIndex]), withAnimation: rowInsertionAnimation) - case .delete(let index): - collection.apply(.delete(at: index.asFlatDataIndex)) - tableView.removeRows(at: IndexSet([index.asFlatDataIndex]), withAnimation: rowRemovalAnimation) - case .update(let index, let element): - collection.apply(.update(at: index.asFlatDataIndex, newElement: element)) - let allColumnIndexes = IndexSet(tableView.tableColumns.enumerated().map { $0.0 }) - tableView.reloadData(forRowIndexes: IndexSet([index.asFlatDataIndex]), columnIndexes: allColumnIndexes) - case .move(let from, let to): - collection.apply(.move(from: from.asFlatDataIndex, to: to.asFlatDataIndex)) - tableView.moveRow(at: from.asFlatDataIndex, to: to.asFlatDataIndex) + open func apply(operation: Changeset.Operation) { + guard let tableView = tableView else { return } + + switch operation.asOrderedCollectionOperation { + case let .insert(element, index): + collection.apply(.insert(element, at: index.asFlatDataIndex)) + tableView.insertRows(at: IndexSet([index.asFlatDataIndex]), withAnimation: rowInsertionAnimation) + case let .delete(index): + collection.apply(.delete(at: index.asFlatDataIndex)) + tableView.removeRows(at: IndexSet([index.asFlatDataIndex]), withAnimation: rowRemovalAnimation) + case let .update(index, element): + collection.apply(.update(at: index.asFlatDataIndex, newElement: element)) + let allColumnIndexes = IndexSet(tableView.tableColumns.enumerated().map { $0.0 }) + tableView.reloadData(forRowIndexes: IndexSet([index.asFlatDataIndex]), columnIndexes: allColumnIndexes) + case let .move(from, to): + collection.apply(.move(from: from.asFlatDataIndex, to: to.asFlatDataIndex)) + tableView.moveRow(at: from.asFlatDataIndex, to: to.asFlatDataIndex) + } } - } - private func associateWithTableView(_ tableView: NSTableView) { - objc_setAssociatedObject(tableView, &TableViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if tableView.reactive.hasProtocolProxy(for: NSTableViewDataSource.self) { - tableView.reactive.dataSource.forwardTo = self - } else { - tableView.dataSource = self + private func associateWithTableView(_ tableView: NSTableView) { + objc_setAssociatedObject(tableView, &TableViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if tableView.reactive.hasProtocolProxy(for: NSTableViewDataSource.self) { + tableView.reactive.dataSource.forwardTo = self + } else { + tableView.dataSource = self + } } - } - private func clone(_ collection: Changeset.Collection) -> [Changeset.Collection.Item] { - return (0.. [Changeset.Collection.Item] { + return (0 ..< collection.numberOfItems).map { collection.item(at: $0) } + } } -} -extension TableViewBinderDataSource { - public class ReloadingBinder: TableViewBinderDataSource { - public override func apply(changeset: Changeset) { - tableView?.reloadData() + extension TableViewBinderDataSource { + public class ReloadingBinder: TableViewBinderDataSource { + public override func apply(changeset _: Changeset) { + tableView?.reloadData() + } } } -} - -extension SignalProtocol where Element: FlatDataSourceChangesetConvertible, Error == Never { - - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideLeft]`. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to tableView: NSTableView, animated: Bool = true, rowAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft]) -> Disposable { - if animated { - let binder = TableViewBinderDataSource() - binder.rowInsertionAnimation = rowAnimation - binder.rowRemovalAnimation = rowAnimation - return bind(to: tableView, using: binder) - } else { - let binder = TableViewBinderDataSource.ReloadingBinder() - return bind(to: tableView, using: binder) + + extension SignalProtocol where Element: FlatDataSourceChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `[.effectFade, .slideLeft]`. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to tableView: NSTableView, animated: Bool = true, rowAnimation: NSTableView.AnimationOptions = [.effectFade, .slideLeft]) -> Disposable { + if animated { + let binder = TableViewBinderDataSource() + binder.rowInsertionAnimation = rowAnimation + binder.rowRemovalAnimation = rowAnimation + return bind(to: tableView, using: binder) + } else { + let binder = TableViewBinderDataSource.ReloadingBinder() + return bind(to: tableView, using: binder) + } } - } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - binder: A `TableViewBinder` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to tableView: NSTableView, using binder: TableViewBinderDataSource) -> Disposable { - binder.tableView = tableView - return bind(to: tableView) { (_, changeset) in - binder.changeset = changeset.asFlatDataSourceChangeset + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - binder: A `TableViewBinder` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to tableView: NSTableView, using binder: TableViewBinderDataSource) -> Disposable { + binder.tableView = tableView + return bind(to: tableView) { _, changeset in + binder.changeset = changeset.asFlatDataSourceChangeset + } } } -} #endif diff --git a/Sources/Bond/AppKit/NSTableView.swift b/Sources/Bond/AppKit/NSTableView.swift index 94980b04..0aa783c0 100644 --- a/Sources/Bond/AppKit/NSTableView.swift +++ b/Sources/Bond/AppKit/NSTableView.swift @@ -8,44 +8,43 @@ #if os(macOS) -import AppKit -import ReactiveKit - -extension ReactiveExtensions where Base: NSTableView { - - /// A `ProtocolProxy` for the table view delegate. - /// - /// - Note: Accessing this property for the first time will replace table view's current delegate - /// with a protocol proxy object (an object that is stored in this property). - /// Current delegate will be used as `forwardTo` delegate of protocol proxy. - public var delegate: ProtocolProxy { - return protocolProxy(for: NSTableViewDelegate.self, keyPath: \.delegate) + import AppKit + import ReactiveKit + + extension ReactiveExtensions where Base: NSTableView { + /// A `ProtocolProxy` for the table view delegate. + /// + /// - Note: Accessing this property for the first time will replace table view's current delegate + /// with a protocol proxy object (an object that is stored in this property). + /// Current delegate will be used as `forwardTo` delegate of protocol proxy. + public var delegate: ProtocolProxy { + return protocolProxy(for: NSTableViewDelegate.self, keyPath: \.delegate) + } + + /// A `ProtocolProxy` for the table view data source. + /// + /// - Note: Accessing this property for the first time will replace table view's current data source + /// with a protocol proxy object (an object that is stored in this property). + /// Current data source will be used as `forwardTo` data source of protocol proxy. + public var dataSource: ProtocolProxy { + return protocolProxy(for: NSTableViewDataSource.self, keyPath: \.dataSource) + } + + public var selectionIsChanging: SafeSignal { + return NotificationCenter.default.reactive.notification(name: NSTableView.selectionIsChangingNotification, object: base).eraseType() + } + + public var selectionDidChange: SafeSignal { + return NotificationCenter.default.reactive.notification(name: NSTableView.selectionDidChangeNotification, object: base).eraseType() + } + + public var selectedRowIndexes: Bond { + return bond { $0.selectRowIndexes($1, byExtendingSelection: false) } + } + + public var selectedColumnIndexes: Bond { + return bond { $0.selectColumnIndexes($1, byExtendingSelection: false) } + } } - /// A `ProtocolProxy` for the table view data source. - /// - /// - Note: Accessing this property for the first time will replace table view's current data source - /// with a protocol proxy object (an object that is stored in this property). - /// Current data source will be used as `forwardTo` data source of protocol proxy. - public var dataSource: ProtocolProxy { - return protocolProxy(for: NSTableViewDataSource.self, keyPath: \.dataSource) - } - - public var selectionIsChanging: SafeSignal { - return NotificationCenter.default.reactive.notification(name: NSTableView.selectionIsChangingNotification, object: base).eraseType() - } - - public var selectionDidChange: SafeSignal { - return NotificationCenter.default.reactive.notification(name: NSTableView.selectionDidChangeNotification, object: base).eraseType() - } - - public var selectedRowIndexes: Bond { - return bond { $0.selectRowIndexes($1, byExtendingSelection: false) } - } - - public var selectedColumnIndexes: Bond { - return bond { $0.selectColumnIndexes($1, byExtendingSelection: false) } - } -} - #endif diff --git a/Sources/Bond/AppKit/NSTextField.swift b/Sources/Bond/AppKit/NSTextField.swift index 47535437..f3220cfd 100644 --- a/Sources/Bond/AppKit/NSTextField.swift +++ b/Sources/Bond/AppKit/NSTextField.swift @@ -24,93 +24,91 @@ #if os(macOS) -import AppKit -import ReactiveKit - -extension ReactiveExtensions where Base: NSTextField { - - public var font: Bond { - return bond { $0.font = $1 } - } - - public var textColor: Bond { - return bond { $0.textColor = $1 } - } - - public var backgroundColor: Bond { - return bond { $0.backgroundColor = $1 } - } - - public var placeholderString: Bond { - return bond { $0.placeholderString = $1 } - } - - public var placeholderAttributedString: Bond { - return bond { $0.placeholderAttributedString = $1 } - } - - public var editingString: DynamicSubject { - return dynamicSubject( - signal: self.textDidChange.eraseType(), - get: { (textField: NSTextField) -> String in - return textField.formatter?.editingString(for: textField.stringValue) ?? textField.stringValue - }, - set: { (textField: NSTextField, value: String) in - textField.stringValue = value - } - ) - } - - public var textDidChange: SafeSignal { - return NotificationCenter.default - .reactive.notification(name: NSControl.textDidChangeNotification, object: base) - .compactMap { $0.object as? NSTextField } - } - - public var textDidBeginEditing: SafeSignal { - return NotificationCenter.default - .reactive.notification(name: NSControl.textDidBeginEditingNotification, object: base) - .compactMap { $0.object as? NSTextField } - } - - public var textDidEndEditing: SafeSignal<(NSTextField, Bool)> { - return NotificationCenter.default - .reactive.notification(name: NSControl.textDidEndEditingNotification, object: base) - .compactMap { notification -> (NSTextField, Bool)? in - guard let textField = notification.object as? NSTextField else { - return nil + import AppKit + import ReactiveKit + + extension ReactiveExtensions where Base: NSTextField { + public var font: Bond { + return bond { $0.font = $1 } + } + + public var textColor: Bond { + return bond { $0.textColor = $1 } + } + + public var backgroundColor: Bond { + return bond { $0.backgroundColor = $1 } + } + + public var placeholderString: Bond { + return bond { $0.placeholderString = $1 } + } + + public var placeholderAttributedString: Bond { + return bond { $0.placeholderAttributedString = $1 } + } + + public var editingString: DynamicSubject { + return dynamicSubject( + signal: textDidChange.eraseType(), + get: { (textField: NSTextField) -> String in + textField.formatter?.editingString(for: textField.stringValue) ?? textField.stringValue + }, + set: { (textField: NSTextField, value: String) in + textField.stringValue = value } - return (textField, Self.resignedFirstResponder(in: notification)) - } - } - - /// Interrogates the passed notification for details of how the field ended editing, and if it resigned its first responder status as a result. - /// - /// - Parameter notification: Notification of type `NSControlTextDidEndEditing` - /// - Returns: true if the text field resigned first responder, false if it did not - private static func resignedFirstResponder(in notification: Notification) -> Bool { - guard - notification.name == NSControl.textDidEndEditingNotification, - let textField = notification.object as? NSTextField, - let textMovement = notification.userInfo?["NSTextMovement"] as? Int + ) + } + + public var textDidChange: SafeSignal { + return NotificationCenter.default + .reactive.notification(name: NSControl.textDidChangeNotification, object: base) + .compactMap { $0.object as? NSTextField } + } + + public var textDidBeginEditing: SafeSignal { + return NotificationCenter.default + .reactive.notification(name: NSControl.textDidBeginEditingNotification, object: base) + .compactMap { $0.object as? NSTextField } + } + + public var textDidEndEditing: SafeSignal<(NSTextField, Bool)> { + return NotificationCenter.default + .reactive.notification(name: NSControl.textDidEndEditingNotification, object: base) + .compactMap { notification -> (NSTextField, Bool)? in + guard let textField = notification.object as? NSTextField else { + return nil + } + return (textField, Self.resignedFirstResponder(in: notification)) + } + } + + /// Interrogates the passed notification for details of how the field ended editing, and if it resigned its first responder status as a result. + /// + /// - Parameter notification: Notification of type `NSControlTextDidEndEditing` + /// - Returns: true if the text field resigned first responder, false if it did not + private static func resignedFirstResponder(in notification: Notification) -> Bool { + guard + notification.name == NSControl.textDidEndEditingNotification, + let textField = notification.object as? NSTextField, + let textMovement = notification.userInfo?["NSTextMovement"] as? Int else { return false } - let returnKeyPressed = (textMovement == NSReturnTextMovement) - let tabKeyPressed = (textMovement == NSTabTextMovement || textMovement == NSBacktabTextMovement) - let tabbedIntoTextField = tabKeyPressed && textField.nextKeyView == textField - let tabbedOutOfTextField = tabKeyPressed && textField.nextKeyView == nil + let returnKeyPressed = (textMovement == NSReturnTextMovement) + let tabKeyPressed = (textMovement == NSTabTextMovement || textMovement == NSBacktabTextMovement) + let tabbedIntoTextField = tabKeyPressed && textField.nextKeyView == textField + let tabbedOutOfTextField = tabKeyPressed && textField.nextKeyView == nil - return (returnKeyPressed || tabbedIntoTextField || tabbedOutOfTextField) + return (returnKeyPressed || tabbedIntoTextField || tabbedOutOfTextField) + } } -} - -extension NSTextField { - public func bind(signal: Signal) -> Disposable { - return reactive.stringValue.bind(signal: signal) + extension NSTextField { + public func bind(signal: Signal) -> Disposable { + return reactive.stringValue.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSTextView.swift b/Sources/Bond/AppKit/NSTextView.swift index a3c1358d..17fd11cb 100644 --- a/Sources/Bond/AppKit/NSTextView.swift +++ b/Sources/Bond/AppKit/NSTextView.swift @@ -24,19 +24,18 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension ReactiveExtensions where Base: NSTextView { - - public var string: DynamicSubject { - let notificationName = NSText.didChangeNotification - return dynamicSubject( - signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), - get: { $0.string }, - set: { $0.string = $1 } - ) + extension ReactiveExtensions where Base: NSTextView { + public var string: DynamicSubject { + let notificationName = NSText.didChangeNotification + return dynamicSubject( + signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), + get: { $0.string }, + set: { $0.string = $1 } + ) + } } -} #endif diff --git a/Sources/Bond/AppKit/NSView.swift b/Sources/Bond/AppKit/NSView.swift index 784c7442..45ad5c11 100644 --- a/Sources/Bond/AppKit/NSView.swift +++ b/Sources/Bond/AppKit/NSView.swift @@ -24,26 +24,25 @@ #if os(macOS) -import AppKit -import ReactiveKit + import AppKit + import ReactiveKit -extension NSResponder: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: NSView { - - public var alphaValue: Bond { - return bond { $0.alphaValue = $1 } + extension NSResponder: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } } - public var isHidden: Bond { - return bond { $0.isHidden = $1 } - } + extension ReactiveExtensions where Base: NSView { + public var alphaValue: Bond { + return bond { $0.alphaValue = $1 } + } + + public var isHidden: Bond { + return bond { $0.isHidden = $1 } + } - public var toolTip: Bond { - return bond { $0.toolTip = $1 } + public var toolTip: Bond { + return bond { $0.toolTip = $1 } + } } -} #endif diff --git a/Sources/Bond/BNDInvocation.swift b/Sources/Bond/BNDInvocation.swift index 0c7ef1d4..a8dd21b6 100644 --- a/Sources/Bond/BNDInvocation.swift +++ b/Sources/Bond/BNDInvocation.swift @@ -14,7 +14,6 @@ import ObjectiveC #endif internal extension BNDInvocation { - func readArgument(_ index: Int) -> T { let size = Int(methodSignature.getArgumentSize(at: UInt(index))) let alignment = Int(methodSignature.getArgumentAlignment(at: UInt(index))) @@ -55,9 +54,9 @@ internal extension BNDInvocation { case NSObjCBoolType: return NSNumber(value: pointer.assumingMemoryBound(to: CBool.self).pointee) as! T case NSObjCSelectorType: - return pointer.assumingMemoryBound(to: Optional.self).pointee as! T + return pointer.assumingMemoryBound(to: Selector?.self).pointee as! T case NSObjCObjectType: - return pointer.downcastPointee(to: T.self, assuming: Optional.self) + return pointer.downcastPointee(to: T.self, assuming: AnyObject?.self) default: return pointer.assumingMemoryBound(to: T.self).pointee } @@ -70,7 +69,7 @@ internal extension BNDInvocation { let alignment = methodSignature.getReturnArgumentAlignment() let type = methodSignature.getReturnArgumentType() - func write(_ value: V, as type: U.Type) { + func write(_ value: V, as _: U.Type) { let pointer = UnsafeMutablePointer.allocate(capacity: 1) pointer.initialize(repeating: value as! U, count: 1) setReturnValue(pointer) @@ -106,9 +105,9 @@ internal extension BNDInvocation { case NSObjCBoolType: write(value as! NSNumber, as: CBool.self) case NSObjCSelectorType: - write(value, as: Optional.self) + write(value, as: Selector?.self) case NSObjCObjectType: - write(value, as: Optional.self) + write(value, as: AnyObject?.self) default: write(value, as: T.self) } @@ -116,10 +115,9 @@ internal extension BNDInvocation { } private extension UnsafeMutableRawPointer { - - func downcastPointee(to: T.Type, assuming: U.Type) -> T { + func downcastPointee(to _: T.Type, assuming _: U.Type) -> T { if let OptionalT = T.self as? OptionalFromAny.Type { - return OptionalT.init(from: assumingMemoryBound(to: U.self).pointee) as! T + return OptionalT(from: assumingMemoryBound(to: U.self).pointee) as! T } else { return assumingMemoryBound(to: U.self).pointee as! T } @@ -131,7 +129,6 @@ private protocol OptionalFromAny { } extension Optional: OptionalFromAny { - init(from unsafeValue: Any?) { if let unsafeValue = unsafeValue { self = unsafeValue as? Wrapped diff --git a/Sources/Bond/Bond.swift b/Sources/Bond/Bond.swift index 28cec312..464647e0 100644 --- a/Sources/Bond/Bond.swift +++ b/Sources/Bond/Bond.swift @@ -28,7 +28,6 @@ import ReactiveKit /// one can create a Bond instance onto which signals can be bound. When a next event /// is sent on the bound signal, Bond will call the setter closure to update the target. public struct Bond: BindableProtocol { - private weak var target: Deallocatable? private let setter: (AnyObject, Element) -> Void private let context: ExecutionContext @@ -36,13 +35,13 @@ public struct Bond: BindableProtocol { public init(target: Target, context: ExecutionContext, setter: @escaping (Target, Element) -> Void) { self.target = target self.context = context - self.setter = { setter($0 as! Target, $1) } + self.setter = { setter($0 as! Target, $1) } } public init(target: Target, setter: @escaping (Target, Element) -> Void) where Target: BindingExecutionContextProvider { self.target = target - self.context = target.bindingExecutionContext - self.setter = { setter($0 as! Target, $1) } + context = target.bindingExecutionContext + self.setter = { setter($0 as! Target, $1) } } public func bind(signal: Signal) -> Disposable { @@ -61,7 +60,6 @@ public struct Bond: BindableProtocol { } extension ReactiveExtensions where Base: Deallocatable { - /// Creates a bond on the receiver. public func bond(context: ExecutionContext, setter: @escaping (Base, Element) -> Void) -> Bond { return Bond(target: base, context: context, setter: setter) @@ -69,7 +67,6 @@ extension ReactiveExtensions where Base: Deallocatable { } extension ReactiveExtensions where Base: Deallocatable, Base: BindingExecutionContextProvider { - /// Creates a bond on the receiver. public func bond(setter: @escaping (Base, Element) -> Void) -> Bond { return Bond(target: base, setter: setter) diff --git a/Sources/Bond/Data Sources/FlatDataSourceChangesetConvertible.swift b/Sources/Bond/Data Sources/FlatDataSourceChangesetConvertible.swift index bb105ddc..de0ee7f2 100644 --- a/Sources/Bond/Data Sources/FlatDataSourceChangesetConvertible.swift +++ b/Sources/Bond/Data Sources/FlatDataSourceChangesetConvertible.swift @@ -47,9 +47,7 @@ public protocol FlatDataSourceChangeset: ChangesetProtocol where Collection: QueryableFlatDataSourceProtocol, Operation: OrderedCollectionOperationProtocol, Operation.Index: FlatDataIndexConvertable, - Operation.Element == Collection.Item -{ -} + Operation.Element == Collection.Item {} /// A type that can be expressed as `FlatDataSourceChangeset`. public protocol FlatDataSourceChangesetConvertible { @@ -76,15 +74,12 @@ extension Array: FlatDataSourceChangesetConvertible { extension OrderedCollectionChangeset: FlatDataSourceChangeset where Collection: QueryableFlatDataSourceProtocol, Collection.Index: FlatDataIndexConvertable, - Collection.Item == Collection.Element -{ -} + Collection.Item == Collection.Element {} extension OrderedCollectionChangeset: FlatDataSourceChangesetConvertible where Collection: QueryableFlatDataSourceProtocol, Collection.Index: FlatDataIndexConvertable, - Collection.Item == Collection.Element -{ + Collection.Item == Collection.Element { public typealias Changeset = OrderedCollectionChangeset public var asFlatDataSourceChangeset: OrderedCollectionChangeset { diff --git a/Sources/Bond/Data Sources/OutlineChangesetConvertible.swift b/Sources/Bond/Data Sources/OutlineChangesetConvertible.swift index 6929f193..e8c5d217 100644 --- a/Sources/Bond/Data Sources/OutlineChangesetConvertible.swift +++ b/Sources/Bond/Data Sources/OutlineChangesetConvertible.swift @@ -14,21 +14,18 @@ public protocol OutlineChangesetConvertible { } extension TreeChangeset: OutlineChangesetConvertible { - public var asTreeArrayChangeset: TreeChangeset { return self } } extension TreeArray: OutlineChangesetConvertible { - public var asTreeArrayChangeset: TreeChangeset> { return TreeChangeset(collection: self, patch: []) } } extension ObjectTreeArray: OutlineChangesetConvertible { - public var asTreeArrayChangeset: TreeChangeset> { return TreeChangeset(collection: self, patch: []) } diff --git a/Sources/Bond/Data Sources/SectionedDataSourceChangesetConvertible.swift b/Sources/Bond/Data Sources/SectionedDataSourceChangesetConvertible.swift index 7cd7b158..3034883d 100644 --- a/Sources/Bond/Data Sources/SectionedDataSourceChangesetConvertible.swift +++ b/Sources/Bond/Data Sources/SectionedDataSourceChangesetConvertible.swift @@ -44,8 +44,7 @@ public protocol SectionedDataIndexPathConvertable { /// A changeset of an ordered collection that conforms to `SectionedDataSourceProtocol` and whose indices can be expressed as `IndexPath`. /// Signals of this type of changeset can be bound to table or collection views. -public protocol SectionedDataSourceChangeset: ChangesetProtocol where Diff: OrderedCollectionDiffProtocol, Diff.Index: SectionedDataIndexPathConvertable, Collection: SectionedDataSourceProtocol { -} +public protocol SectionedDataSourceChangeset: ChangesetProtocol where Diff: OrderedCollectionDiffProtocol, Diff.Index: SectionedDataIndexPathConvertable, Collection: SectionedDataSourceProtocol {} /// A type that can be expressed as `SectionedDataSourceChangeset`. public protocol SectionedDataSourceChangesetConvertible { @@ -54,12 +53,11 @@ public protocol SectionedDataSourceChangesetConvertible { } extension Array: QueryableSectionedDataSourceProtocol { - public var numberOfSections: Int { return 1 } - public func numberOfItems(inSection section: Int) -> Int { + public func numberOfItems(inSection _: Int) -> Int { return count } @@ -69,7 +67,6 @@ extension Array: QueryableSectionedDataSourceProtocol { } extension Array: SectionedDataSourceChangesetConvertible { - public var asSectionedDataSourceChangeset: OrderedCollectionChangeset<[Element]> { return OrderedCollectionChangeset(collection: self, patch: []) } @@ -78,14 +75,12 @@ extension Array: SectionedDataSourceChangesetConvertible { extension OrderedCollectionChangeset: SectionedDataSourceChangeset where Diff.Index: SectionedDataIndexPathConvertable, Collection: SectionedDataSourceProtocol {} extension OrderedCollectionChangeset: SectionedDataSourceChangesetConvertible where Diff.Index: SectionedDataIndexPathConvertable, Collection: SectionedDataSourceProtocol { - public var asSectionedDataSourceChangeset: OrderedCollectionChangeset { return self } } extension TreeArray: SectionedDataSourceProtocol { - public var numberOfSections: Int { return children.count } @@ -96,7 +91,6 @@ extension TreeArray: SectionedDataSourceProtocol { } extension Array2D: QueryableSectionedDataSourceProtocol { - public var numberOfSections: Int { return sections.count } @@ -111,7 +105,6 @@ extension Array2D: QueryableSectionedDataSourceProtocol { } extension TreeArray: SectionedDataSourceChangesetConvertible { - public var asSectionedDataSourceChangeset: TreeChangeset> { return TreeChangeset(collection: self, patch: []) } @@ -120,7 +113,6 @@ extension TreeArray: SectionedDataSourceChangesetConvertible { extension TreeChangeset: SectionedDataSourceChangeset where Collection: SectionedDataSourceProtocol {} extension TreeChangeset: SectionedDataSourceChangesetConvertible where Collection: SectionedDataSourceProtocol { - public typealias Changeset = TreeChangeset public var asSectionedDataSourceChangeset: TreeChangeset { @@ -129,14 +121,12 @@ extension TreeChangeset: SectionedDataSourceChangesetConvertible where Collectio } extension IndexPath: SectionedDataIndexPathConvertable { - public var asSectionDataIndexPath: IndexPath { return self } } extension Int: SectionedDataIndexPathConvertable { - public var asSectionDataIndexPath: IndexPath { return [0, self] } diff --git a/Sources/Bond/Data Structures/Array2D.swift b/Sources/Bond/Data Structures/Array2D.swift index 0d591ff0..dfd5bb94 100644 --- a/Sources/Bond/Data Structures/Array2D.swift +++ b/Sources/Bond/Data Structures/Array2D.swift @@ -32,10 +32,8 @@ public protocol Array2DProtocol: RangeReplaceableTreeProtocol where Children == /// A data structure whose items are grouped into section. /// Array2D can be used as an underlying data structure for UITableView or UICollectionView data source. public struct Array2D: Array2DProtocol { - /// Represents a single section of Array2D. public struct Section { - /// Section metadata, e.g. section title. public var metadata: SectionMetadata @@ -60,7 +58,6 @@ public struct Array2D: Array2DProtocol { // MARK: Convenience methods extension Array2D { - /// Create a new Array2D from the given list of section metadata and respective items. /// Each SectionMetadata corresponds to one section populated with the given items. public init(sectionsWithItems: [(SectionMetadata, [Item])]) { @@ -97,24 +94,23 @@ extension Array2D { sections.append(Section(metadata: metadata, items: [])) } - /// Append `item` to the section `section` of the array. public mutating func appendItem(_ item: Item, toSectionAt sectionIndex: Int) { sections[sectionIndex].items.append(item) } /// Insert section at `index` with `items`. - public mutating func insert(section: Section, at index: Int) { + public mutating func insert(section: Section, at index: Int) { sections.insert(section, at: index) } /// Insert section at `index` with `items`. - public mutating func insert(section metadata: SectionMetadata, at index: Int) { + public mutating func insert(section metadata: SectionMetadata, at index: Int) { sections.insert(Section(metadata: metadata, items: []), at: index) } /// Insert `item` at `indexPath`. - public mutating func insert(item: Item, at indexPath: IndexPath) { + public mutating func insert(item: Item, at indexPath: IndexPath) { sections[indexPath.section].items.insert(item, at: indexPath.item) } @@ -148,7 +144,7 @@ extension Array2D { /// Remove all items from the array. Keep empty sections. public mutating func removeAllItems() { - for index in 0.. Bool) -> Int { var lo = 0 - var hi = self.count - 1 + var hi = count - 1 while lo <= hi { - let mid = (lo + hi)/2 + let mid = (lo + hi) / 2 if isOrderedBefore(self[mid], element) { lo = mid + 1 } else if isOrderedBefore(element, self[mid]) { @@ -48,16 +47,13 @@ extension RangeReplaceableCollection where Index == Int { } } - extension Collection { - func indices(where isIncluded: (Element) -> Bool) -> [Index] { return indices.filter { isIncluded(self[$0]) } } } extension RangeReplaceableCollection { - public mutating func move(from fromIndex: Index, to toIndex: Index) { let item = remove(at: fromIndex) insert(item, at: toIndex) @@ -65,7 +61,6 @@ extension RangeReplaceableCollection { } extension RangeReplaceableCollection where Index: Strideable { - public mutating func move(from fromIndices: [Index], to toIndex: Index) { let items = fromIndices.map { self[$0] } for index in fromIndices.sorted().reversed() { diff --git a/Sources/Bond/Data Structures/IndexPath+Bond.swift b/Sources/Bond/Data Structures/IndexPath+Bond.swift index bcd47d02..61e9e052 100644 --- a/Sources/Bond/Data Structures/IndexPath+Bond.swift +++ b/Sources/Bond/Data Structures/IndexPath+Bond.swift @@ -25,13 +25,12 @@ import Foundation extension IndexPath { - public func isAffectedByDeletionOrInsertion(at index: IndexPath) -> Bool { assert(index.count > 0) - assert(self.count > 0) - guard index.count <= self.count else { return false } + assert(count > 0) + guard index.count <= count else { return false } let testLevel = index.count - 1 - if index.prefix(testLevel) == self.prefix(testLevel) { + if index.prefix(testLevel) == prefix(testLevel) { return index[testLevel] <= self[testLevel] } else { return false @@ -39,14 +38,14 @@ extension IndexPath { } public func shifted(by: Int, atLevelOf other: IndexPath) -> IndexPath { - assert(self.count > 0) + assert(count > 0) assert(other.count > 0) let level = other.count - 1 - guard level < self.count else { return self } + guard level < count else { return self } if by == -1 { - return self.advanced(by: -1, atLevel: level) + return advanced(by: -1, atLevel: level) } else if by == 1 { - return self.advanced(by: 1, atLevel: level) + return advanced(by: 1, atLevel: level) } else { fatalError() } @@ -59,8 +58,8 @@ extension IndexPath { } public func isAncestor(of other: IndexPath) -> Bool { - guard self.count < other.count else { return false } - return self == other.prefix(self.count) + guard count < other.count else { return false } + return self == other.prefix(count) } public func replacingAncestor(_ ancestor: IndexPath, with newAncestor: IndexPath) -> IndexPath { diff --git a/Sources/Bond/Data Structures/TreeArray.swift b/Sources/Bond/Data Structures/TreeArray.swift index ebc94dca..1037ed05 100644 --- a/Sources/Bond/Data Structures/TreeArray.swift +++ b/Sources/Bond/Data Structures/TreeArray.swift @@ -26,11 +26,10 @@ import Foundation /// A tree array represents a valueless root node of a tree structure where children are of TreeNode type. public struct TreeArray: RangeReplaceableTreeProtocol, Instantiatable, CustomDebugStringConvertible { - public var children: [TreeNode] public init() { - self.children = [] + children = [] } public init(_ children: [TreeNode]) { @@ -38,7 +37,7 @@ public struct TreeArray: RangeReplaceableTreeProtocol, Instantiatable, Cu } public init(childrenValues: [Value]) { - self.children = childrenValues.map { TreeNode($0) } + children = childrenValues.map { TreeNode($0) } } public var debugDescription: String { @@ -52,12 +51,11 @@ public struct TreeArray: RangeReplaceableTreeProtocol, Instantiatable, Cu /// Class-based variant of TreeArray. public final class ObjectTreeArray: RangeReplaceableTreeProtocol, Instantiatable, CustomDebugStringConvertible { - public var value: Void = () public var children: [ObjectTreeNode] public required init() { - self.children = [] + children = [] } public init(_ children: [ObjectTreeNode]) { @@ -77,7 +75,7 @@ public final class ObjectTreeArray: RangeReplaceableTreeProtocol, Instant return TreeArray(children.map { $0.asTreeNode }) } set { - self.children = newValue.children.map { $0.asObject } + children = newValue.children.map { $0.asObject } } } } diff --git a/Sources/Bond/Data Structures/TreeNode.swift b/Sources/Bond/Data Structures/TreeNode.swift index 95bbee19..e89cccd3 100644 --- a/Sources/Bond/Data Structures/TreeNode.swift +++ b/Sources/Bond/Data Structures/TreeNode.swift @@ -32,13 +32,12 @@ public protocol TreeNodeWithValueProtocol: RangeReplaceableTreeProtocol { /// A tree node represents a node in a tree structure. /// A tree node has a value associated with itself and zero or more child tree nodes of the same TreeNode type. public struct TreeNode: TreeNodeWithValueProtocol, CustomDebugStringConvertible { - public var value: Value public var children: [TreeNode] public init(_ value: Value) { self.value = value - self.children = [] + children = [] } public init(_ value: Value, _ children: [TreeNode]) { @@ -78,13 +77,12 @@ public struct TreeNode: TreeNodeWithValueProtocol, CustomDebugStringConve /// Class-based variant of TreeNode. public final class ObjectTreeNode: TreeNodeWithValueProtocol, CustomDebugStringConvertible { - public var value: Value public var children: [ObjectTreeNode] public init(_ value: Value) { self.value = value - self.children = [] + children = [] } public init(_ value: Value, _ children: [ObjectTreeNode]) { @@ -109,8 +107,8 @@ public final class ObjectTreeNode: TreeNodeWithValueProtocol, CustomDebug } set { if indexPath.isEmpty { - self.value = newValue.value - self.children = newValue.children + value = newValue.value + children = newValue.children } else { children[indexPath[0]][indexPath.dropFirst()] = newValue } diff --git a/Sources/Bond/Data Structures/TreeProtocol+Differ.swift b/Sources/Bond/Data Structures/TreeProtocol+Differ.swift index 9ff18341..2591bdd4 100644 --- a/Sources/Bond/Data Structures/TreeProtocol+Differ.swift +++ b/Sources/Bond/Data Structures/TreeProtocol+Differ.swift @@ -22,18 +22,17 @@ // THE SOFTWARE. // -import Foundation import Differ +import Foundation extension TreeProtocol { - public func diff(_ other: Self, sourceRoot: IndexPath = [], destinationRoot: IndexPath = [], areEqual: @escaping (Children.Element, Children.Element) -> Bool) -> OrderedCollectionDiff { let traces = children.outputDiffPathTraces(to: other.children, isEqual: areEqual) let diff = Diff(traces: traces) var collectionDiff = OrderedCollectionDiff(from: diff, sourceRoot: sourceRoot, destinationRoot: destinationRoot) for trace in traces { - if trace.from.x + 1 == trace.to.x && trace.from.y + 1 == trace.to.y { + if trace.from.x + 1 == trace.to.x, trace.from.y + 1 == trace.to.y { // match point x -> y, diff children let childA = children[trace.from.x] let childB = other.children[trace.from.y] @@ -56,14 +55,13 @@ extension TreeProtocol { } extension OrderedCollectionDiff where Index == IndexPath { - public init(from diff: Diff, sourceRoot: IndexPath, destinationRoot: IndexPath) { self.init() for element in diff.elements { switch element { - case .insert(let at): + case let .insert(at): inserts.append(destinationRoot.appending(at)) - case .delete(let at): + case let .delete(at): deletes.append(sourceRoot.appending(at)) } } diff --git a/Sources/Bond/Data Structures/TreeProtocol.swift b/Sources/Bond/Data Structures/TreeProtocol.swift index 04fec806..2c46a27f 100644 --- a/Sources/Bond/Data Structures/TreeProtocol.swift +++ b/Sources/Bond/Data Structures/TreeProtocol.swift @@ -27,7 +27,6 @@ import Foundation /// A protocol that provides abstraction over a tree type. /// A tree can be any containter type that encapsulates objects or values that are also trees. public protocol TreeProtocol { - /// A collection of child nodes that are trees and whose children are also trees associatedtype Children: Collection where Children.Element: TreeProtocol, @@ -39,36 +38,30 @@ public protocol TreeProtocol { } public protocol MutableTreeProtocol: TreeProtocol where Children: MutableCollection, Children.Element: MutableTreeProtocol { - /// Child nodes of the current tree node. var children: Children { get set } } public protocol RangeReplaceableTreeProtocol: MutableTreeProtocol { - /// Replace child nodes at the given range with the given child nodes. /// Analogous of `RangeReplaceableCollection.replaceSubrange`. mutating func replaceSubrange(_ subrange: Range, with newChildren: C) where C.Element == Children.Element } extension TreeProtocol { - public subscript(childAt indexPath: IndexPath) -> Children.Element { - get { - guard !indexPath.isEmpty else { - fatalError("Index path cannot be empty!") - } - if indexPath.count == 1 { - return children[indexPath[0]] - } else { - return children[indexPath[0]][childAt: indexPath.dropFirst()] - } + guard !indexPath.isEmpty else { + fatalError("Index path cannot be empty!") + } + if indexPath.count == 1 { + return children[indexPath[0]] + } else { + return children[indexPath[0]][childAt: indexPath.dropFirst()] } } } extension MutableTreeProtocol { - public subscript(childAt indexPath: IndexPath) -> Children.Element { get { guard !indexPath.isEmpty else { @@ -94,7 +87,6 @@ extension MutableTreeProtocol { } extension RangeReplaceableTreeProtocol where Children: RangeReplaceableCollection, Children.Element: RangeReplaceableTreeProtocol { - public mutating func replaceSubrange(_ subrange: Range, with newChildren: C) where C.Element == Children.Element { guard subrange.lowerBound.count == subrange.upperBound.count else { fatalError("Range lowerBound and upperBound must point to the same subtree!") @@ -103,13 +95,13 @@ extension RangeReplaceableTreeProtocol where Children: RangeReplaceableCollectio fatalError("Index paths in the given range must not be empty!") } if subrange.lowerBound.count == 1 { - children.replaceSubrange(subrange.lowerBound[0].. [Children.Element] { - return indexes.sorted().reversed().map { remove(at:$0) }.reversed() + return indexes.sorted().reversed().map { remove(at: $0) }.reversed() } /// Remove all child node. Only the tree root node (self) will remain. public mutating func removeAll() { - replaceSubrange([0]..<[children.count], with: []) + replaceSubrange([0] ..< [children.count], with: []) } /// Move the node from one position to another. diff --git a/Sources/Bond/Data Structures/TreeView.swift b/Sources/Bond/Data Structures/TreeView.swift index 8443a707..5db984d7 100644 --- a/Sources/Bond/Data Structures/TreeView.swift +++ b/Sources/Bond/Data Structures/TreeView.swift @@ -25,7 +25,6 @@ import Foundation extension TreeProtocol { - /// Provides a view of the tree that is a flat collection of tree nodes in depth-first search order. /// Indices of the provided collection are of type `IndexPath`. /// - complexity: Accessing: O(1), Iterating the tree view: O(n) in both time and space @@ -52,7 +51,6 @@ extension TreeProtocol { } extension TreeProtocol where Children.Element == Self { - /// Provides a view of the tree that is a flat collection of tree nodes in depth-first search order. /// Indices of the provided collection are of type `IndexPath`. /// - complexity: Accessing: O(1), Iterating the tree view: O(n) in both time and space @@ -83,7 +81,6 @@ public protocol IndexPathTreeIterator: IteratorProtocol where Element == (elemen } public class TreeView: Collection { - public let startIndex: IndexPath public let endIndex: IndexPath @@ -99,7 +96,7 @@ public class TreeView: Collection { self.endIndex = endIndex var iterator = iterator var previousIndexPath: IndexPath? - self.loadNextNode = { + loadNextNode = { if let next = iterator.next() { if let previousIndexPath = previousIndexPath { self.lookupTreeMap[previousIndexPath]?.after = next.indexPath @@ -132,7 +129,6 @@ public class TreeView: Collection { } struct DFSTreeIterator: IndexPathTreeIterator { - typealias Item = (element: Tree.Children.Element, indexPath: IndexPath) var remainingItems: [Item] @@ -140,7 +136,7 @@ struct DFSTreeIterator: IndexPathTreeIterator { let indexPathTransformer: (IndexPath) -> IndexPath init(remainingTreeNodes: [Tree.Children.Element], indexPathTransformer: @escaping (IndexPath) -> IndexPath = { $0 }) { - self.remainingItems = remainingTreeNodes.enumerated().map { ($0.element, [$0.offset]) } + remainingItems = remainingTreeNodes.enumerated().map { ($0.element, [$0.offset]) } self.indexPathTransformer = indexPathTransformer } @@ -154,7 +150,6 @@ struct DFSTreeIterator: IndexPathTreeIterator { } struct BFSTreeIterator: IndexPathTreeIterator { - typealias Item = (element: Tree.Children.Element, indexPath: IndexPath) var remainingItems: [Item] @@ -162,7 +157,7 @@ struct BFSTreeIterator: IndexPathTreeIterator { let indexPathTransformer: (IndexPath) -> IndexPath init(remainingTreeNodes: [Tree.Children.Element], indexPathTransformer: @escaping (IndexPath) -> IndexPath = { $0 }) { - self.remainingItems = remainingTreeNodes.enumerated().map { ($0.element, [$0.offset]) } + remainingItems = remainingTreeNodes.enumerated().map { ($0.element, [$0.offset]) } self.indexPathTransformer = indexPathTransformer } diff --git a/Sources/Bond/Deprecations/Deprecations.swift b/Sources/Bond/Deprecations/Deprecations.swift index 63a3297d..43ff6beb 100644 --- a/Sources/Bond/Deprecations/Deprecations.swift +++ b/Sources/Bond/Deprecations/Deprecations.swift @@ -25,7 +25,6 @@ public enum DataSourceEventKind {} @available(*, deprecated) extension DataSourceEventProtocol { - @available(*, deprecated, renamed: "Collection") public typealias DataSource = Collection @@ -36,13 +35,11 @@ extension DataSourceEventProtocol { } extension TreeArray { - @available(*, deprecated, renamed: "Value") public typealias ChildValue = Value } extension ChangesetContainerProtocol where Changeset.Collection: TreeProtocol { - /// Returns `true` if underlying collection is empty, `false` otherwise. @available(*, deprecated, renamed: "tree.children.isEmpty") public var isEmpty: Bool { @@ -66,7 +63,6 @@ public typealias TreeNodeProtocol = TreeProtocol public typealias TreeArrayProtocol = RangeReplaceableTreeProtocol extension TreeProtocol { - @available(*, deprecated, renamed: "Children.Element") public typealias ChildNode = Children.Element @@ -82,7 +78,6 @@ extension TreeProtocol { } extension TreeProtocol where Children.Element: Equatable { - @available(*, deprecated, renamed: "depthFirst.firstIndex(of:)") public func index(of node: Children.Element) -> IndexPath? { return depthFirst.firstIndex(of: node) @@ -90,7 +85,6 @@ extension TreeProtocol where Children.Element: Equatable { } extension RangeReplaceableTreeProtocol { - @available(*, deprecated, message: "Use subscript [childAt: IndexPath] instead") public subscript(indexPath: IndexPath) -> Children.Element { get { diff --git a/Sources/Bond/DynamicSubject.swift b/Sources/Bond/DynamicSubject.swift index e68c6f86..29f22da2 100644 --- a/Sources/Bond/DynamicSubject.swift +++ b/Sources/Bond/DynamicSubject.swift @@ -22,8 +22,8 @@ // THE SOFTWARE. // -import ReactiveKit import Foundation +import ReactiveKit public typealias DynamicSubject = FailableDynamicSubject @@ -33,7 +33,6 @@ public typealias DynamicSubject = FailableDynamicSubject: SubjectProtocol, BindableProtocol { - private weak var target: AnyObject? private var signal: Signal private let context: ExecutionContext @@ -51,8 +50,8 @@ public struct FailableDynamicSubject: SubjectProtoc self.target = target self.signal = signal self.context = context - self.getter = { get($0 as! Target) } - self.setter = { set($0 as! Target, $1) } + getter = { get($0 as! Target) } + setter = { set($0 as! Target, $1) } self.triggerEventOnSetting = triggerEventOnSetting } @@ -65,8 +64,8 @@ public struct FailableDynamicSubject: SubjectProtoc self.target = target self.signal = signal self.context = context - self.getter = { .success(get($0 as! Target)) } - self.setter = { set($0 as! Target, $1) } + getter = { .success(get($0 as! Target)) } + setter = { set($0 as! Target, $1) } self.triggerEventOnSetting = triggerEventOnSetting } @@ -76,16 +75,16 @@ public struct FailableDynamicSubject: SubjectProtoc get: @escaping (AnyObject) -> Result, set: @escaping (AnyObject, Element) -> Void, triggerEventOnSetting: Bool = true) { - self.target = _target + target = _target self.signal = signal self.context = context - self.getter = { get($0) } - self.setter = { set($0, $1) } + getter = { get($0) } + setter = { set($0, $1) } self.triggerEventOnSetting = triggerEventOnSetting } public func on(_ event: Signal.Event) { - if case .next(let element) = event, let target = target { + if case let .next(element) = event, let target = target { setter(target, element) if triggerEventOnSetting { subject.send(()) @@ -99,9 +98,9 @@ public struct FailableDynamicSubject: SubjectProtoc return signal.prepend(()).merge(with: subject).tryMap { [weak target] () -> Result in if let target = target { switch getter(target) { - case .success(let element): + case let .success(element): return .success(element) - case .failure(let error): + case let .failure(error): return .failure(error) } } else { @@ -119,7 +118,7 @@ public struct FailableDynamicSubject: SubjectProtoc return signal.prefix(untilOutputFrom: (target as! Deallocatable).deallocated).observe { [weak target] event in context.execute { [weak target] in switch event { - case .next(let element): + case let .next(element): guard let target = target else { return } setter(target, element) if triggerEventOnSetting { @@ -139,7 +138,7 @@ public struct FailableDynamicSubject: SubjectProtoc public var value: Element! { if let target = target { switch getter(target) { - case .success(let value): + case let .success(value): return value case .failure: return nil @@ -160,13 +159,13 @@ public struct FailableDynamicSubject: SubjectProtoc context: context, get: { [getter] (target) -> Result in switch getter(target) { - case .success(let value): + case let .success(value): return .success(getTransform(value)) - case .failure(let error): + case let .failure(error): return .failure(error) } }, - set: { [setter] (target, element) in + set: { [setter] target, element in setter(target, setTransform(element)) } ) @@ -174,7 +173,6 @@ public struct FailableDynamicSubject: SubjectProtoc } extension ReactiveExtensions where Base: Deallocatable { - public func dynamicSubject(signal: Signal, context: ExecutionContext, triggerEventOnSetting: Bool = true, @@ -185,7 +183,6 @@ extension ReactiveExtensions where Base: Deallocatable { } extension ReactiveExtensions where Base: Deallocatable, Base: BindingExecutionContextProvider { - public func dynamicSubject(signal: Signal, triggerEventOnSetting: Bool = true, get: @escaping (Base) -> Element, diff --git a/Sources/Bond/Instantiatable.swift b/Sources/Bond/Instantiatable.swift index 3f312f4e..cc8a2218 100644 --- a/Sources/Bond/Instantiatable.swift +++ b/Sources/Bond/Instantiatable.swift @@ -25,6 +25,5 @@ import Foundation public protocol Instantiatable { - init() } diff --git a/Sources/Bond/MainBlockDisposable.swift b/Sources/Bond/MainBlockDisposable.swift index cd94cb6a..c5058fc5 100644 --- a/Sources/Bond/MainBlockDisposable.swift +++ b/Sources/Bond/MainBlockDisposable.swift @@ -1,6 +1,6 @@ // // MainBlockDisposable.swift -// +// // // Created by Diego Rodriguez on 12/12/2019. // @@ -11,19 +11,18 @@ import ReactiveKit /// A disposable that executes the given block on the main thread upon disposing. public final class MainBlockDisposable: Disposable { private let _blockDisposable: BlockDisposable - - public init(_ handler: @escaping () -> ()) { + + public init(_ handler: @escaping () -> Void) { _blockDisposable = BlockDisposable(handler) } - + public var isDisposed: Bool { return _blockDisposable.isDisposed } - + public func dispose() { ExecutionContext.immediateOnMain.execute { [weak self] in - self?._blockDisposable.dispose() + self?._blockDisposable.dispose() } } } - diff --git a/Sources/Bond/Observable Collections/Changeset.swift b/Sources/Bond/Observable Collections/Changeset.swift index 0d8787dd..49f93183 100644 --- a/Sources/Bond/Observable Collections/Changeset.swift +++ b/Sources/Bond/Observable Collections/Changeset.swift @@ -27,7 +27,6 @@ import Foundation /// A type that represents a collection change description, i.e. a modification of a collection. /// Changeset provides the collection itself as well as the change diff and patch. public protocol ChangesetProtocol { - associatedtype Diff: Instantiatable associatedtype Operation associatedtype Collection @@ -62,7 +61,6 @@ public protocol ChangesetProtocol { /// A type that represents a collection change description, i.e. a modification of a collection. /// Changeset provides the collection itself as well as the change diff and patch. open class Changeset: ChangesetProtocol { - open var precalculatedDiff: Diff? open var precalculatedPatch: [Operation]? @@ -84,25 +82,25 @@ open class Changeset: ChangesetProt public required init(collection: Collection, patch: [Operation]) { self.collection = collection - self.precalculatedPatch = patch + precalculatedPatch = patch } public required init(collection: Collection, diff: Diff) { self.collection = collection - self.precalculatedDiff = diff + precalculatedDiff = diff } public init(collection: Collection, patch: [Operation], diff: Diff) { self.collection = collection - self.precalculatedPatch = patch - self.precalculatedDiff = diff + precalculatedPatch = patch + precalculatedDiff = diff } - open func calculateDiff(from patch: [Operation]) -> Diff { + open func calculateDiff(from _: [Operation]) -> Diff { return Diff() } - open func calculatePatch(from diff: Diff) -> [Operation] { + open func calculatePatch(from _: Diff) -> [Operation] { return [] } } diff --git a/Sources/Bond/Observable Collections/ChangesetContainer.swift b/Sources/Bond/Observable Collections/ChangesetContainer.swift index e19263c5..88c55a78 100644 --- a/Sources/Bond/Observable Collections/ChangesetContainer.swift +++ b/Sources/Bond/Observable Collections/ChangesetContainer.swift @@ -26,8 +26,7 @@ import Foundation import ReactiveKit /// A type that contains or wraps a changeset. -public protocol ChangesetContainerProtocol: class { - +public protocol ChangesetContainerProtocol: AnyObject { associatedtype Changeset: ChangesetProtocol /// Contained changeset. @@ -35,12 +34,10 @@ public protocol ChangesetContainerProtocol: class { } public protocol MutableChangesetContainerProtocol: ChangesetContainerProtocol { - var changeset: Changeset { get set } } extension ChangesetContainerProtocol { - public typealias Collection = Changeset.Collection public typealias Operation = Changeset.Operation public typealias Diff = Changeset.Diff @@ -52,7 +49,6 @@ extension ChangesetContainerProtocol { } extension MutableChangesetContainerProtocol { - /// Update the collection and provide a description of changes as patch. public func descriptiveUpdate(_ update: (inout Collection) -> [Operation]) { var collection = changeset.collection @@ -99,7 +95,6 @@ extension MutableChangesetContainerProtocol { } extension ChangesetContainerProtocol where Changeset.Collection: Swift.Collection { - /// Returns `true` if underlying collection is empty, `false` otherwise. public var isEmpty: Bool { return collection.isEmpty @@ -112,14 +107,11 @@ extension ChangesetContainerProtocol where Changeset.Collection: Swift.Collectio /// Access the collection element at `index`. public subscript(index: Collection.Index) -> Collection.Element { - get { - return collection[index] - } + return collection[index] } } extension ChangesetContainerProtocol where Changeset.Collection: Swift.Collection, Changeset.Collection.Index == Int { - /// Underlying array. public var array: Collection { return collection @@ -127,7 +119,6 @@ extension ChangesetContainerProtocol where Changeset.Collection: Swift.Collectio } extension ChangesetContainerProtocol where Changeset.Collection: TreeProtocol { - /// Underlying tree. public var tree: Collection { return collection @@ -135,14 +126,11 @@ extension ChangesetContainerProtocol where Changeset.Collection: TreeProtocol { /// Access the element at `index`. public subscript(childAt indexPath: IndexPath) -> Collection.Children.Element { - get { - return collection[childAt: indexPath] - } + return collection[childAt: indexPath] } } extension SignalProtocol where Error == Never { - /// Bind the collection signal to the given changeset container like MutableObervableArray. @discardableResult public func bind(to changesetContainer: C) -> Disposable where C: BindableProtocol, C.Element == C.Changeset, C.Changeset.Collection == Element { diff --git a/Sources/Bond/Observable Collections/OrderedCollectionChangeset.swift b/Sources/Bond/Observable Collections/OrderedCollectionChangeset.swift index df5e12aa..db49ff58 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionChangeset.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionChangeset.swift @@ -29,12 +29,10 @@ public protocol OrderedCollectionChangesetProtocol: ChangesetProtocol where Operation == OrderedCollectionOperation, Diff == OrderedCollectionDiff, Collection.Index: Strideable { - var asOrderedCollectionChangeset: OrderedCollectionChangeset { get } } public final class OrderedCollectionChangeset: Changeset, OrderedCollectionDiff>, OrderedCollectionChangesetProtocol where Collection.Index: Strideable { - public override func calculateDiff(from patch: [OrderedCollectionOperation]) -> OrderedCollectionDiff { return Diff(from: patch) } @@ -49,7 +47,6 @@ public final class OrderedCollectionChangeset: Cha } extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionChangesetProtocol, Changeset.Collection: MutableCollection { - /// Access or update the element at `index`. public subscript(index: Collection.Index) -> Collection.Element { get { @@ -65,7 +62,6 @@ extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionCh } extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionChangesetProtocol, Changeset.Collection: RangeReplaceableCollection { - /// Append `newElement` at the end of the collection. public func append(_ newElement: Collection.Element) { descriptiveUpdate { (collection) -> [Operation] in @@ -86,7 +82,7 @@ extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionCh public func insert(contentsOf newElements: [Collection.Element], at index: Collection.Index) { descriptiveUpdate { (collection) -> [Operation] in collection.insert(contentsOf: newElements, at: index) - let indices = (0..) { guard !subrange.isEmpty else { return } @@ -153,4 +148,3 @@ Changeset.Collection.Index.Stride == Int { } } } - diff --git a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Differ.swift b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Differ.swift index eb3d739d..f34da4eb 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Differ.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Differ.swift @@ -22,12 +22,11 @@ // THE SOFTWARE. // -import Foundation import Differ +import Foundation import ReactiveKit extension SignalProtocol where Element: TreeProtocol { - /// Diff each next element (tree node) against the previous one and emit a diff event. public func diff(_ areEqual: @escaping (Element.Children.Element, Element.Children.Element) -> Bool) -> Signal, Error> { return diff(generateDiff: { c1, c2 in c1.diff(c2, areEqual: areEqual) }) @@ -35,7 +34,6 @@ extension SignalProtocol where Element: TreeProtocol { } extension SignalProtocol where Element: TreeProtocol, Element.Children.Element: Equatable { - /// Diff each next element (tree node) against the previous one and emit a diff event. public func diff() -> Signal, Error> { return diff(generateDiff: { c1, c2 in c1.diff(c2, areEqual: ==) }) @@ -43,25 +41,23 @@ extension SignalProtocol where Element: TreeProtocol, Element.Children.Element: } extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtocol { - /// Replace the underlying tree node with the given tree node. Setting `performDiff: true` will make the framework /// calculate the diff between the existing and new tree node and emit an event with the calculated diff. /// - Complexity: O((N+M)*D) if `performDiff: true`, O(1) otherwise. public func replace(with newCollection: Changeset.Collection, performDiff: Bool, areEqual: @escaping (Changeset.Collection.Children.Element, Changeset.Collection.Children.Element) -> Bool) { replace(with: newCollection, performDiff: performDiff) { (old, new) -> OrderedCollectionDiff in - return old.diff(new, areEqual: areEqual) + old.diff(new, areEqual: areEqual) } } } extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtocol, Changeset.Collection.Children.Element: Equatable { - /// Replace the underlying tree node with the given tree node. Setting `performDiff: true` will make the framework /// calculate the diff between the existing and new tree node and emit an event with the calculated diff. /// - Complexity: O((N+M)*D) if `performDiff: true`, O(1) otherwise. public func replace(with newCollection: Changeset.Collection, performDiff: Bool) { replace(with: newCollection, performDiff: performDiff) { (old, new) -> OrderedCollectionDiff in - return old.diff(new, areEqual: ==) + old.diff(new, areEqual: ==) } } } diff --git a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Patch.swift b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Patch.swift index 702d1f50..9e165d76 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Patch.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath+Patch.swift @@ -25,9 +25,7 @@ import Foundation extension OrderedCollectionDiff where Index == IndexPath { - private struct Edit { - var deletionIndex: IndexPath? var insertionIndex: IndexPath? var element: Element? @@ -46,7 +44,6 @@ extension OrderedCollectionDiff where Index == IndexPath { } public func generatePatch(to collection: C) -> [OrderedCollectionOperation] { - let inserts = self.inserts.map { Edit(deletionIndex: nil, insertionIndex: $0, element: collection[childAt: $0]) } let deletes = self.deletes.map { Edit(deletionIndex: $0, insertionIndex: nil, element: nil) } let moves = self.moves.map { Edit(deletionIndex: $0.from, insertionIndex: $0.to, element: nil) } @@ -106,7 +103,7 @@ extension OrderedCollectionDiff where Index == IndexPath { var deletionScript = Array(deletionTree.depthFirst.indices.dropFirst().map { deletesAndMoves[deletionTree[$0].value] }.reversed()) var insertionSeedScript = deletionScript var moveCounter = 0 - for index in 0.. OrderedCollectionOperation in - return .update(at: pair.0, newElement: collection[childAt: pair.1]) + .update(at: pair.0, newElement: collection[childAt: pair.1]) } return updates + patch diff --git a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath.swift b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath.swift index f9db7a9f..e89cca8e 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionDiff+IndexPath.swift @@ -25,7 +25,6 @@ import Foundation extension OrderedCollectionDiff where Index == IndexPath { - /// Calculates diff from the given patch. /// - complexity: O(Nˆ2) where N is the number of patch operations. public init(from patch: [OrderedCollectionOperation]) { @@ -41,18 +40,18 @@ extension OrderedCollectionDiff where Index == IndexPath { return } - for patchSoFar in (1...patch.count).map({ patch.prefix(upTo: $0) }) { + for patchSoFar in (1 ... patch.count).map({ patch.prefix(upTo: $0) }) { let patchToUndo = Array(patchSoFar.dropLast()) switch patchSoFar.last! { - case .insert(let atIndex): + case let .insert(atIndex): recordInsertion(at: atIndex, patch: patchToUndo) - case .delete(let atIndex): + case let .delete(atIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: atIndex) recordDeletion(at: atIndex, sourceIndex: sourceIndex, patch: patchToUndo) - case .update(let atIndex): + case let .update(atIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: atIndex) recordUpdate(at: atIndex, sourceIndex: sourceIndex, patch: patchToUndo) - case .move(let fromIndex, let toIndex): + case let .move(fromIndex, toIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: fromIndex) recordMove(from: fromIndex, to: toIndex, sourceIndex: sourceIndex, patch: patchToUndo) } @@ -74,19 +73,18 @@ extension OrderedCollectionDiff where Index == IndexPath { return } - forEachDestinationIndex { (index) in + forEachDestinationIndex { index in if index.isAffectedByDeletionOrInsertion(at: insertionIndex) { index = index.shifted(by: 1, atLevelOf: insertionIndex) } } - + inserts.append(insertionIndex) } private mutating func recordDeletion(at deletionIndex: Index, sourceIndex: Index?, patch: [AnyOrderedCollectionOperation]) { - func adjustDestinationIndices() { - forEachDestinationIndex { (index) in + forEachDestinationIndex { index in if index.isAffectedByDeletionOrInsertion(at: deletionIndex) { index = index.shifted(by: -1, atLevelOf: deletionIndex) } @@ -145,7 +143,6 @@ extension OrderedCollectionDiff where Index == IndexPath { } private mutating func recordUpdate(at updateIndex: Index, sourceIndex: Index?, patch: [AnyOrderedCollectionOperation]) { - // If updating an inserted index or in a such subtree if inserts.contains(where: { $0 == updateIndex || $0.isAncestor(of: updateIndex) }) { return @@ -268,12 +265,12 @@ extension OrderedCollectionDiff where Index == IndexPath { var movesIntoSubtree: [Int] = [] func adjustDestinationIndices() { - forEachDestinationIndex(insertsToSkip: Set(insertsIntoSubtree), movesToSkip: Set(movesIntoSubtree)) { (index) in + forEachDestinationIndex(insertsToSkip: Set(insertsIntoSubtree), movesToSkip: Set(movesIntoSubtree)) { index in if index.isAffectedByDeletionOrInsertion(at: fromIndex) { index = index.shifted(by: -1, atLevelOf: fromIndex) } } - forEachDestinationIndex(insertsToSkip: Set(insertsIntoSubtree), movesToSkip: Set(movesIntoSubtree)) { (index) in + forEachDestinationIndex(insertsToSkip: Set(insertsIntoSubtree), movesToSkip: Set(movesIntoSubtree)) { index in if index.isAffectedByDeletionOrInsertion(at: toIndex) { index = index.shifted(by: 1, atLevelOf: toIndex) } @@ -313,7 +310,7 @@ extension OrderedCollectionDiff where Index == IndexPath { updates.removeAll(where: { move.from.isAncestor(of: $0) }) deletes.removeAll(where: { move.from.isAncestor(of: $0) }) - inserts.removeAll(where: { move.to.isAncestor(of: $0 )}) + inserts.removeAll(where: { move.to.isAncestor(of: $0) }) deletes.append(move.from) if !inserts.contains(where: { $0.isAncestor(of: move.to) }) { @@ -322,10 +319,10 @@ extension OrderedCollectionDiff where Index == IndexPath { } private mutating func forEachDestinationIndex(insertsToSkip: Set = [], movesToSkip: Set = [], apply: (inout Index) -> Void) { - for i in 0.. Bool) -> Signal, Error> { return diff(generateDiff: { c1, c2 in OrderedCollectionDiff(from: c1.extendedDiff(c2, isEqual: areEqual)) }) @@ -52,7 +50,6 @@ extension SignalProtocol where Element: Collection, Element.Index == Int { } extension SignalProtocol where Element: Collection, Element.Element: Equatable, Element.Index == Int { - /// Diff each next element (array) against the previous one and emit a diff event. public func diff() -> Signal, Error> { return diff(generateDiff: { c1, c2 in OrderedCollectionDiff(from: c1.extendedDiff(c2)) }) @@ -60,24 +57,23 @@ extension SignalProtocol where Element: Collection, Element.Element: Equatable, } extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionChangesetProtocol, Changeset.Collection.Index == Int { - /// Replace the underlying collection with the given collection. Setting `performDiff: true` will make the framework /// calculate the diff between the existing and new collection and emit an event with the calculated diff. /// - Complexity: O((N+M)*D) if `performDiff: true`, O(1) otherwise. public func replace(with newCollection: Changeset.Collection, performDiff: Bool, areEqual: @escaping (Changeset.Collection.Element, Changeset.Collection.Element) -> Bool) { replace(with: newCollection, performDiff: performDiff) { (old, new) -> OrderedCollectionDiff in - return OrderedCollectionDiff(from: old.extendedDiff(new, isEqual: areEqual)) + OrderedCollectionDiff(from: old.extendedDiff(new, isEqual: areEqual)) } } } -extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionChangesetProtocol, Changeset.Collection.Index == Int, Changeset.Collection.Element: Equatable { +extension MutableChangesetContainerProtocol where Changeset: OrderedCollectionChangesetProtocol, Changeset.Collection.Index == Int, Changeset.Collection.Element: Equatable { /// Replace the underlying collection with the given collection. Setting `performDiff: true` will make the framework /// calculate the diff between the existing and new collection and emit an event with the calculated diff. /// - Complexity: O((N+M)*D) if `performDiff: true`, O(1) otherwise. public func replace(with newCollection: Changeset.Collection, performDiff: Bool) { replace(with: newCollection, performDiff: performDiff) { (old, new) -> OrderedCollectionDiff in - return OrderedCollectionDiff(from: old.extendedDiff(new)) + OrderedCollectionDiff(from: old.extendedDiff(new)) } } } diff --git a/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable+Patch.swift b/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable+Patch.swift index 52f68f6b..c569ba4f 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable+Patch.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable+Patch.swift @@ -25,9 +25,7 @@ import Foundation extension OrderedCollectionDiff where Index: Strideable { - private struct Edit { - var deletionIndex: Index? var insertionIndex: Index? var element: Element? @@ -46,33 +44,32 @@ extension OrderedCollectionDiff where Index: Strideable { } public func generatePatch(to collection: C) -> [OrderedCollectionOperation] where C.Index == Index { - let inserts = self.inserts.map { Edit(deletionIndex: nil, insertionIndex: $0, element: collection[$0]) } let deletes = self.deletes.map { Edit(deletionIndex: $0, insertionIndex: nil, element: nil) } let moves = self.moves.map { Edit(deletionIndex: $0.from, insertionIndex: $0.to, element: nil) } var script = deletes + moves + inserts - for i in 0..= priorDeletionIndex { script[j].deletionIndex = deletionIndex.advanced(by: -1) } } } - for i in (0.. laterInsertionIndex { script[j].insertionIndex = insertionIndex.advanced(by: -1) } } } - for i in 0.. laterDeletionIndex { script[i].insertionIndex = insertionIndex.advanced(by: 1) } @@ -85,12 +82,12 @@ extension OrderedCollectionDiff where Index: Strideable { let patch = script.map { $0.asOperation } let updatesInFinalCollection: [Index] = self.updates.compactMap { - return AnyOrderedCollectionOperation.simulate(patch: patch.map { $0.asAnyOrderedCollectionOperation }, on: $0) + AnyOrderedCollectionOperation.simulate(patch: patch.map { $0.asAnyOrderedCollectionOperation }, on: $0) } let zipped = zip(self.updates, updatesInFinalCollection) let updates = zipped.map { (pair) -> OrderedCollectionOperation in - return .update(at: pair.0, newElement: collection[pair.1]) + .update(at: pair.0, newElement: collection[pair.1]) } return updates + patch diff --git a/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable.swift b/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable.swift index 0acbb5bf..a1b871ac 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionDiff+Strideable.swift @@ -25,7 +25,6 @@ import Foundation extension OrderedCollectionDiff where Index: Strideable { - /// Calculates diff from the given patch. /// - complexity: O(Nˆ2) where N is the number of patch operations. public init(from patch: [OrderedCollectionOperation]) { @@ -41,26 +40,26 @@ extension OrderedCollectionDiff where Index: Strideable { return } - for patchSoFar in (1...patch.count).map({ patch.prefix(upTo: $0) }) { + for patchSoFar in (1 ... patch.count).map({ patch.prefix(upTo: $0) }) { let patchToUndo = patchSoFar.dropLast() switch patchSoFar.last! { - case .insert(let atIndex): + case let .insert(atIndex): recordInsertion(at: atIndex) - case .delete(let atIndex): + case let .delete(atIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: atIndex) recordDeletion(at: atIndex, sourceIndex: sourceIndex) - case .update(let atIndex): + case let .update(atIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: atIndex) recordUpdate(at: atIndex, sourceIndex: sourceIndex) - case .move(let fromIndex, let toIndex): + case let .move(fromIndex, toIndex): let sourceIndex = AnyOrderedCollectionOperation.undo(patch: patchToUndo, on: fromIndex) recordMove(from: fromIndex, to: toIndex, sourceIndex: sourceIndex) } } } - + private mutating func recordInsertion(at insertionIndex: Index) { - forEachDestinationIndex { (index) in + forEachDestinationIndex { index in if insertionIndex <= index { index = index.advanced(by: 1) } @@ -69,9 +68,8 @@ extension OrderedCollectionDiff where Index: Strideable { } private mutating func recordDeletion(at deletionIndex: Index, sourceIndex: Index?) { - defer { - forEachDestinationIndex { (index) in + forEachDestinationIndex { index in if deletionIndex <= index { index = index.advanced(by: -1) } @@ -99,7 +97,6 @@ extension OrderedCollectionDiff where Index: Strideable { } private mutating func recordUpdate(at updateIndex: Index, sourceIndex: Index?) { - // If updating previously inserted index if inserts.contains(where: { $0 == updateIndex }) { return @@ -126,7 +123,7 @@ extension OrderedCollectionDiff where Index: Strideable { guard fromIndex != toIndex else { return } func adjustDestinationIndices() { - forEachDestinationIndex { (index) in + forEachDestinationIndex { index in if fromIndex <= index { index = index.advanced(by: -1) } @@ -163,10 +160,10 @@ extension OrderedCollectionDiff where Index: Strideable { } private mutating func forEachDestinationIndex(apply: (inout Index) -> Void) { - for i in 0..: OrderedCollectionDiffProtocol { - /// Indices of inserted elements in the final collection index space. public var inserts: [Index] @@ -47,10 +46,10 @@ public struct OrderedCollectionDiff: OrderedCollectionDiffProtocol { public var moves: [(from: Index, to: Index)] public init() { - self.inserts = [] - self.deletes = [] - self.updates = [] - self.moves = [] + inserts = [] + deletes = [] + updates = [] + moves = [] } public init(inserts: [Index], deletes: [Index], updates: [Index], moves: [(from: Index, to: Index)]) { @@ -76,7 +75,6 @@ public struct OrderedCollectionDiff: OrderedCollectionDiffProtocol { } extension OrderedCollectionDiff { - public init(inserts: [Index]) { self.init(inserts: inserts, deletes: [], updates: [], moves: []) } @@ -102,7 +100,6 @@ extension OrderedCollectionDiff { } extension OrderedCollectionDiffProtocol { - public func map(_ transform: (Index) -> T) -> OrderedCollectionDiff { let diff = asOrderedCollectionDiff return OrderedCollectionDiff( @@ -115,7 +112,6 @@ extension OrderedCollectionDiffProtocol { } extension OrderedCollectionDiff: Equatable where Index: Equatable { - public static func == (lhs: OrderedCollectionDiff, rhs: OrderedCollectionDiff) -> Bool { let movesEqual = lhs.moves.map { $0.from } == rhs.moves.map { $0.from } && lhs.moves.map { $0.to } == rhs.moves.map { $0.to } return lhs.inserts == rhs.inserts && lhs.deletes == rhs.deletes && lhs.updates == rhs.updates && movesEqual @@ -123,7 +119,6 @@ extension OrderedCollectionDiff: Equatable where Index: Equatable { } extension OrderedCollectionDiff: CustomDebugStringConvertible { - public var debugDescription: String { return "Inserts: \(inserts), Deletes: \(deletes), Updates: \(updates), Moves: \(moves)" } diff --git a/Sources/Bond/Observable Collections/OrderedCollectionOperation+IndexPath+Undo.swift b/Sources/Bond/Observable Collections/OrderedCollectionOperation+IndexPath+Undo.swift index 2a67fa65..17fbb464 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionOperation+IndexPath+Undo.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionOperation+IndexPath+Undo.swift @@ -25,10 +25,9 @@ import Foundation extension AnyOrderedCollectionOperation where Index == IndexPath { - func undoOperationOn(_ index: IndexPath) -> IndexPath? { switch self { - case .insert(let insertionIndex): + case let .insert(insertionIndex): if insertionIndex == index || insertionIndex.isAncestor(of: index) { return nil } else if index.isAffectedByDeletionOrInsertion(at: insertionIndex) { @@ -36,19 +35,19 @@ extension AnyOrderedCollectionOperation where Index == IndexPath { } else { return index } - case .delete(let deletionIndex): + case let .delete(deletionIndex): if index.isAffectedByDeletionOrInsertion(at: deletionIndex) { return index.shifted(by: 1, atLevelOf: deletionIndex) } else { return index } - case .update(let updateIndex): + case let .update(updateIndex): if updateIndex.isAncestor(of: index) { return nil } else { return index } - case .move(let from, let to): + case let .move(from, to): if to == index { return from } else if to.isAncestor(of: index) { @@ -68,13 +67,13 @@ extension AnyOrderedCollectionOperation where Index == IndexPath { func simulateOperationOn(_ index: IndexPath) -> IndexPath? { switch self { - case .insert(let insertionIndex): + case let .insert(insertionIndex): if index.isAffectedByDeletionOrInsertion(at: insertionIndex) { return index.shifted(by: 1, atLevelOf: insertionIndex) } else { return index } - case .delete(let deletionIndex): + case let .delete(deletionIndex): if deletionIndex == index || deletionIndex.isAncestor(of: index) { return nil } else if index.isAffectedByDeletionOrInsertion(at: deletionIndex) { @@ -82,13 +81,13 @@ extension AnyOrderedCollectionOperation where Index == IndexPath { } else { return index } - case .update(let updateIndex): + case let .update(updateIndex): if updateIndex.isAncestor(of: index) { return nil } else { return index } - case .move(let from, let to): + case let .move(from, to): if from == index { return to } else if from.isAncestor(of: index) { diff --git a/Sources/Bond/Observable Collections/OrderedCollectionOperation+Strideable+Undo.swift b/Sources/Bond/Observable Collections/OrderedCollectionOperation+Strideable+Undo.swift index a8003ea8..ed70b4cd 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionOperation+Strideable+Undo.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionOperation+Strideable+Undo.swift @@ -25,10 +25,9 @@ import Foundation extension AnyOrderedCollectionOperation where Index: Strideable { - func undoOperationOn(_ index: Index) -> Index? { switch self { - case .insert(let insertionIndex): + case let .insert(insertionIndex): if insertionIndex == index { return nil } else if insertionIndex < index { @@ -36,7 +35,7 @@ extension AnyOrderedCollectionOperation where Index: Strideable { } else { return index } - case .delete(let deletionIndex): + case let .delete(deletionIndex): if deletionIndex <= index { return index.advanced(by: 1) } else { @@ -44,7 +43,7 @@ extension AnyOrderedCollectionOperation where Index: Strideable { } case .update: return index - case .move(let from, let to): + case let .move(from, to): if to == index { return from } else { @@ -62,13 +61,13 @@ extension AnyOrderedCollectionOperation where Index: Strideable { func simulateOperationOn(_ index: Index) -> Index? { switch self { - case .insert(let insertionIndex): + case let .insert(insertionIndex): if insertionIndex <= index { return index.advanced(by: 1) } else { return index } - case .delete(let deletionIndex): + case let .delete(deletionIndex): if deletionIndex == index { return nil } else if deletionIndex < index { @@ -78,7 +77,7 @@ extension AnyOrderedCollectionOperation where Index: Strideable { } case .update: return index - case .move(let from, let to): + case let .move(from, to): if from == index { return to } else { diff --git a/Sources/Bond/Observable Collections/OrderedCollectionOperation.swift b/Sources/Bond/Observable Collections/OrderedCollectionOperation.swift index 2fa04fce..2aa657b3 100644 --- a/Sources/Bond/Observable Collections/OrderedCollectionOperation.swift +++ b/Sources/Bond/Observable Collections/OrderedCollectionOperation.swift @@ -16,7 +16,6 @@ public protocol OrderedCollectionOperationProtocol { /// A unit operation that can be applied to an ordered collection. public enum OrderedCollectionOperation: OrderedCollectionOperationProtocol { - case insert(Element, at: Index) case delete(at: Index) case update(at: Index, newElement: Element) @@ -29,7 +28,6 @@ public enum OrderedCollectionOperation: OrderedCollectionOperati /// Element type erased ordered collection operation. public enum AnyOrderedCollectionOperation { - case insert(at: Index) case delete(at: Index) case update(at: Index) @@ -37,75 +35,72 @@ public enum AnyOrderedCollectionOperation { } extension OrderedCollectionOperation { - public func mapElement(_ transform: (Element) -> U) -> OrderedCollectionOperation { switch self { - case .insert(let element, let at): + case let .insert(element, at): return .insert(transform(element), at: at) - case .delete(let at): + case let .delete(at): return .delete(at: at) - case .update(let at, let element): + case let .update(at, element): return .update(at: at, newElement: transform(element)) - case .move(let from, let to): + case let .move(from, to): return .move(from: from, to: to) } } public func mapIndex(_ transform: (Index) -> U) -> OrderedCollectionOperation { switch self { - case .insert(let element, let at): + case let .insert(element, at): return .insert(element, at: transform(at)) - case .delete(let at): + case let .delete(at): return .delete(at: transform(at)) - case .update(let at, let element): + case let .update(at, element): return .update(at: transform(at), newElement: element) - case .move(let from, let to): + case let .move(from, to): return .move(from: transform(from), to: transform(to)) } } public var asAnyOrderedCollectionOperation: AnyOrderedCollectionOperation { switch self { - case .insert(_, let at): + case let .insert(_, at): return .insert(at: at) - case .delete(let at): + case let .delete(at): return .delete(at: at) - case .update(let at, _): + case let .update(at, _): return .update(at: at) - case .move(let from, let to): + case let .move(from, to): return .move(from: from, to: to) } } } extension OrderedCollectionOperation: CustomDebugStringConvertible { - public var debugDescription: String { switch self { - case .insert(let element, let at): + case let .insert(element, at): return "I(\(element), at: \(at))" - case .delete(let at): + case let .delete(at): return "D(at: \(at))" - case .update(let at, let newElement): + case let .update(at, newElement): return "U(at: \(at), newElement: \(newElement))" - case .move(let from, let to): + case let .move(from, to): return "M(from: \(from), to: \(to))" } } } extension RangeReplaceableCollection where Index: Strideable { - public mutating func apply(_ operation: OrderedCollectionChangeset.Operation) { switch operation { - case .insert(let element, let at): + case let .insert(element, at): insert(element, at: at) - case .delete(let at): + case let .delete(at): _ = remove(at: at) - case .update(let at, let newElement): + case let .update(at, newElement): _ = remove(at: at) insert(newElement, at: at) - case .move(let from, let to): + case let .move(from, to): let element = remove(at: from) insert(element, at: to) } @@ -113,16 +108,15 @@ extension RangeReplaceableCollection where Index: Strideable { } extension RangeReplaceableTreeProtocol { - public mutating func apply(_ operation: TreeChangeset.Operation) { switch operation { - case .insert(let element, let at): + case let .insert(element, at): insert(element, at: at) - case .delete(let at): + case let .delete(at): _ = remove(at: at) - case .update(let at, let newElement): + case let .update(at, newElement): update(at: at, newNode: newElement) - case .move(let from, let to): + case let .move(from, to): let element = remove(at: from) insert(element, at: to) } @@ -130,7 +124,6 @@ extension RangeReplaceableTreeProtocol { } extension MutableChangesetContainerProtocol where Changeset.Collection: RangeReplaceableCollection, Changeset.Collection: MutableCollection, Changeset.Collection.Index: Strideable, Changeset.Operation == OrderedCollectionChangeset.Operation { - public func apply(_ operation: Changeset.Operation) { descriptiveUpdate { (collection) -> [Changeset.Operation] in collection.apply(operation) @@ -140,7 +133,6 @@ extension MutableChangesetContainerProtocol where Changeset.Collection: RangeRep } extension MutableChangesetContainerProtocol where Changeset.Collection: RangeReplaceableTreeProtocol, Changeset.Operation == TreeChangeset.Operation { - public func apply(_ operation: Changeset.Operation) { descriptiveUpdate { (collection) -> [Changeset.Operation] in collection.apply(operation) diff --git a/Sources/Bond/Observable Collections/Property+ChangesetContainerProtocol.swift b/Sources/Bond/Observable Collections/Property+ChangesetContainerProtocol.swift index 9c734081..a557a9a1 100644 --- a/Sources/Bond/Observable Collections/Property+ChangesetContainerProtocol.swift +++ b/Sources/Bond/Observable Collections/Property+ChangesetContainerProtocol.swift @@ -37,8 +37,8 @@ public typealias MutableObservableArray = Property = AnyProperty>> public typealias MutableObservableSet = Property>> -public typealias ObservableDictionary = AnyProperty>> -public typealias MutableObservableDictionary = Property>> +public typealias ObservableDictionary = AnyProperty> +public typealias MutableObservableDictionary = Property> public typealias ObservableTree = AnyProperty> public typealias MutableObservableTree = Property> @@ -47,18 +47,14 @@ public typealias ObservableArray2D = AnyProperty = Property>> extension AnyProperty: ChangesetContainerProtocol where Value: ChangesetProtocol { - public typealias Changeset = Value public var changeset: Value { - get { - return value - } + return value } } extension Property: ChangesetContainerProtocol, MutableChangesetContainerProtocol where Value: ChangesetProtocol { - public typealias Changeset = Value public var changeset: Value { @@ -87,28 +83,24 @@ extension Property: ChangesetContainerProtocol, MutableChangesetContainerProtoco } extension Property where Value: ChangesetProtocol { - public convenience init(_ collection: Value.Collection) { self.init(Value(collection: collection, patch: [])) } } extension Property where Value: ChangesetProtocol, Value.Collection: RangeReplaceableCollection { - public convenience init() { self.init(Value(collection: .init(), patch: [])) } } extension Property where Value: ChangesetProtocol, Value.Collection: Instantiatable { - public convenience init() { self.init(Value(collection: .init(), patch: [])) } } extension Property where Value: ChangesetProtocol, Value.Collection: Array2DProtocol { - /// Total number of items across all sections. public var numberOfItemsInAllSections: Int { return value.collection.children.map { $0.children.count }.reduce(0, +) @@ -116,7 +108,6 @@ extension Property where Value: ChangesetProtocol, Value.Collection: Array2DProt } extension AnyProperty { - // TODO: move to ReactiveKit public convenience init(_ value: Value) { self.init(property: Property(value)) @@ -124,21 +115,18 @@ extension AnyProperty { } extension AnyProperty where Value: ChangesetProtocol { - public convenience init(_ collection: Value.Collection) { self.init(Value(collection: collection, patch: [])) } } extension AnyProperty where Value: ChangesetProtocol, Value.Collection: RangeReplaceableCollection { - public convenience init() { self.init(Value(collection: .init(), patch: [])) } } extension AnyProperty where Value: ChangesetProtocol, Value.Collection: Instantiatable { - public convenience init() { self.init(Value(collection: .init(), patch: [])) } diff --git a/Sources/Bond/Observable Collections/Signal+ChangesetProtocol.swift b/Sources/Bond/Observable Collections/Signal+ChangesetProtocol.swift index 78b8fd6d..91a66c7a 100644 --- a/Sources/Bond/Observable Collections/Signal+ChangesetProtocol.swift +++ b/Sources/Bond/Observable Collections/Signal+ChangesetProtocol.swift @@ -26,7 +26,6 @@ import Foundation import ReactiveKit extension SignalProtocol where Element: ChangesetProtocol { - /// When working with a changeset that calculates patch lazily, /// you can use this method to calculate the patch in advance. /// Observer will then have patch available in O(1). @@ -49,14 +48,13 @@ extension SignalProtocol where Element: ChangesetProtocol { } extension SignalProtocol where Element: Collection, Element.Index: Strideable { - /// Generate the diff between previous and current collection using the provided diff generator function. public func diff(generateDiff: @escaping (Element, Element) -> OrderedCollectionChangeset.Diff) -> Signal, Error> { return Signal { observer in var collection: Element? return self.observe { event in switch event { - case .next(let element): + case let .next(element): let newCollection = element if let collection = collection { let diff = generateDiff(collection, newCollection) @@ -65,7 +63,7 @@ extension SignalProtocol where Element: Collection, Element.Index: Strideable { observer.receive(OrderedCollectionChangeset(collection: newCollection, patch: [])) } collection = newCollection - case .failed(let error): + case let .failed(error): observer.receive(completion: .failure(error)) case .completed: observer.receive(completion: .finished) @@ -76,14 +74,13 @@ extension SignalProtocol where Element: Collection, Element.Index: Strideable { } extension SignalProtocol where Element: TreeProtocol { - /// Generate the diff between previous and current tree using the provided diff generator function. public func diff(generateDiff: @escaping (Element, Element) -> TreeChangeset.Diff) -> Signal, Error> { return Signal { observer in var collection: Element? return self.observe { event in switch event { - case .next(let element): + case let .next(element): let newCollection = element if let collection = collection { let diff = generateDiff(collection, newCollection) @@ -92,7 +89,7 @@ extension SignalProtocol where Element: TreeProtocol { observer.receive(TreeChangeset(collection: newCollection, patch: [])) } collection = newCollection - case .failed(let error): + case let .failed(error): observer.receive(completion: .failure(error)) case .completed: observer.receive(completion: .finished) @@ -103,7 +100,6 @@ extension SignalProtocol where Element: TreeProtocol { } extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Element.Collection.Index: Hashable { - /// - complexity: Each event sorts the collection O(nlogn). public func sortedCollection(by areInIncreasingOrder: @escaping (Element.Collection.Element, Element.Collection.Element) -> Bool) -> Signal, Error> { var previousIndexMap: [Element.Collection.Index: Int] = [:] @@ -113,14 +109,14 @@ extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Elem let elementsWithIndices = Swift.zip(event.collection, indices) let sortedElementsWithIndices = elementsWithIndices.sorted(by: { (a, b) -> Bool in - return areInIncreasingOrder(a.0, b.0) + areInIncreasingOrder(a.0, b.0) }) let sortedElements = sortedElementsWithIndices.map { $0.0 } - let indexMap = sortedElementsWithIndices.map { $0.1 }.enumerated().reduce([Element.Collection.Index: Int](), { (indexMap, new) -> [Element.Collection.Index: Int] in - return indexMap.merging([new.element: new.offset], uniquingKeysWith: { $1 }) - }) + let indexMap = sortedElementsWithIndices.map { $0.1 }.enumerated().reduce([Element.Collection.Index: Int]()) { (indexMap, new) -> [Element.Collection.Index: Int] in + indexMap.merging([new.element: new.offset], uniquingKeysWith: { $1 }) + } let diff = event.diff.transformingIndices(fromIndexMap: previousIndexMap, toIndexMap: indexMap) previousIndexMap = indexMap @@ -134,7 +130,6 @@ extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Elem } extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Element.Collection.Index: Hashable, Element.Collection.Element: Comparable { - /// - complexity: Each event sorts collection O(nlogn). public func sortedCollection() -> Signal, Error> { return sortedCollection(by: <) @@ -142,7 +137,6 @@ extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Elem } extension SignalProtocol where Element: UnorderedCollectionChangesetProtocol, Element.Collection.Index: Hashable { - /// - complexity: Each event sorts the collection O(nlogn). public func sortedCollection(by areInIncreasingOrder: @escaping (Element.Collection.Element, Element.Collection.Element) -> Bool) -> Signal, Error> { var previousIndexMap: [Element.Collection.Index: Int] = [:] @@ -152,14 +146,14 @@ extension SignalProtocol where Element: UnorderedCollectionChangesetProtocol, El let elementsWithIndices = Swift.zip(event.collection, indices) let sortedElementsWithIndices = elementsWithIndices.sorted(by: { (a, b) -> Bool in - return areInIncreasingOrder(a.0, b.0) + areInIncreasingOrder(a.0, b.0) }) let sortedElements = sortedElementsWithIndices.map { $0.0 } - let indexMap = sortedElementsWithIndices.map { $0.1 }.enumerated().reduce([Element.Collection.Index: Int](), { (indexMap, new) -> [Element.Collection.Index: Int] in - return indexMap.merging([new.element: new.offset], uniquingKeysWith: { $1 }) - }) + let indexMap = sortedElementsWithIndices.map { $0.1 }.enumerated().reduce([Element.Collection.Index: Int]()) { (indexMap, new) -> [Element.Collection.Index: Int] in + indexMap.merging([new.element: new.offset], uniquingKeysWith: { $1 }) + } let diff = event.diff.transformingIndices(fromIndexMap: previousIndexMap, toIndexMap: indexMap) previousIndexMap = indexMap @@ -173,7 +167,6 @@ extension SignalProtocol where Element: UnorderedCollectionChangesetProtocol, El } extension SignalProtocol where Element: UnorderedCollectionChangesetProtocol, Element.Collection.Index: Hashable, Element.Collection.Element: Comparable { - /// - complexity: Each event sorts collection O(nlogn). public func sortedCollection() -> Signal, Error> { return sortedCollection(by: <) @@ -181,11 +174,10 @@ extension SignalProtocol where Element: UnorderedCollectionChangesetProtocol, El } extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Element.Collection.Index == Int { - /// - complexity: Each event transforms collection O(n). Use `lazyMapCollection` if you need on-demand mapping. public func mapCollection(_ transform: @escaping (Element.Collection.Element) -> U) -> Signal, Error> { return map { (event: Element) -> OrderedCollectionChangeset<[U]> in - return OrderedCollectionChangeset( + OrderedCollectionChangeset( collection: event.collection.map(transform), diff: event.diff ) @@ -195,7 +187,7 @@ extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Elem /// - complexity: O(1). public func lazyMapCollection(_ transform: @escaping (Element.Collection.Element) -> U) -> Signal>, Error> { return map { (event: Element) -> OrderedCollectionChangeset> in - return OrderedCollectionChangeset( + OrderedCollectionChangeset( collection: event.collection.lazy.map(transform), diff: event.diff ) @@ -234,7 +226,6 @@ extension SignalProtocol where Element: OrderedCollectionChangesetProtocol, Elem } extension OrderedCollectionDiff where Index: Hashable { - public func transformingIndices(fromIndexMap: [Index: NewIndex], toIndexMap: [Index: NewIndex]) -> OrderedCollectionDiff { var inserts = self.inserts.compactMap { toIndexMap[$0] } var deletes = self.deletes.compactMap { fromIndexMap[$0] } @@ -262,7 +253,6 @@ extension OrderedCollectionDiff where Index: Hashable { } extension UnorderedCollectionDiff where Index: Hashable { - public func transformingIndices(fromIndexMap: [Index: NewIndex], toIndexMap: [Index: NewIndex]) -> UnorderedCollectionDiff { var inserts = self.inserts.compactMap { toIndexMap[$0] } var deletes = self.deletes.compactMap { fromIndexMap[$0] } diff --git a/Sources/Bond/Observable Collections/TreeChangeset+Array2D.swift b/Sources/Bond/Observable Collections/TreeChangeset+Array2D.swift index b766a5c1..5827fc70 100644 --- a/Sources/Bond/Observable Collections/TreeChangeset+Array2D.swift +++ b/Sources/Bond/Observable Collections/TreeChangeset+Array2D.swift @@ -25,7 +25,6 @@ import Foundation extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtocol, Changeset.Collection: Array2DProtocol { - public typealias SectionMetadata = Collection.SectionMetadata public typealias Item = Collection.Item public typealias Section = Array2D.Section @@ -70,17 +69,17 @@ extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtoc } /// Insert section at `index` with `items`. - public func insert(section: Section, at index: Int) { + public func insert(section: Section, at index: Int) { insert(.section(section), at: [index]) } /// Insert section at `index` with `items`. - public func insert(section metadata: SectionMetadata, at index: Int) { + public func insert(section metadata: SectionMetadata, at index: Int) { insert(.section(Section(metadata: metadata, items: [])), at: [index]) } /// Insert `item` at `indexPath`. - public func insert(item: Item, at indexPath: IndexPath) { + public func insert(item: Item, at indexPath: IndexPath) { insert(.item(item), at: indexPath) } @@ -140,7 +139,6 @@ extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtoc } extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtocol, Changeset.Collection: Array2DProtocol, Changeset.Collection.Item: Equatable { - /// Replace items of a section at the given index with new items. Setting `performDiff: true` will make the framework /// calculate the diff between the existing and new items and emit an event with the calculated diff. public func replaceItems(ofSectionAt sectionIndex: Int, with newItems: [Item], performDiff: Bool) { diff --git a/Sources/Bond/Observable Collections/TreeChangeset.swift b/Sources/Bond/Observable Collections/TreeChangeset.swift index 58b36497..cd3fe2b5 100644 --- a/Sources/Bond/Observable Collections/TreeChangeset.swift +++ b/Sources/Bond/Observable Collections/TreeChangeset.swift @@ -28,12 +28,10 @@ public protocol TreeChangesetProtocol: ChangesetProtocol where Collection: TreeProtocol, Operation == OrderedCollectionOperation, Diff == OrderedCollectionDiff { - var asTreeChangeset: TreeChangeset { get } } public final class TreeChangeset: Changeset, OrderedCollectionDiff>, TreeChangesetProtocol { - public override func calculateDiff(from patch: [OrderedCollectionOperation]) -> Diff { return Diff(from: patch) } @@ -48,7 +46,6 @@ public final class TreeChangeset: Changeset 0 else { return } descriptiveUpdate { (collection) -> [Operation] in collection.insert(contentsOf: newNodes, at: indexPath) - let indices = newNodes.indices.map { indexPath.advanced(by: $0, atLevel: indexPath.count-1) } + let indices = newNodes.indices.map { indexPath.advanced(by: $0, atLevel: indexPath.count - 1) } return zip(newNodes, indices).map { Operation.insert($0, at: $1) } } } @@ -102,7 +99,7 @@ extension MutableChangesetContainerProtocol where Changeset: TreeChangesetProtoc descriptiveUpdate { (collection) -> [Operation] in collection.move(from: fromIndices, to: toIndex) let movesDiff = fromIndices.enumerated().map { - (from: $0.element, to: toIndex.advanced(by: $0.offset, atLevel: toIndex.count-1)) + (from: $0.element, to: toIndex.advanced(by: $0.offset, atLevel: toIndex.count - 1)) } return OrderedCollectionDiff(inserts: [], deletes: [], updates: [], moves: movesDiff).generatePatch(to: collection) } diff --git a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Dictionary.swift b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Dictionary.swift index 2e4fbb11..146ec6d2 100644 --- a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Dictionary.swift +++ b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Dictionary.swift @@ -11,12 +11,11 @@ import Foundation public protocol _DictionaryProtocol { associatedtype Key: Hashable associatedtype Value - var _asDictionary: Dictionary { get set } + var _asDictionary: [Key: Value] { get set } } extension Dictionary: _DictionaryProtocol { - - public var _asDictionary: Dictionary { + public var _asDictionary: [Key: Value] { get { return self } @@ -30,7 +29,6 @@ extension MutableChangesetContainerProtocol where Changeset: UnorderedCollectionChangesetProtocol, Changeset.Collection: _DictionaryProtocol, Changeset.Operation == UnorderedCollectionOperation.Element, Dictionary.Index> { - /// Update, insert or remove value from the dictionary. public subscript(_ key: Changeset.Collection.Key) -> Changeset.Collection.Value? { get { diff --git a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Set.swift b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Set.swift index e380557c..d4f90f64 100644 --- a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Set.swift +++ b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset+Set.swift @@ -14,7 +14,6 @@ public protocol _SetProtocol { } extension Set: _SetProtocol { - public var _asSet: Set { get { return self @@ -29,7 +28,6 @@ extension MutableChangesetContainerProtocol where Changeset: UnorderedCollectionChangesetProtocol, Changeset.Collection: _SetProtocol, Changeset.Operation == UnorderedCollectionOperation.Element, Set.Index> { - /// Insert item in the set. public func insert(_ member: Changeset.Collection.Element) { descriptiveUpdate { (collection) -> [Operation] in diff --git a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset.swift b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset.swift index c85fea53..ad6ab287 100644 --- a/Sources/Bond/Observable Collections/UnorderedCollectionChangeset.swift +++ b/Sources/Bond/Observable Collections/UnorderedCollectionChangeset.swift @@ -28,12 +28,10 @@ public protocol UnorderedCollectionChangesetProtocol: ChangesetProtocol where Collection: Swift.Collection, Operation == UnorderedCollectionOperation, Diff == UnorderedCollectionDiff { - var asUnorderedCollectionChangeset: UnorderedCollectionChangeset { get } } public final class UnorderedCollectionChangeset: Changeset, UnorderedCollectionDiff>, UnorderedCollectionChangesetProtocol { - public override func calculateDiff(from patch: [UnorderedCollectionOperation]) -> UnorderedCollectionDiff { return Diff(from: patch) } @@ -48,7 +46,6 @@ public final class UnorderedCollectionChangeset: C } extension MutableChangesetContainerProtocol where Changeset: UnorderedCollectionChangesetProtocol, Changeset.Collection: MutableCollection { - /// Access or update the element at `index`. public subscript(index: Collection.Index) -> Collection.Element { get { @@ -64,7 +61,6 @@ extension MutableChangesetContainerProtocol where Changeset: UnorderedCollection } extension MutableChangesetContainerProtocol where Changeset: UnorderedCollectionChangesetProtocol, Changeset.Collection: RangeReplaceableCollection { - /// Append `newElement` to the collection. public func append(_ newElement: Collection.Element) { descriptiveUpdate { (collection) -> [Operation] in @@ -85,7 +81,7 @@ extension MutableChangesetContainerProtocol where Changeset: UnorderedCollection public func insert(contentsOf newElements: [Collection.Element], at index: Collection.Index) { descriptiveUpdate { (collection) -> [Operation] in collection.insert(contentsOf: newElements, at: index) - let indices = (0..: UnorderedCollectionDiffProtocol { - /// Indices of inserted elements in the final collection index space. public var inserts: [Index] @@ -25,9 +24,9 @@ public struct UnorderedCollectionDiff: UnorderedCollectionDiffProtocol { public var updates: [Index] public init() { - self.inserts = [] - self.deletes = [] - self.updates = [] + inserts = [] + deletes = [] + updates = [] } public init(inserts: [Index], deletes: [Index], updates: [Index]) { @@ -50,7 +49,6 @@ public struct UnorderedCollectionDiff: UnorderedCollectionDiffProtocol { } extension UnorderedCollectionDiff { - /// Calculates diff from the given patch. /// - complexity: O(Nˆ2) where N is the number of patch operations. public init(from patch: [UnorderedCollectionOperation]) { @@ -61,14 +59,13 @@ extension UnorderedCollectionDiff { /// - complexity: O(Nˆ2) where N is the number of patch operations. public init(from patch: [AnyUnorderedCollectionOperation]) { self.init() - inserts = patch.compactMap { if case .insert(let index) = $0 { return index } else { return nil } } - deletes = patch.compactMap { if case .delete(let index) = $0 { return index } else { return nil } } - updates = patch.compactMap { if case .update(let index) = $0 { return index } else { return nil } } + inserts = patch.compactMap { if case let .insert(index) = $0 { return index } else { return nil } } + deletes = patch.compactMap { if case let .delete(index) = $0 { return index } else { return nil } } + updates = patch.compactMap { if case let .update(index) = $0 { return index } else { return nil } } } } extension UnorderedCollectionDiffProtocol { - public func map(_ transform: (Index) -> T) -> UnorderedCollectionDiff { let diff = asUnorderedCollectionDiff return UnorderedCollectionDiff( @@ -88,14 +85,12 @@ extension UnorderedCollectionDiffProtocol { } extension UnorderedCollectionDiff: Equatable where Index: Equatable { - public static func == (lhs: UnorderedCollectionDiff, rhs: UnorderedCollectionDiff) -> Bool { return lhs.inserts == rhs.inserts && lhs.deletes == rhs.deletes && lhs.updates == rhs.updates } } extension UnorderedCollectionDiff: CustomDebugStringConvertible { - public var debugDescription: String { return "Inserts: \(inserts), Deletes: \(deletes), Updates: \(updates)" } diff --git a/Sources/Bond/Observable Collections/UnorderedCollectionOperation.swift b/Sources/Bond/Observable Collections/UnorderedCollectionOperation.swift index 7d90e387..544b6252 100644 --- a/Sources/Bond/Observable Collections/UnorderedCollectionOperation.swift +++ b/Sources/Bond/Observable Collections/UnorderedCollectionOperation.swift @@ -10,7 +10,6 @@ import Foundation /// A unit operation that can be applied to an unordered collection. public enum UnorderedCollectionOperation { - case insert(Element, at: Index) case delete(at: Index) case update(at: Index, newElement: Element) @@ -18,35 +17,32 @@ public enum UnorderedCollectionOperation { /// Element type erased unordered collection operation. public enum AnyUnorderedCollectionOperation { - case insert(at: Index) case delete(at: Index) case update(at: Index) } extension UnorderedCollectionOperation { - public var asAnyUnorderedCollectionOperation: AnyUnorderedCollectionOperation { switch self { - case .insert(_, let at): + case let .insert(_, at): return .insert(at: at) - case .delete(let at): + case let .delete(at): return .delete(at: at) - case .update(let at, _): + case let .update(at, _): return .update(at: at) } } } extension UnorderedCollectionOperation: CustomDebugStringConvertible { - public var debugDescription: String { switch self { - case .insert(let element, let at): + case let .insert(element, at): return "I(\(element), at: \(at))" - case .delete(let at): + case let .delete(at): return "D(at: \(at))" - case .update(let at, let newElement): + case let .update(at, newElement): return "U(at: \(at), newElement: \(newElement))" } } diff --git a/Sources/Bond/Property+BidirectionalMap.swift b/Sources/Bond/Property+BidirectionalMap.swift index c0c5860f..71734b8b 100644 --- a/Sources/Bond/Property+BidirectionalMap.swift +++ b/Sources/Bond/Property+BidirectionalMap.swift @@ -22,11 +22,10 @@ // THE SOFTWARE. // -import ReactiveKit import Foundation +import ReactiveKit extension Property { - /// Transform the `getter` and `setter` by applying a `transform` on them. public func bidirectionalMap(to getTransform: @escaping (Element) -> U, from setTransform: @escaping (U) -> Element) -> DynamicSubject { @@ -35,9 +34,9 @@ extension Property { signal: eraseType(), context: .immediate, get: { (property) -> U in - return getTransform(property.value) + getTransform(property.value) }, - set: { (propery, value) in + set: { propery, value in propery.value = setTransform(value) } ) diff --git a/Sources/Bond/ProtocolProxy.swift b/Sources/Bond/ProtocolProxy.swift index 7371c969..20125b84 100644 --- a/Sources/Bond/ProtocolProxy.swift +++ b/Sources/Bond/ProtocolProxy.swift @@ -27,17 +27,16 @@ import ObjectiveC import ReactiveKit #if !BUILDING_WITH_XCODE -import BNDProtocolProxyBase + import BNDProtocolProxyBase #endif public class ProtocolProxy: BNDProtocolProxyBase { - private var invokers: [Selector: (BNDInvocation) -> Void] = [:] private var handlers: [Selector: Any] = [:] private let propertyController: ProtocolProxyPropertyController - public init(object: NSObject, `protocol`: Protocol, setter: Selector) { - self.propertyController = SelectorProtocolProxyPropertyController(object: object, selector: setter) + public init(object: NSObject, protocol: Protocol, setter: Selector) { + propertyController = SelectorProtocolProxyPropertyController(object: object, selector: setter) super.init(with: `protocol`) } @@ -138,7 +137,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { } } - private func _signal(for selector: Selector, registerInvoker: @escaping (PassthroughSubject) -> Disposable) -> SafeSignal{ + private func _signal(for selector: Selector, registerInvoker: @escaping (PassthroughSubject) -> Disposable) -> SafeSignal { if let signal = handlers[selector] { return signal as! SafeSignal } else { @@ -166,7 +165,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker0(for: selector) { () -> R in - return dispatch(subject) + dispatch(subject) } } } @@ -183,7 +182,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker1(for: selector) { (a: A) -> R in - return dispatch(subject, a) + dispatch(subject, a) } } } @@ -200,7 +199,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker2(for: selector) { (a: A, b: B) -> R in - return dispatch(subject, a, b) + dispatch(subject, a, b) } } } @@ -217,7 +216,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker3(for: selector) { (a: A, b: B, c: C) -> R in - return dispatch(subject, a, b, c) + dispatch(subject, a, b, c) } } } @@ -234,7 +233,7 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker4(for: selector) { (a: A, b: B, c: C, d: D) -> R in - return dispatch(subject, a, b, c, d) + dispatch(subject, a, b, c, d) } } } @@ -251,13 +250,13 @@ public class ProtocolProxy: BNDProtocolProxyBase { return _signal(for: selector) { [weak self] subject in guard let me = self else { return NonDisposable.instance } return me.registerInvoker5(for: selector) { (a: A, b: B, c: C, d: D, e: E) -> R in - return dispatch(subject, a, b, c, d, e) + dispatch(subject, a, b, c, d, e) } } } - public override func conforms(to aProtocol: Protocol) -> Bool { - if protocol_isEqual(`protocol`, self.`protocol`) { + public override func conforms(to _: Protocol) -> Bool { + if protocol_isEqual(`protocol`, `protocol`) { return true } else { return super.conforms(to: `protocol`) @@ -288,13 +287,12 @@ public class ProtocolProxy: BNDProtocolProxyBase { } extension ProtocolProxy { - /// Provides a feed for specified protocol method. /// /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! public func feed(property: Property, to selector: Selector, map: @escaping (S, A) -> R) -> Disposable { return signal(for: selector) { (_: PassthroughSubject, a1: A) -> R in - return map(property.value, a1) + map(property.value, a1) }.observe { _ in } } @@ -303,7 +301,7 @@ extension ProtocolProxy { /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! public func feed(property: Property, to selector: Selector, map: @escaping (S, A, B) -> R) -> Disposable { return signal(for: selector) { (_: PassthroughSubject, a1: A, a2: B) -> R in - return map(property.value, a1, a2) + map(property.value, a1, a2) }.observe { _ in } } @@ -312,7 +310,7 @@ extension ProtocolProxy { /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! public func feed(property: Property, to selector: Selector, map: @escaping (S, A, B, C) -> R) -> Disposable { return signal(for: selector) { (_: PassthroughSubject, a1: A, a2: B, a3: C) -> R in - return map(property.value, a1, a2, a3) + map(property.value, a1, a2, a3) }.observe { _ in } } @@ -321,7 +319,7 @@ extension ProtocolProxy { /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! public func feed(property: Property, to selector: Selector, map: @escaping (S, A, B, C, D) -> R) -> Disposable { return signal(for: selector) { (_: PassthroughSubject, a1: A, a2: B, a3: C, a4: D) -> R in - return map(property.value, a1, a2, a3, a4) + map(property.value, a1, a2, a3, a4) }.observe { _ in } } @@ -330,13 +328,12 @@ extension ProtocolProxy { /// - important: This is ObjC API so you have to use ObjC types like NSString instead of String! public func feed(property: Property, to selector: Selector, map: @escaping (S, A, B, C, D, E) -> R) -> Disposable { return signal(for: selector) { (_: PassthroughSubject, a1: A, a2: B, a3: C, a4: D, a5: E) -> R in - return map(property.value, a1, a2, a3, a4, a5) + map(property.value, a1, a2, a3, a4, a5) }.observe { _ in } } } extension NSObject { - private struct AssociatedKeys { static var ProtocolProxies = "ProtocolProxies" } @@ -358,13 +355,12 @@ extension NSObject { } extension ReactiveExtensions where Base: NSObject { - /// Creates a proxy object that conforms to a given protocol and injects itself as a delegate for a given selector. /// The object can then be used to intercept various delegate callbacks as signals. /// /// - warning: If the protocol has any required methods, you have to handle them by providing a signal, a feed or implement /// them in a class whose instance you set to `forwardTo` property of the returned proxy object. - public func protocolProxy(for `protocol`: Protocol, selector: Selector) -> ProtocolProxy { + public func protocolProxy(for protocol: Protocol, selector: Selector) -> ProtocolProxy { let controller = SelectorProtocolProxyPropertyController(object: base, selector: selector) return protocolProxy(protocol: `protocol`, controller: controller) } @@ -377,11 +373,11 @@ extension ReactiveExtensions where Base: NSObject { /// /// - warning: If the protocol has any required methods, you have to handle them by providing a signal, a feed, /// implement them in the original delegate or implement them in a class whose instance you set to `forwardTo` property of the returned proxy object. - public func protocolProxy

(for `protocol`: Protocol, keyPath: ReferenceWritableKeyPath) -> ProtocolProxy { + public func protocolProxy

(for protocol: Protocol, keyPath: ReferenceWritableKeyPath) -> ProtocolProxy { let controller = KeyPathProtocolProxyPropertyController(object: base, keyPath: keyPath) return protocolProxy(protocol: `protocol`, controller: controller) } - + /// Creates a proxy object that conforms to a given protocol and injects itself as a delegate for a given key path. /// The object can then be used to intercept various delegate callbacks as signals. /// @@ -390,7 +386,7 @@ extension ReactiveExtensions where Base: NSObject { /// /// - warning: If the protocol has any required methods, you have to handle them by providing a signal, a feed, /// implement them in the original delegate or implement them in a class whose instance you set to `forwardTo` property of the returned proxy object. - public func protocolProxy

(for `protocol`: Protocol, keyPath: ReferenceWritableKeyPath) -> ProtocolProxy { + public func protocolProxy

(for protocol: Protocol, keyPath: ReferenceWritableKeyPath) -> ProtocolProxy { let controller = OptionalKeyPathProtocolProxyPropertyController(object: base, keyPath: keyPath) return protocolProxy(protocol: `protocol`, controller: controller) } diff --git a/Sources/Bond/ProtocolProxyController.swift b/Sources/Bond/ProtocolProxyController.swift index 5f8c0e0b..c0872802 100644 --- a/Sources/Bond/ProtocolProxyController.swift +++ b/Sources/Bond/ProtocolProxyController.swift @@ -18,12 +18,11 @@ public class ProtocolProxyPropertyController: NSObject { } public final class KeyPathProtocolProxyPropertyController: ProtocolProxyPropertyController { - private weak var _object: Root? private let keyPath: ReferenceWritableKeyPath public init(object: Root, keyPath: ReferenceWritableKeyPath) { - self._object = object + _object = object self.keyPath = keyPath super.init(object: object) } @@ -52,12 +51,11 @@ public final class KeyPathProtocolProxyPropertyController: Pr } public final class OptionalKeyPathProtocolProxyPropertyController: ProtocolProxyPropertyController { - private weak var _object: Root? private let keyPath: ReferenceWritableKeyPath public init(object: Root, keyPath: ReferenceWritableKeyPath) { - self._object = object + _object = object self.keyPath = keyPath super.init(object: object) } @@ -84,7 +82,6 @@ public final class OptionalKeyPathProtocolProxyPropertyController { return bond { $0.opacity = $1 } } diff --git a/Sources/Bond/Shared/NSLayoutConstraint.swift b/Sources/Bond/Shared/NSLayoutConstraint.swift index 314a0d96..866c1599 100644 --- a/Sources/Bond/Shared/NSLayoutConstraint.swift +++ b/Sources/Bond/Shared/NSLayoutConstraint.swift @@ -35,7 +35,6 @@ extension NSLayoutConstraint: BindingExecutionContextProvider { } extension ReactiveExtensions where Base: NSLayoutConstraint { - public var isActive: Bond { return bond { $0.isActive = $1 } } diff --git a/Sources/Bond/Shared/NSObject+KVO.swift b/Sources/Bond/Shared/NSObject+KVO.swift index e44764c9..f223cf66 100644 --- a/Sources/Bond/Shared/NSObject+KVO.swift +++ b/Sources/Bond/Shared/NSObject+KVO.swift @@ -26,17 +26,14 @@ import Foundation import ReactiveKit extension NSObject { - /// KVO reactive extensions error. public enum KVOError: Error { - /// Sent when the type is not convertible to the expected type. case notConvertible(String) } } extension ReactiveExtensions where Base: NSObject { - /// Creates a signal that represents values of the given KVO-compatible key path. /// /// class User: NSObject { @@ -61,7 +58,7 @@ extension ReactiveExtensions where Base: NSObject { let options: NSKeyValueObservingOptions = startWithCurrentValue ? [.initial] : [] - let subscription = base.observe(keyPath, options: options) { base, change in + let subscription = base.observe(keyPath, options: options) { base, _ in observer.receive(base[keyPath: keyPath]) } @@ -71,15 +68,14 @@ extension ReactiveExtensions where Base: NSObject { } return DeinitDisposable(disposable: MainBlockDisposable { - subscription.invalidate() - disposable.dispose() + subscription.invalidate() + disposable.dispose() }) } } } extension ReactiveExtensions where Base: NSObject { - /// Creates a `DynamicSubject` representing the given KVO path of the given type. /// /// If the key path emits a value of type other than `ofType`, program execution will stop with `fatalError`. @@ -90,7 +86,7 @@ extension ReactiveExtensions where Base: NSObject { /// - keyPath: Key path of the property to wrap. /// - ofType: Type of the property to wrap, e.g. `Int.self`. /// - context: Execution context in which to update the property. Use `.immediateOnMain` to update the object from main queue. - public func keyPath(_ keyPath: String, ofType: T.Type, context: ExecutionContext) -> DynamicSubject { + public func keyPath(_ keyPath: String, ofType _: T.Type, context: ExecutionContext) -> DynamicSubject { return DynamicSubject( target: base, signal: RKKeyValueSignal(keyPath: keyPath, for: base).toSignal(), @@ -120,7 +116,7 @@ extension ReactiveExtensions where Base: NSObject { /// - keyPath: Key path of the property to wrap. /// - ofType: Type of the property to wrap, e.g. `Optional.self`. /// - context: Execution context in which to update the property. Use `.immediateOnMain` to update the object from main queue. - public func keyPath(_ keyPath: String, ofType: T.Type, context: ExecutionContext) -> DynamicSubject where T: OptionalProtocol { + public func keyPath(_ keyPath: String, ofType _: T.Type, context: ExecutionContext) -> DynamicSubject where T: OptionalProtocol { return DynamicSubject( target: base, signal: RKKeyValueSignal(keyPath: keyPath, for: base).toSignal(), @@ -156,7 +152,7 @@ extension ReactiveExtensions where Base: NSObject { /// - keyPath: Key path of the property to wrap. /// - ofExpectedType: Type of the property to wrap, e.g. `Int.self`. /// - context: Execution context in which to update the property. Use `.immediateOnMain` to update the object from main queue. - public func keyPath(_ keyPath: String, ofExpectedType: T.Type, context: ExecutionContext) -> FailableDynamicSubject { + public func keyPath(_ keyPath: String, ofExpectedType _: T.Type, context: ExecutionContext) -> FailableDynamicSubject { return FailableDynamicSubject( target: base, signal: RKKeyValueSignal(keyPath: keyPath, for: base).castError(), @@ -186,7 +182,7 @@ extension ReactiveExtensions where Base: NSObject { /// - keyPath: Key path of the property to wrap. /// - ofExpectedType: Type of the property to wrap, e.g. `Optional.self`. /// - context: Execution context in which to update the property. Use `.immediateOnMain` to update the object from main queue. - public func keyPath(_ keyPath: String, ofExpectedType: T.Type, context: ExecutionContext) -> FailableDynamicSubject where T: OptionalProtocol { + public func keyPath(_ keyPath: String, ofExpectedType _: T.Type, context: ExecutionContext) -> FailableDynamicSubject where T: OptionalProtocol { return FailableDynamicSubject( target: base, signal: RKKeyValueSignal(keyPath: keyPath, for: base).castError(), @@ -214,7 +210,6 @@ extension ReactiveExtensions where Base: NSObject { } extension ReactiveExtensions where Base: NSObject, Base: BindingExecutionContextProvider { - /// Creates a `DynamicSubject` representing the given KVO path of the given type. /// /// user.keyPath("name", ofType: String.self) @@ -267,7 +262,7 @@ extension ReactiveExtensions where Base: NSObject, Base: BindingExecutionContext // MARK: - Implementation private class RKKeyValueSignal: NSObject, SignalProtocol { - private weak var object: NSObject? = nil + private weak var object: NSObject? private var context = 0 private var keyPath: String private let subject: Subject @@ -278,12 +273,12 @@ private class RKKeyValueSignal: NSObject, SignalProtocol { fileprivate init(keyPath: String, for object: NSObject) { self.keyPath = keyPath - self.subject = PassthroughSubject() + subject = PassthroughSubject() self.object = object super.init() lock.lock() - deallocationDisposable.otherDisposable = object._willDeallocate.reduce(nil, {$1}).observeNext { object in + deallocationDisposable.otherDisposable = object._willDeallocate.reduce(nil) { $1 }.observeNext { object in if self.observing { object?.unbox.removeObserver(self, forKeyPath: self.keyPath, context: &self.context) } @@ -296,7 +291,7 @@ private class RKKeyValueSignal: NSObject, SignalProtocol { subject.send(completion: .finished) } - fileprivate override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + fileprivate override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { if context == &self.context { if let _ = change?[NSKeyValueChangeKey.newKey] { subject.send(()) @@ -309,18 +304,18 @@ private class RKKeyValueSignal: NSObject, SignalProtocol { private func increaseNumberOfObservers() { lock.lock(); defer { lock.unlock() } numberOfObservers += 1 - if let object = object, numberOfObservers == 1 && !observing { + if let object = object, numberOfObservers == 1, !observing { observing = true - object.addObserver(self, forKeyPath: keyPath, options: [.new], context: &self.context) + object.addObserver(self, forKeyPath: keyPath, options: [.new], context: &context) } } private func decreaseNumberOfObservers() { lock.lock(); defer { lock.unlock() } numberOfObservers -= 1 - if let object = object, numberOfObservers == 0 && observing { + if let object = object, numberOfObservers == 0, observing { observing = false - object.removeObserver(self, forKeyPath: self.keyPath, context: &self.context) + object.removeObserver(self, forKeyPath: keyPath, context: &context) } } diff --git a/Sources/Bond/Shared/NSObject.swift b/Sources/Bond/Shared/NSObject.swift index 7ab1f0c0..9ecedd2f 100644 --- a/Sources/Bond/Shared/NSObject.swift +++ b/Sources/Bond/Shared/NSObject.swift @@ -26,12 +26,11 @@ import Foundation import ReactiveKit extension NSObject { - /// Bind `signal` to `bindable` and dispose in `bnd_bag` of receiver. public func bind(_ signal: O, to bindable: B) where O.Element == B.Element, O.Error == Never { signal.bind(to: bindable).dispose(in: bag) } - + /// Bind `signal` to `bindable` and dispose in `bnd_bag` of receiver. public func bind(_ signal: O, to bindable: B) where B.Element: OptionalProtocol, O.Element == B.Element.Wrapped, O.Error == Never { signal.bind(to: bindable).dispose(in: bag) @@ -44,13 +43,12 @@ internal struct UnownedUnsafe { } extension NSObject { - private struct StaticVariables { static var willDeallocateSubject = "WillDeallocateSubject" static var swizzledTypes: Set = [] static var lock = NSRecursiveLock(name: "com.reactivekit.bond.nsobject") } - + internal var _willDeallocate: Signal, Never> { StaticVariables.lock.lock(); defer { StaticVariables.lock.unlock() } if let subject = objc_getAssociatedObject(self, &StaticVariables.willDeallocateSubject) as? ReplayOneSubject, Never> { @@ -59,7 +57,7 @@ extension NSObject { let subject = ReplayOneSubject, Never>() subject.send(UnownedUnsafe(self)) let typeName = String(describing: type(of: self)) - + if !StaticVariables.swizzledTypes.contains(typeName) { StaticVariables.swizzledTypes.insert(typeName) type(of: self)._swizzleDeinit { me in @@ -68,16 +66,16 @@ extension NSObject { } } } - + objc_setAssociatedObject(self, &StaticVariables.willDeallocateSubject, subject, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) return subject.toSignal() } } - + private class func _swizzleDeinit(onDeinit: @escaping (NSObject) -> Void) { let selector = sel_registerName("dealloc") - var originalImplementation: IMP? = nil - + var originalImplementation: IMP? + let swizzledImplementationBlock: @convention(block) (UnsafeRawPointer) -> Void = { me in onDeinit(unsafeBitCast(me, to: NSObject.self)) let superImplementation = class_getMethodImplementation(class_getSuperclass(self), selector) @@ -86,9 +84,9 @@ extension NSObject { unsafeBitCast(imp, to: _IMP.self)(me, selector) } } - + let swizzledImplementation = imp_implementationWithBlock(swizzledImplementationBlock) - + if !class_addMethod(self, selector, swizzledImplementation, "v@:"), let method = class_getInstanceMethod(self, selector) { originalImplementation = method_getImplementation(method) originalImplementation = method_setImplementation(method, swizzledImplementation) diff --git a/Sources/Bond/Shared/NotificationCenter.swift b/Sources/Bond/Shared/NotificationCenter.swift index c977d0ea..51fd4d72 100644 --- a/Sources/Bond/Shared/NotificationCenter.swift +++ b/Sources/Bond/Shared/NotificationCenter.swift @@ -26,7 +26,6 @@ import Foundation import ReactiveKit extension ReactiveExtensions where Base: NotificationCenter { - /// Observe notifications using a signal. public func notification(name: NSNotification.Name, object: AnyObject? = nil) -> Signal { return Signal { observer in diff --git a/Sources/Bond/Signal+Heartbeat.swift b/Sources/Bond/Signal+Heartbeat.swift index bd60af0a..b8f430ce 100644 --- a/Sources/Bond/Signal+Heartbeat.swift +++ b/Sources/Bond/Signal+Heartbeat.swift @@ -24,19 +24,18 @@ #if os(iOS) || os(tvOS) -import ReactiveKit -import UIKit + import ReactiveKit + import UIKit -extension SignalProtocol { - - /// Fires an event on start and every `interval` seconds as long as the app is in foreground. - /// Pauses when the app goes to background. Restarts when the app is back in foreground. - public static func heartbeat(interval seconds: Double) -> Signal { - let willEnterForeground = NotificationCenter.default.reactive.notification(name: UIApplication.willEnterForegroundNotification) - let didEnterBackgorund = NotificationCenter.default.reactive.notification(name: UIApplication.didEnterBackgroundNotification) - return willEnterForeground.replaceElements(with: ()).prepend(()).flatMapLatest { () -> Signal in - return SafeSignal(sequence: 0..., interval: seconds, queue: .global()).replaceElements(with: ()).prepend(()).prefix(untilOutputFrom: didEnterBackgorund) + extension SignalProtocol { + /// Fires an event on start and every `interval` seconds as long as the app is in foreground. + /// Pauses when the app goes to background. Restarts when the app is back in foreground. + public static func heartbeat(interval seconds: Double) -> Signal { + let willEnterForeground = NotificationCenter.default.reactive.notification(name: UIApplication.willEnterForegroundNotification) + let didEnterBackgorund = NotificationCenter.default.reactive.notification(name: UIApplication.didEnterBackgroundNotification) + return willEnterForeground.replaceElements(with: ()).prepend(()).flatMapLatest { () -> Signal in + SafeSignal(sequence: 0..., interval: seconds, queue: .global()).replaceElements(with: ()).prepend(()).prefix(untilOutputFrom: didEnterBackgorund) + } } } -} #endif diff --git a/Sources/Bond/UIKit/UIAccessibilityIdentification.swift b/Sources/Bond/UIKit/UIAccessibilityIdentification.swift index f113aed6..04723b8b 100644 --- a/Sources/Bond/UIKit/UIAccessibilityIdentification.swift +++ b/Sources/Bond/UIKit/UIAccessibilityIdentification.swift @@ -8,16 +8,15 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: Deallocatable, Base: UIAccessibilityIdentification { - - var accessibilityIdentifier: Bond { - return bond(context: .immediateOnMain) { - $0.accessibilityIdentifier = $1 + extension ReactiveExtensions where Base: Deallocatable, Base: UIAccessibilityIdentification { + var accessibilityIdentifier: Bond { + return bond(context: .immediateOnMain) { + $0.accessibilityIdentifier = $1 + } } } -} #endif diff --git a/Sources/Bond/UIKit/UIActivityIndicatorView.swift b/Sources/Bond/UIKit/UIActivityIndicatorView.swift index ea455f99..59b7e6c5 100644 --- a/Sources/Bond/UIKit/UIActivityIndicatorView.swift +++ b/Sources/Bond/UIKit/UIActivityIndicatorView.swift @@ -24,27 +24,25 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIActivityIndicatorView { - - public var isAnimating: Bond { - return bond { - if $1 { - $0.startAnimating() - } else { - $0.stopAnimating() + extension ReactiveExtensions where Base: UIActivityIndicatorView { + public var isAnimating: Bond { + return bond { + if $1 { + $0.startAnimating() + } else { + $0.stopAnimating() + } } } } -} - -extension UIActivityIndicatorView: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.isAnimating.bind(signal: signal) + extension UIActivityIndicatorView: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.isAnimating.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIApplication.swift b/Sources/Bond/UIKit/UIApplication.swift index 88c34652..18b119f8 100644 --- a/Sources/Bond/UIKit/UIApplication.swift +++ b/Sources/Bond/UIKit/UIApplication.swift @@ -24,16 +24,15 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIApplication { - - #if os(iOS) - public var isNetworkActivityIndicatorVisible: Bond { - return bond { $0.isNetworkActivityIndicatorVisible = $1 } + extension ReactiveExtensions where Base: UIApplication { + #if os(iOS) + public var isNetworkActivityIndicatorVisible: Bond { + return bond { $0.isNetworkActivityIndicatorVisible = $1 } + } + #endif } - #endif -} #endif diff --git a/Sources/Bond/UIKit/UIBarButtonItem.swift b/Sources/Bond/UIKit/UIBarButtonItem.swift index 89fbca02..bf1c890f 100644 --- a/Sources/Bond/UIKit/UIBarButtonItem.swift +++ b/Sources/Bond/UIKit/UIBarButtonItem.swift @@ -24,51 +24,48 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension UIBarButtonItem { - - fileprivate struct AssociatedKeys { - static var BarButtonItemHelperKey = "bnd_BarButtonItemHelperKey" - } + extension UIBarButtonItem { + fileprivate struct AssociatedKeys { + static var BarButtonItemHelperKey = "bnd_BarButtonItemHelperKey" + } - @objc fileprivate class BondTarget: NSObject - { - weak var barButtonItem: UIBarButtonItem? - let subject = PassthroughSubject() + @objc fileprivate class BondTarget: NSObject { + weak var barButtonItem: UIBarButtonItem? + let subject = PassthroughSubject() - init(barButtonItem: UIBarButtonItem) { - self.barButtonItem = barButtonItem - super.init() + init(barButtonItem: UIBarButtonItem) { + self.barButtonItem = barButtonItem + super.init() - barButtonItem.target = self - barButtonItem.action = #selector(eventHandler) - } + barButtonItem.target = self + barButtonItem.action = #selector(eventHandler) + } - @objc func eventHandler() { - subject.send(()) - } + @objc func eventHandler() { + subject.send(()) + } - deinit { - barButtonItem?.target = nil - barButtonItem?.action = nil - subject.send(completion: .finished) + deinit { + barButtonItem?.target = nil + barButtonItem?.action = nil + subject.send(completion: .finished) + } } } -} - -extension ReactiveExtensions where Base: UIBarButtonItem { - public var tap: SafeSignal { - if let target = objc_getAssociatedObject(base, &UIBarButtonItem.AssociatedKeys.BarButtonItemHelperKey) as AnyObject? { - return (target as! UIBarButtonItem.BondTarget).subject.toSignal() - } else { - let target = UIBarButtonItem.BondTarget(barButtonItem: base) - objc_setAssociatedObject(base, &UIBarButtonItem.AssociatedKeys.BarButtonItemHelperKey, target, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return target.subject.toSignal() + extension ReactiveExtensions where Base: UIBarButtonItem { + public var tap: SafeSignal { + if let target = objc_getAssociatedObject(base, &UIBarButtonItem.AssociatedKeys.BarButtonItemHelperKey) as AnyObject? { + return (target as! UIBarButtonItem.BondTarget).subject.toSignal() + } else { + let target = UIBarButtonItem.BondTarget(barButtonItem: base) + objc_setAssociatedObject(base, &UIBarButtonItem.AssociatedKeys.BarButtonItemHelperKey, target, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return target.subject.toSignal() + } } } -} #endif diff --git a/Sources/Bond/UIKit/UIBarItem.swift b/Sources/Bond/UIKit/UIBarItem.swift index ae295fc8..224f8e70 100644 --- a/Sources/Bond/UIKit/UIBarItem.swift +++ b/Sources/Bond/UIKit/UIBarItem.swift @@ -24,26 +24,25 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension UIBarItem: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: UIBarItem { - - public var title: Bond { - return bond { $0.title = $1 } + extension UIBarItem: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } } - public var image: Bond { - return bond { $0.image = $1 } - } + extension ReactiveExtensions where Base: UIBarItem { + public var title: Bond { + return bond { $0.title = $1 } + } + + public var image: Bond { + return bond { $0.image = $1 } + } - public var isEnabled: Bond { - return bond { $0.isEnabled = $1 } + public var isEnabled: Bond { + return bond { $0.isEnabled = $1 } + } } -} #endif diff --git a/Sources/Bond/UIKit/UIButton.swift b/Sources/Bond/UIKit/UIButton.swift index c3d8ea5a..68848c6d 100644 --- a/Sources/Bond/UIKit/UIButton.swift +++ b/Sources/Bond/UIKit/UIButton.swift @@ -24,34 +24,33 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIButton { + extension ReactiveExtensions where Base: UIButton { + public var title: Bond { + return bond { $0.setTitle($1, for: .normal) } + } - public var title: Bond { - return bond { $0.setTitle($1, for: .normal) } - } - - public var tap: SafeSignal { - return controlEvents(.touchUpInside) - } + public var tap: SafeSignal { + return controlEvents(.touchUpInside) + } - public var isSelected: Bond { - return bond { $0.isSelected = $1 } - } + public var isSelected: Bond { + return bond { $0.isSelected = $1 } + } - public var isHighlighted: Bond { - return bond { $0.isHighlighted = $1 } - } + public var isHighlighted: Bond { + return bond { $0.isHighlighted = $1 } + } - public var backgroundImage: Bond { - return bond { $0.setBackgroundImage($1, for: .normal) } - } + public var backgroundImage: Bond { + return bond { $0.setBackgroundImage($1, for: .normal) } + } - public var image: Bond { - return bond { $0.setImage($1, for: .normal) } + public var image: Bond { + return bond { $0.setImage($1, for: .normal) } + } } -} #endif diff --git a/Sources/Bond/UIKit/UICollectionView+DataSource.swift b/Sources/Bond/UIKit/UICollectionView+DataSource.swift index 586aa4dd..64c4c4e9 100644 --- a/Sources/Bond/UIKit/UICollectionView+DataSource.swift +++ b/Sources/Bond/UIKit/UICollectionView+DataSource.swift @@ -24,217 +24,214 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { - - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - createCell: A closure that creates (dequeues) cell for the given table view and configures it with the given data source at the given index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to collectionView: UICollectionView, createCell: @escaping (Element.Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell) -> Disposable { - let binder = CollectionViewBinderDataSource(createCell) - return bind(to: collectionView, using: binder) - } - - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - binder: A `TableViewBinder` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to collectionView: UICollectionView, using binderDataSource: CollectionViewBinderDataSource) -> Disposable { - binderDataSource.collectionView = collectionView - return bind(to: collectionView) { (_, changeset) in - binderDataSource.changeset = changeset.asSectionedDataSourceChangeset - } - } -} - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { - - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - cellType: A type of the cells that should display the data. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to collectionView: UICollectionView, cellType: Cell.Type, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifier = String(describing: Cell.self) - let bundle = Bundle(for: Cell.self) - if let _ = bundle.path(forResource: identifier, ofType: "nib") { - let nib = UINib(nibName: identifier, bundle: bundle) - collectionView.register(nib, forCellWithReuseIdentifier: identifier) - } else { - collectionView.register(cellType as AnyClass, forCellWithReuseIdentifier: identifier) + import ReactiveKit + import UIKit + + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - createCell: A closure that creates (dequeues) cell for the given table view and configures it with the given data source at the given index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to collectionView: UICollectionView, createCell: @escaping (Element.Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell) -> Disposable { + let binder = CollectionViewBinderDataSource(createCell) + return bind(to: collectionView, using: binder) + } + + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - binder: A `TableViewBinder` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to collectionView: UICollectionView, using binderDataSource: CollectionViewBinderDataSource) -> Disposable { + binderDataSource.collectionView = collectionView + return bind(to: collectionView) { _, changeset in + binderDataSource.changeset = changeset.asSectionedDataSourceChangeset + } } - return bind(to: collectionView, createCell: { (dataSource, indexPath, collectionView) -> UICollectionViewCell in - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell - let item = dataSource.item(at: indexPath) - configureCell(cell, item) - return cell - }) } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - cellType: A type of the cells that should display the data. Cell type name will be used as reusable identifier and the binding will automatically dequeue cell. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to collectionView: UICollectionView, cellType: Cell.Type, using binderDataSource: CollectionViewBinderDataSource, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifier = String(describing: Cell.self) - let bundle = Bundle(for: Cell.self) - if let _ = bundle.path(forResource: identifier, ofType: "nib") { - let nib = UINib(nibName: identifier, bundle: bundle) - collectionView.register(nib, forCellWithReuseIdentifier: identifier) - } else { - collectionView.register(cellType as AnyClass, forCellWithReuseIdentifier: identifier) - } - binderDataSource.createCell = { (dataSource, indexPath, collectionView) -> UICollectionViewCell in - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell - let item = dataSource.item(at: indexPath) - configureCell(cell, item) - return cell + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - cellType: A type of the cells that should display the data. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to collectionView: UICollectionView, cellType: Cell.Type, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifier = String(describing: Cell.self) + let bundle = Bundle(for: Cell.self) + if let _ = bundle.path(forResource: identifier, ofType: "nib") { + let nib = UINib(nibName: identifier, bundle: bundle) + collectionView.register(nib, forCellWithReuseIdentifier: identifier) + } else { + collectionView.register(cellType as AnyClass, forCellWithReuseIdentifier: identifier) + } + return bind(to: collectionView, createCell: { (dataSource, indexPath, collectionView) -> UICollectionViewCell in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell + let item = dataSource.item(at: indexPath) + configureCell(cell, item) + return cell + }) + } + + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - cellType: A type of the cells that should display the data. Cell type name will be used as reusable identifier and the binding will automatically dequeue cell. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to collectionView: UICollectionView, cellType: Cell.Type, using binderDataSource: CollectionViewBinderDataSource, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifier = String(describing: Cell.self) + let bundle = Bundle(for: Cell.self) + if let _ = bundle.path(forResource: identifier, ofType: "nib") { + let nib = UINib(nibName: identifier, bundle: bundle) + collectionView.register(nib, forCellWithReuseIdentifier: identifier) + } else { + collectionView.register(cellType as AnyClass, forCellWithReuseIdentifier: identifier) + } + binderDataSource.createCell = { (dataSource, indexPath, collectionView) -> UICollectionViewCell in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! Cell + let item = dataSource.item(at: indexPath) + configureCell(cell, item) + return cell + } + return bind(to: collectionView, using: binderDataSource) } - return bind(to: collectionView, using: binderDataSource) } -} - -private var CollectionViewBinderDataSourceAssociationKey = "CollectionViewBinderDataSource" -open class CollectionViewBinderDataSource: NSObject, UICollectionViewDataSource { + private var CollectionViewBinderDataSourceAssociationKey = "CollectionViewBinderDataSource" - public var createCell: ((Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell)? + open class CollectionViewBinderDataSource: NSObject, UICollectionViewDataSource { + public var createCell: ((Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell)? - public var changeset: Changeset? = nil { - didSet { - if let changeset = changeset, oldValue != nil { - applyChangeset(changeset) - } else { - collectionView?.reloadData() - ensureCollectionViewSyncsWithTheDataSource() + public var changeset: Changeset? { + didSet { + if let changeset = changeset, oldValue != nil { + applyChangeset(changeset) + } else { + collectionView?.reloadData() + ensureCollectionViewSyncsWithTheDataSource() + } } } - } - open weak var collectionView: UICollectionView? = nil { - didSet { - guard let collectionView = collectionView else { return } - associateWithCollectionView(collectionView) + open weak var collectionView: UICollectionView? { + didSet { + guard let collectionView = collectionView else { return } + associateWithCollectionView(collectionView) + } } - } - public override init() { - createCell = nil - } + public override init() { + createCell = nil + } - /// - parameter createCell: A closure that creates cell for a given table view and configures it with the given data source at the given index path. - public init(_ createCell: @escaping (Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell) { - self.createCell = createCell - } + /// - parameter createCell: A closure that creates cell for a given table view and configures it with the given data source at the given index path. + public init(_ createCell: @escaping (Changeset.Collection, IndexPath, UICollectionView) -> UICollectionViewCell) { + self.createCell = createCell + } - open func numberOfSections(in collectionView: UICollectionView) -> Int { - return changeset?.collection.numberOfSections ?? 0 - } + open func numberOfSections(in _: UICollectionView) -> Int { + return changeset?.collection.numberOfSections ?? 0 + } - open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return changeset?.collection.numberOfItems(inSection: section) ?? 0 - } + open func collectionView(_: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return changeset?.collection.numberOfItems(inSection: section) ?? 0 + } - open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let changeset = changeset else { fatalError() } - if let createCell = createCell { - return createCell(changeset.collection, indexPath, collectionView) - } else { - fatalError("Subclass of CollectionViewBinderDataSource should override and implement collectionView(_:cellForItemAt:) method if they do not initialize `createCell` closure.") + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let changeset = changeset else { fatalError() } + if let createCell = createCell { + return createCell(changeset.collection, indexPath, collectionView) + } else { + fatalError("Subclass of CollectionViewBinderDataSource should override and implement collectionView(_:cellForItemAt:) method if they do not initialize `createCell` closure.") + } } - } - open func applyChangeset(_ changeset: Changeset) { - guard let collectionView = collectionView else { return } - let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } - if diff.isEmpty { - collectionView.reloadData() - } else if diff.count == 1 { - applyChangesetDiff(diff) - } else { - collectionView.performBatchUpdates({ + open func applyChangeset(_ changeset: Changeset) { + guard let collectionView = collectionView else { return } + let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } + if diff.isEmpty { + collectionView.reloadData() + } else if diff.count == 1 { applyChangesetDiff(diff) - }, completion: nil) + } else { + collectionView.performBatchUpdates({ + applyChangesetDiff(diff) + }, completion: nil) + } + ensureCollectionViewSyncsWithTheDataSource() } - ensureCollectionViewSyncsWithTheDataSource() - } - open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { - guard let collectionView = collectionView else { return } - let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } - if !insertedSections.isEmpty { - collectionView.insertSections(IndexSet(insertedSections)) - } - let insertedItems = diff.inserts.filter { $0.count == 2 } - if !insertedItems.isEmpty { - collectionView.insertItems(at: insertedItems) - } - let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } - if !deletedSections.isEmpty { - collectionView.deleteSections(IndexSet(deletedSections)) - } - let deletedItems = diff.deletes.filter { $0.count == 2 } - if !deletedItems.isEmpty { - collectionView.deleteItems(at: deletedItems) - } - let updatedItems = diff.updates.filter { $0.count == 2 } - if !updatedItems.isEmpty { - collectionView.reloadItems(at: updatedItems) - } - let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } - if !updatedSections.isEmpty { - collectionView.reloadSections(IndexSet(updatedSections)) - } - for move in diff.moves { - if move.from.count == 2 && move.to.count == 2 { - collectionView.moveItem(at: move.from, to: move.to) - } else if move.from.count == 1 && move.to.count == 1 { - collectionView.moveSection(move.from[0], toSection: move.to[0]) + open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { + guard let collectionView = collectionView else { return } + let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } + if !insertedSections.isEmpty { + collectionView.insertSections(IndexSet(insertedSections)) + } + let insertedItems = diff.inserts.filter { $0.count == 2 } + if !insertedItems.isEmpty { + collectionView.insertItems(at: insertedItems) + } + let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } + if !deletedSections.isEmpty { + collectionView.deleteSections(IndexSet(deletedSections)) + } + let deletedItems = diff.deletes.filter { $0.count == 2 } + if !deletedItems.isEmpty { + collectionView.deleteItems(at: deletedItems) + } + let updatedItems = diff.updates.filter { $0.count == 2 } + if !updatedItems.isEmpty { + collectionView.reloadItems(at: updatedItems) + } + let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } + if !updatedSections.isEmpty { + collectionView.reloadSections(IndexSet(updatedSections)) + } + for move in diff.moves { + if move.from.count == 2, move.to.count == 2 { + collectionView.moveItem(at: move.from, to: move.to) + } else if move.from.count == 1, move.to.count == 1 { + collectionView.moveSection(move.from[0], toSection: move.to[0]) + } } } - } - private func ensureCollectionViewSyncsWithTheDataSource() { - // Hack to immediately apply changes. Solves the crashing issue when performing updates before collection view is on screen. - _ = collectionView?.numberOfSections - } + private func ensureCollectionViewSyncsWithTheDataSource() { + // Hack to immediately apply changes. Solves the crashing issue when performing updates before collection view is on screen. + _ = collectionView?.numberOfSections + } - private func associateWithCollectionView(_ collectionView: UICollectionView) { - objc_setAssociatedObject(collectionView, &CollectionViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if collectionView.reactive.hasProtocolProxy(for: UICollectionViewDataSource.self) { - collectionView.reactive.dataSource.forwardTo = self - } else { - collectionView.dataSource = self + private func associateWithCollectionView(_ collectionView: UICollectionView) { + objc_setAssociatedObject(collectionView, &CollectionViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if collectionView.reactive.hasProtocolProxy(for: UICollectionViewDataSource.self) { + collectionView.reactive.dataSource.forwardTo = self + } else { + collectionView.dataSource = self + } } } -} #endif diff --git a/Sources/Bond/UIKit/UICollectionView.swift b/Sources/Bond/UIKit/UICollectionView.swift index 3bfbe90f..73779a35 100644 --- a/Sources/Bond/UIKit/UICollectionView.swift +++ b/Sources/Bond/UIKit/UICollectionView.swift @@ -24,37 +24,36 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UICollectionView { - - /// A `ProtocolProxy` for the collection view delegate. - /// - /// - Note: Accessing this property for the first time will replace collection view's current delegate - /// with a protocol proxy object (an object that is stored in this property). - /// Current delegate will be used as `forwardTo` delegate of protocol proxy. - public var delegate: ProtocolProxy { - return protocolProxy(for: UICollectionViewDelegate.self, keyPath: \.delegate) - } + extension ReactiveExtensions where Base: UICollectionView { + /// A `ProtocolProxy` for the collection view delegate. + /// + /// - Note: Accessing this property for the first time will replace collection view's current delegate + /// with a protocol proxy object (an object that is stored in this property). + /// Current delegate will be used as `forwardTo` delegate of protocol proxy. + public var delegate: ProtocolProxy { + return protocolProxy(for: UICollectionViewDelegate.self, keyPath: \.delegate) + } - /// A `ProtocolProxy` for the collection view data source. - /// - /// - Note: Accessing this property for the first time will replace collection view's current data source - /// with a protocol proxy object (an object that is stored in this property). - /// Current data source will be used as `forwardTo` data source of protocol proxy. - public var dataSource: ProtocolProxy { - return protocolProxy(for: UICollectionViewDataSource.self, keyPath: \.dataSource) - } + /// A `ProtocolProxy` for the collection view data source. + /// + /// - Note: Accessing this property for the first time will replace collection view's current data source + /// with a protocol proxy object (an object that is stored in this property). + /// Current data source will be used as `forwardTo` data source of protocol proxy. + public var dataSource: ProtocolProxy { + return protocolProxy(for: UICollectionViewDataSource.self, keyPath: \.dataSource) + } - /// A signal that emits index paths of selected collection view cells. - /// - /// - Note: Uses collection view's `delegate` protocol proxy to observe calls made to `UICollectionViewDelegate.collectionView(_:didSelectItemAt:)` method. - public var selectedItemIndexPath: SafeSignal { - return delegate.signal(for: #selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:))) { (subject: PassthroughSubject, _: UICollectionView, indexPath: IndexPath) in - subject.send(indexPath) + /// A signal that emits index paths of selected collection view cells. + /// + /// - Note: Uses collection view's `delegate` protocol proxy to observe calls made to `UICollectionViewDelegate.collectionView(_:didSelectItemAt:)` method. + public var selectedItemIndexPath: SafeSignal { + return delegate.signal(for: #selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:))) { (subject: PassthroughSubject, _: UICollectionView, indexPath: IndexPath) in + subject.send(indexPath) + } } } -} #endif diff --git a/Sources/Bond/UIKit/UIControl.swift b/Sources/Bond/UIKit/UIControl.swift index 2f5ca361..e676a38a 100644 --- a/Sources/Bond/UIKit/UIControl.swift +++ b/Sources/Bond/UIKit/UIControl.swift @@ -24,59 +24,57 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIControl { + extension ReactiveExtensions where Base: UIControl { + public func controlEvents(_ events: UIControl.Event) -> SafeSignal { + let base = self.base + return Signal { [weak base] observer in + guard let base = base else { + observer.receive(completion: .finished) + return NonDisposable.instance + } + let target = BNDControlTarget(control: base, events: events) { + observer.receive(()) + } + return MainBlockDisposable { + target.unregister() + } + }.prefix(untilOutputFrom: base.deallocated) + } - public func controlEvents(_ events: UIControl.Event) -> SafeSignal { - let base = self.base - return Signal { [weak base] observer in - guard let base = base else { - observer.receive(completion: .finished) - return NonDisposable.instance - } - let target = BNDControlTarget(control: base, events: events) { - observer.receive(()) - } - return MainBlockDisposable { - target.unregister() - } - }.prefix(untilOutputFrom: base.deallocated) + public var isEnabled: Bond { + return bond { $0.isEnabled = $1 } + } } - public var isEnabled: Bond { - return bond { $0.isEnabled = $1 } - } -} + @objc fileprivate class BNDControlTarget: NSObject { + private weak var control: UIControl? + private let observer: () -> Void + private let events: UIControl.Event -@objc fileprivate class BNDControlTarget: NSObject -{ - private weak var control: UIControl? - private let observer: () -> Void - private let events: UIControl.Event + fileprivate init(control: UIControl, events: UIControl.Event, observer: @escaping () -> Void) { + self.control = control + self.events = events + self.observer = observer - fileprivate init(control: UIControl, events: UIControl.Event, observer: @escaping () -> Void) { - self.control = control - self.events = events - self.observer = observer + super.init() - super.init() + control.addTarget(self, action: #selector(actionHandler), for: events) + } - control.addTarget(self, action: #selector(actionHandler), for: events) - } + @objc private func actionHandler() { + observer() + } - @objc private func actionHandler() { - observer() - } - - fileprivate func unregister() { - control?.removeTarget(self, action: nil, for: events) - } + fileprivate func unregister() { + control?.removeTarget(self, action: nil, for: events) + } - deinit { - unregister() + deinit { + unregister() + } } -} #endif diff --git a/Sources/Bond/UIKit/UIDatePicker.swift b/Sources/Bond/UIKit/UIDatePicker.swift index 05bb44c5..2b032b9e 100644 --- a/Sources/Bond/UIKit/UIDatePicker.swift +++ b/Sources/Bond/UIKit/UIDatePicker.swift @@ -24,25 +24,23 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIDatePicker { - - public var date: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.date }, - set: { $0.date = $1 } - ) + extension ReactiveExtensions where Base: UIDatePicker { + public var date: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.date }, + set: { $0.date = $1 } + ) + } } -} - -extension UIDatePicker: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.date.bind(signal: signal) + extension UIDatePicker: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.date.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIGestureRecognizer.swift b/Sources/Bond/UIKit/UIGestureRecognizer.swift index f1adeba1..c7786762 100644 --- a/Sources/Bond/UIKit/UIGestureRecognizer.swift +++ b/Sources/Bond/UIKit/UIGestureRecognizer.swift @@ -25,112 +25,109 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension UIGestureRecognizer: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: UIGestureRecognizer { + extension UIGestureRecognizer: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } + } - public var isEnabled: Bond { - return bond { $0.isEnabled = $1 } + extension ReactiveExtensions where Base: UIGestureRecognizer { + public var isEnabled: Bond { + return bond { $0.isEnabled = $1 } + } } -} #endif #if os(iOS) -extension ReactiveExtensions where Base: UIView { - - public func addGestureRecognizer(_ gestureRecognizer: T) -> SafeSignal { - let base = self.base - return Signal { [weak base] observer in - guard let base = base else { - observer.receive(completion: .finished) - return NonDisposable.instance - } - let target = BNDGestureTarget(view: base, gestureRecognizer: gestureRecognizer) { recog in - observer.receive(recog as! T) - } - return MainBlockDisposable { - target.unregister() - } - }.prefix(untilOutputFrom: base.deallocated) + extension ReactiveExtensions where Base: UIView { + public func addGestureRecognizer(_ gestureRecognizer: T) -> SafeSignal { + let base = self.base + return Signal { [weak base] observer in + guard let base = base else { + observer.receive(completion: .finished) + return NonDisposable.instance + } + let target = BNDGestureTarget(view: base, gestureRecognizer: gestureRecognizer) { recog in + observer.receive(recog as! T) + } + return MainBlockDisposable { + target.unregister() + } + }.prefix(untilOutputFrom: base.deallocated) + } + + public func tapGesture(numberOfTaps: Int = 1, numberOfTouches: Int = 1) -> SafeSignal { + let gesture = UITapGestureRecognizer() + gesture.numberOfTapsRequired = numberOfTaps + gesture.numberOfTouchesRequired = numberOfTouches + + return addGestureRecognizer(gesture) + } + + public func panGesture(numberOfTouches: Int = 1) -> SafeSignal { + let gesture = UIPanGestureRecognizer() + gesture.minimumNumberOfTouches = numberOfTouches + + return addGestureRecognizer(gesture) + } + + public func swipeGesture(numberOfTouches: Int, direction: UISwipeGestureRecognizer.Direction) -> SafeSignal { + let gesture = UISwipeGestureRecognizer() + gesture.numberOfTouchesRequired = numberOfTouches + gesture.direction = direction + + return addGestureRecognizer(gesture) + } + + public func pinchGesture() -> SafeSignal { + return addGestureRecognizer(UIPinchGestureRecognizer()) + } + + public func longPressGesture(numberOfTaps: Int = 0, numberOfTouches: Int = 1, minimumPressDuration: CFTimeInterval = 0.3, allowableMovement: CGFloat = 10) -> SafeSignal { + let gesture = UILongPressGestureRecognizer() + gesture.numberOfTapsRequired = numberOfTaps + gesture.numberOfTouchesRequired = numberOfTouches + gesture.minimumPressDuration = minimumPressDuration + gesture.allowableMovement = allowableMovement + + return addGestureRecognizer(gesture) + } + + public func rotationGesture() -> SafeSignal { + return addGestureRecognizer(UIRotationGestureRecognizer()) + } } - public func tapGesture(numberOfTaps: Int = 1, numberOfTouches: Int = 1) -> SafeSignal { - let gesture = UITapGestureRecognizer() - gesture.numberOfTapsRequired = numberOfTaps - gesture.numberOfTouchesRequired = numberOfTouches - - return addGestureRecognizer(gesture) - } + @objc fileprivate class BNDGestureTarget: NSObject { + private weak var view: UIView? + private let observer: (UIGestureRecognizer) -> Void + private let gestureRecognizer: UIGestureRecognizer - public func panGesture(numberOfTouches: Int = 1) -> SafeSignal { - let gesture = UIPanGestureRecognizer() - gesture.minimumNumberOfTouches = numberOfTouches + fileprivate init(view: UIView, gestureRecognizer: UIGestureRecognizer, observer: @escaping (UIGestureRecognizer) -> Void) { + self.view = view + self.gestureRecognizer = gestureRecognizer + self.observer = observer - return addGestureRecognizer(gesture) - } + super.init() - public func swipeGesture(numberOfTouches: Int, direction: UISwipeGestureRecognizer.Direction) -> SafeSignal { - let gesture = UISwipeGestureRecognizer() - gesture.numberOfTouchesRequired = numberOfTouches - gesture.direction = direction + gestureRecognizer.addTarget(self, action: #selector(actionHandler(recogniser:))) + view.addGestureRecognizer(gestureRecognizer) + } - return addGestureRecognizer(gesture) - } - - public func pinchGesture() -> SafeSignal { - return addGestureRecognizer(UIPinchGestureRecognizer()) - } - - public func longPressGesture(numberOfTaps: Int = 0, numberOfTouches: Int = 1, minimumPressDuration: CFTimeInterval = 0.3, allowableMovement: CGFloat = 10) -> SafeSignal { - let gesture = UILongPressGestureRecognizer() - gesture.numberOfTapsRequired = numberOfTaps - gesture.numberOfTouchesRequired = numberOfTouches - gesture.minimumPressDuration = minimumPressDuration - gesture.allowableMovement = allowableMovement - - return addGestureRecognizer(gesture) - } + @objc private func actionHandler(recogniser: UIGestureRecognizer) { + observer(recogniser) + } - public func rotationGesture() -> SafeSignal { - return addGestureRecognizer(UIRotationGestureRecognizer()) - } -} - -@objc fileprivate class BNDGestureTarget: NSObject { - - private weak var view: UIView? - private let observer: (UIGestureRecognizer) -> Void - private let gestureRecognizer: UIGestureRecognizer - - fileprivate init(view: UIView, gestureRecognizer: UIGestureRecognizer, observer: @escaping (UIGestureRecognizer) -> Void) { - self.view = view - self.gestureRecognizer = gestureRecognizer - self.observer = observer - - super.init() - - gestureRecognizer.addTarget(self, action: #selector(actionHandler(recogniser:))) - view.addGestureRecognizer(gestureRecognizer) - } - - @objc private func actionHandler(recogniser: UIGestureRecognizer) { - observer(recogniser) - } - - fileprivate func unregister() { - view?.removeGestureRecognizer(gestureRecognizer) - } + fileprivate func unregister() { + view?.removeGestureRecognizer(gestureRecognizer) + } - deinit { - unregister() + deinit { + unregister() + } } -} #endif diff --git a/Sources/Bond/UIKit/UIImageView.swift b/Sources/Bond/UIKit/UIImageView.swift index b57f7a46..64db03ec 100644 --- a/Sources/Bond/UIKit/UIImageView.swift +++ b/Sources/Bond/UIKit/UIImageView.swift @@ -24,21 +24,19 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIImageView { - - public var image: Bond { - return bond { $0.image = $1 } + extension ReactiveExtensions where Base: UIImageView { + public var image: Bond { + return bond { $0.image = $1 } + } } -} - -extension UIImageView: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.image.bind(signal: signal) + extension UIImageView: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.image.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UILabel.swift b/Sources/Bond/UIKit/UILabel.swift index 0d956863..4190d9a9 100644 --- a/Sources/Bond/UIKit/UILabel.swift +++ b/Sources/Bond/UIKit/UILabel.swift @@ -24,29 +24,27 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UILabel { + extension ReactiveExtensions where Base: UILabel { + public var text: Bond { + return bond { $0.text = $1 } + } - public var text: Bond { - return bond { $0.text = $1 } - } - - public var attributedText: Bond { - return bond { $0.attributedText = $1 } - } + public var attributedText: Bond { + return bond { $0.attributedText = $1 } + } - public var textColor: Bond { - return bond { $0.textColor = $1 } + public var textColor: Bond { + return bond { $0.textColor = $1 } + } } -} - -extension UILabel: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.text.bind(signal: signal) + extension UILabel: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.text.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UINavigationBar.swift b/Sources/Bond/UIKit/UINavigationBar.swift index c922c9d5..b5101bfd 100644 --- a/Sources/Bond/UIKit/UINavigationBar.swift +++ b/Sources/Bond/UIKit/UINavigationBar.swift @@ -24,14 +24,13 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UINavigationBar { - - public var barTintColor: Bond { - return bond { $0.barTintColor = $1 } + extension ReactiveExtensions where Base: UINavigationBar { + public var barTintColor: Bond { + return bond { $0.barTintColor = $1 } + } } -} #endif diff --git a/Sources/Bond/UIKit/UINavigationItem.swift b/Sources/Bond/UIKit/UINavigationItem.swift index be3d0f79..ae9a8237 100644 --- a/Sources/Bond/UIKit/UINavigationItem.swift +++ b/Sources/Bond/UIKit/UINavigationItem.swift @@ -24,18 +24,17 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension UINavigationItem: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: UINavigationItem { + extension UINavigationItem: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } + } - public var title: Bond { - return bond { $0.title = $1 } + extension ReactiveExtensions where Base: UINavigationItem { + public var title: Bond { + return bond { $0.title = $1 } + } } -} #endif diff --git a/Sources/Bond/UIKit/UIPickerView+DataSource.swift b/Sources/Bond/UIKit/UIPickerView+DataSource.swift index 98f52ccf..e7c645ab 100644 --- a/Sources/Bond/UIKit/UIPickerView+DataSource.swift +++ b/Sources/Bond/UIKit/UIPickerView+DataSource.swift @@ -24,119 +24,116 @@ #if os(iOS) -import UIKit -import ReactiveKit - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { - - /// Binds the signal of data source elements to the given picker view. - /// - /// - parameters: - /// - pickerView: A picker view that should display the data from the data source. - /// - createTitle: A closure that configures the title for a given picker view row with the given data source at the given index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. - @discardableResult - public func bind(to pickerView: UIPickerView, createTitle: @escaping (Element.Changeset.Collection, Int, Int, UIPickerView) -> String?) -> Disposable { - let binder = PickerViewBinderDataSource(createTitle) - - return bind(to: pickerView, using: binder) - } + import ReactiveKit + import UIKit + + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given picker view. + /// + /// - parameters: + /// - pickerView: A picker view that should display the data from the data source. + /// - createTitle: A closure that configures the title for a given picker view row with the given data source at the given index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. + @discardableResult + public func bind(to pickerView: UIPickerView, createTitle: @escaping (Element.Changeset.Collection, Int, Int, UIPickerView) -> String?) -> Disposable { + let binder = PickerViewBinderDataSource(createTitle) + + return bind(to: pickerView, using: binder) + } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - pickerView: A picker view that should display the data from the data source. - /// - binder: A `PickerViewBinderDataSource` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. - @discardableResult - public func bind(to pickerView: UIPickerView, using binderDataSource: PickerViewBinderDataSource) -> Disposable { - binderDataSource.pickerView = pickerView - return bind(to: pickerView) { (_, changeset) in - binderDataSource.changeset = changeset.asSectionedDataSourceChangeset + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - pickerView: A picker view that should display the data from the data source. + /// - binder: A `PickerViewBinderDataSource` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. + @discardableResult + public func bind(to pickerView: UIPickerView, using binderDataSource: PickerViewBinderDataSource) -> Disposable { + binderDataSource.pickerView = pickerView + return bind(to: pickerView) { _, changeset in + binderDataSource.changeset = changeset.asSectionedDataSourceChangeset + } } } -} - -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { - - /// Binds the signal of data source elements to the given picker view. - /// - /// - parameters: - /// - pickerView: A picker view that should display the data from the data source. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. - @discardableResult - public func bind(to pickerView: UIPickerView) -> Disposable { - let createTitle: (Element.Changeset.Collection, Int, Int, UIPickerView) -> String? = { (dataSource, row, component, pickerView) in - let indexPath = IndexPath(row: row, section: component) - let item = dataSource.item(at: indexPath) - - return String(describing: item) - } - return bind(to: pickerView, using: PickerViewBinderDataSource(createTitle)) + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { + /// Binds the signal of data source elements to the given picker view. + /// + /// - parameters: + /// - pickerView: A picker view that should display the data from the data source. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the picker view is deallocated. + @discardableResult + public func bind(to pickerView: UIPickerView) -> Disposable { + let createTitle: (Element.Changeset.Collection, Int, Int, UIPickerView) -> String? = { dataSource, row, component, _ in + let indexPath = IndexPath(row: row, section: component) + let item = dataSource.item(at: indexPath) + + return String(describing: item) + } + + return bind(to: pickerView, using: PickerViewBinderDataSource(createTitle)) + } } -} - -private var PickerViewBinderDataSourceAssociationKey = "PickerViewBinderDataSource" -public class PickerViewBinderDataSource: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { + private var PickerViewBinderDataSourceAssociationKey = "PickerViewBinderDataSource" - public var createTitle: ((Changeset.Collection, Int, Int, UIPickerView) -> String?)? + public class PickerViewBinderDataSource: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { + public var createTitle: ((Changeset.Collection, Int, Int, UIPickerView) -> String?)? - public var changeset: Changeset? = nil { - didSet { - pickerView?.reloadAllComponents() + public var changeset: Changeset? { + didSet { + pickerView?.reloadAllComponents() + } } - } - open weak var pickerView: UIPickerView? = nil { - didSet { - guard let pickerView = pickerView else { return } - associateWithPickerView(pickerView) + open weak var pickerView: UIPickerView? { + didSet { + guard let pickerView = pickerView else { return } + associateWithPickerView(pickerView) + } } - } - public override init() { - self.createTitle = nil - } + public override init() { + createTitle = nil + } - /// - parameter createTitle: A closure that configures the title for a given picker view row with the given data source at the given index path. - public init(_ createTitle: @escaping (Changeset.Collection, Int, Int, UIPickerView) -> String?) { - self.createTitle = createTitle - } + /// - parameter createTitle: A closure that configures the title for a given picker view row with the given data source at the given index path. + public init(_ createTitle: @escaping (Changeset.Collection, Int, Int, UIPickerView) -> String?) { + self.createTitle = createTitle + } - public func numberOfComponents(in pickerView: UIPickerView) -> Int { - return changeset?.collection.numberOfSections ?? 0 - } + public func numberOfComponents(in _: UIPickerView) -> Int { + return changeset?.collection.numberOfSections ?? 0 + } - public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return changeset?.collection.numberOfItems(inSection: component) ?? 0 - } + public func pickerView(_: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return changeset?.collection.numberOfItems(inSection: component) ?? 0 + } - open func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - guard let changeset = changeset else { fatalError() } - if let createTitle = createTitle { - return createTitle(changeset.collection, row, component, pickerView) - } else { - fatalError("Subclass of PickerViewBinderDataSource should override and implement pickerView(_:titleForRow:forComponent) method if they do not initialize `createTitle` closure.") + open func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + guard let changeset = changeset else { fatalError() } + if let createTitle = createTitle { + return createTitle(changeset.collection, row, component, pickerView) + } else { + fatalError("Subclass of PickerViewBinderDataSource should override and implement pickerView(_:titleForRow:forComponent) method if they do not initialize `createTitle` closure.") + } } - } - private func associateWithPickerView(_ pickerView: UIPickerView) { - objc_setAssociatedObject(pickerView, &PickerViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + private func associateWithPickerView(_ pickerView: UIPickerView) { + objc_setAssociatedObject(pickerView, &PickerViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if pickerView.reactive.hasProtocolProxy(for: UIPickerViewDataSource.self) { - pickerView.reactive.dataSource.forwardTo = self - } else { - pickerView.dataSource = self - } + if pickerView.reactive.hasProtocolProxy(for: UIPickerViewDataSource.self) { + pickerView.reactive.dataSource.forwardTo = self + } else { + pickerView.dataSource = self + } - if pickerView.reactive.hasProtocolProxy(for: UIPickerViewDelegate.self) { - pickerView.reactive.delegate.forwardTo = self - } else { - pickerView.delegate = self + if pickerView.reactive.hasProtocolProxy(for: UIPickerViewDelegate.self) { + pickerView.reactive.delegate.forwardTo = self + } else { + pickerView.delegate = self + } } } -} #endif diff --git a/Sources/Bond/UIKit/UIPickerView.swift b/Sources/Bond/UIKit/UIPickerView.swift index f009eec6..2a1eca1c 100644 --- a/Sources/Bond/UIKit/UIPickerView.swift +++ b/Sources/Bond/UIKit/UIPickerView.swift @@ -24,44 +24,44 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIPickerView { + extension ReactiveExtensions where Base: UIPickerView { + /// A `ProtocolProxy` for the picker view data source. + /// + /// - Note: Accessing this property for the first time will replace picker view's current data source + /// with a protocol proxy object (an object that is stored in this property). + /// Current data source will be used as `forwardTo` data source of protocol proxy. + public var dataSource: ProtocolProxy { + return protocolProxy(for: UIPickerViewDataSource.self, keyPath: \.dataSource) + } - /// A `ProtocolProxy` for the picker view data source. - /// - /// - Note: Accessing this property for the first time will replace picker view's current data source - /// with a protocol proxy object (an object that is stored in this property). - /// Current data source will be used as `forwardTo` data source of protocol proxy. - public var dataSource: ProtocolProxy { - return protocolProxy(for: UIPickerViewDataSource.self, keyPath: \.dataSource) - } - - /// A `ProtocolProxy` for the picker view delegate. - /// - /// - Note: Accessing this property for the first time will replace table view's current delegate - /// with a protocol proxy object (an object that is stored in this property). - /// Current delegate will be used as `forwardTo` delegate of protocol proxy. - public var delegate: ProtocolProxy { - return protocolProxy(for: UIPickerViewDelegate.self, keyPath: \.delegate) - } + /// A `ProtocolProxy` for the picker view delegate. + /// + /// - Note: Accessing this property for the first time will replace table view's current delegate + /// with a protocol proxy object (an object that is stored in this property). + /// Current delegate will be used as `forwardTo` delegate of protocol proxy. + public var delegate: ProtocolProxy { + return protocolProxy(for: UIPickerViewDelegate.self, keyPath: \.delegate) + } - /// A signal that emits the row and component index of the selected picker view row. - /// - /// - Note: Uses picker view's `delegate` protocol proxy to observe calls made to `UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)` method. - public var selectedRow: SafeSignal<(Int, Int)> { - return delegate.signal( - for: #selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)), - dispatch: { ( - subject: PassthroughSubject<(Int, Int), Never>, - pickerView: UIPickerView, - row: Int, - component: Int) -> Void in + /// A signal that emits the row and component index of the selected picker view row. + /// + /// - Note: Uses picker view's `delegate` protocol proxy to observe calls made to `UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)` method. + public var selectedRow: SafeSignal<(Int, Int)> { + return delegate.signal( + for: #selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)), + dispatch: { ( + subject: PassthroughSubject<(Int, Int), Never>, + _: UIPickerView, + row: Int, + component: Int + ) -> Void in subject.send((row, component)) - } - ) + } + ) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIProgressView.swift b/Sources/Bond/UIKit/UIProgressView.swift index 897767a6..119bc7fb 100644 --- a/Sources/Bond/UIKit/UIProgressView.swift +++ b/Sources/Bond/UIKit/UIProgressView.swift @@ -24,21 +24,19 @@ #if os(iOS) || os(tvOS) -import ReactiveKit -import UIKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIProgressView { - - public var progress: Bond { - return bond { $0.progress = $1 } + extension ReactiveExtensions where Base: UIProgressView { + public var progress: Bond { + return bond { $0.progress = $1 } + } } -} - -extension UIProgressView: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.progress.bind(signal: signal) + extension UIProgressView: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.progress.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIRefreshControl.swift b/Sources/Bond/UIKit/UIRefreshControl.swift index 6ba3328d..1c7d5be7 100644 --- a/Sources/Bond/UIKit/UIRefreshControl.swift +++ b/Sources/Bond/UIKit/UIRefreshControl.swift @@ -24,25 +24,23 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIRefreshControl { - - public var refreshing: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.isRefreshing }, - set: { if $1 { $0.beginRefreshing() } else { $0.endRefreshing() } } - ) + extension ReactiveExtensions where Base: UIRefreshControl { + public var refreshing: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.isRefreshing }, + set: { if $1 { $0.beginRefreshing() } else { $0.endRefreshing() } } + ) + } } -} - -extension UIRefreshControl: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.refreshing.bind(signal: signal) + extension UIRefreshControl: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.refreshing.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UISearchBar.swift b/Sources/Bond/UIKit/UISearchBar.swift index e2b20d2f..40bdccad 100644 --- a/Sources/Bond/UIKit/UISearchBar.swift +++ b/Sources/Bond/UIKit/UISearchBar.swift @@ -24,31 +24,28 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UISearchBar { + extension ReactiveExtensions where Base: UISearchBar { + public var delegate: ProtocolProxy { + return protocolProxy(for: UISearchBarDelegate.self, keyPath: \.delegate) + } - public var delegate: ProtocolProxy { - return protocolProxy(for: UISearchBarDelegate.self, keyPath: \.delegate) - } + public var text: DynamicSubject { + let selector = #selector(UISearchBarDelegate.searchBar(_:textDidChange:)) + let signal = delegate.signal(for: selector) { (subject: PassthroughSubject, _: UISearchBar, _: NSString) in + subject.send() + } - public var text: DynamicSubject { - let selector = #selector(UISearchBarDelegate.searchBar(_:textDidChange:)) - let signal = delegate.signal(for: selector) { (subject: PassthroughSubject, _: UISearchBar, _: NSString) in - subject.send() + return dynamicSubject(signal: signal, get: { $0.text }, set: { $0.text = $1 }) } - - return dynamicSubject(signal: signal, get: { $0.text }, set: { $0.text = $1 }) } -} -extension UISearchBar: BindableProtocol { - - public func bind(signal: Signal) -> Disposable { - return reactive.text.bind(signal: signal) + extension UISearchBar: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.text.bind(signal: signal) + } } -} #endif - diff --git a/Sources/Bond/UIKit/UISegmentedControl.swift b/Sources/Bond/UIKit/UISegmentedControl.swift index a31f86a7..b2488a92 100644 --- a/Sources/Bond/UIKit/UISegmentedControl.swift +++ b/Sources/Bond/UIKit/UISegmentedControl.swift @@ -24,25 +24,23 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UISegmentedControl { - - public var selectedSegmentIndex: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.selectedSegmentIndex }, - set: { $0.selectedSegmentIndex = $1 } - ) + extension ReactiveExtensions where Base: UISegmentedControl { + public var selectedSegmentIndex: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.selectedSegmentIndex }, + set: { $0.selectedSegmentIndex = $1 } + ) + } } -} - -extension UISegmentedControl: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.selectedSegmentIndex.bind(signal: signal) + extension UISegmentedControl: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.selectedSegmentIndex.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UISlider.swift b/Sources/Bond/UIKit/UISlider.swift index 78197587..c5c3839c 100644 --- a/Sources/Bond/UIKit/UISlider.swift +++ b/Sources/Bond/UIKit/UISlider.swift @@ -24,25 +24,23 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UISlider { - - public var value: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.value }, - set: { $0.value = $1 } - ) + extension ReactiveExtensions where Base: UISlider { + public var value: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.value }, + set: { $0.value = $1 } + ) + } } -} - -extension UISlider: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.value.bind(signal: signal) + extension UISlider: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.value.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIStepper.swift b/Sources/Bond/UIKit/UIStepper.swift index 1c3b1bbf..db43d7e6 100644 --- a/Sources/Bond/UIKit/UIStepper.swift +++ b/Sources/Bond/UIKit/UIStepper.swift @@ -24,25 +24,23 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UIStepper { - - public var value: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.value }, - set: { $0.value = $1 } - ) + extension ReactiveExtensions where Base: UIStepper { + public var value: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.value }, + set: { $0.value = $1 } + ) + } } -} - -extension UIStepper: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.value.bind(signal: signal) + extension UIStepper: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.value.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UISwitch.swift b/Sources/Bond/UIKit/UISwitch.swift index e881a411..2ddb4558 100644 --- a/Sources/Bond/UIKit/UISwitch.swift +++ b/Sources/Bond/UIKit/UISwitch.swift @@ -24,25 +24,23 @@ #if os(iOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UISwitch { - - public var isOn: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.valueChanged).eraseType(), - get: { $0.isOn }, - set: { $0.isOn = $1 } - ) + extension ReactiveExtensions where Base: UISwitch { + public var isOn: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.valueChanged).eraseType(), + get: { $0.isOn }, + set: { $0.isOn = $1 } + ) + } } -} - -extension UISwitch: BindableProtocol { - public func bind(signal: Signal) -> Disposable { - return reactive.isOn.bind(signal: signal) + extension UISwitch: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.isOn.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UITableView+DataSource.swift b/Sources/Bond/UIKit/UITableView+DataSource.swift index b35ecda8..d050a425 100644 --- a/Sources/Bond/UIKit/UITableView+DataSource.swift +++ b/Sources/Bond/UIKit/UITableView+DataSource.swift @@ -24,244 +24,239 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - createCell: A closure that creates (dequeues) cell for the given table view and configures it with the given data source at the given index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to tableView: UITableView, animated: Bool = true, rowAnimation: UITableView.RowAnimation = .automatic, createCell: @escaping (Element.Changeset.Collection, IndexPath, UITableView) -> UITableViewCell) -> Disposable { + if animated { + let binder = TableViewBinderDataSource(createCell) + binder.rowInsertionAnimation = rowAnimation + binder.rowDeletionAnimation = rowAnimation + binder.rowReloadAnimation = rowAnimation + return bind(to: tableView, using: binder) + } else { + let binder = TableViewBinderDataSource.ReloadingBinder(createCell) + return bind(to: tableView, using: binder) + } + } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - createCell: A closure that creates (dequeues) cell for the given table view and configures it with the given data source at the given index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to tableView: UITableView, animated: Bool = true, rowAnimation: UITableView.RowAnimation = .automatic, createCell: @escaping (Element.Changeset.Collection, IndexPath, UITableView) -> UITableViewCell) -> Disposable { - if animated { - let binder = TableViewBinderDataSource(createCell) - binder.rowInsertionAnimation = rowAnimation - binder.rowDeletionAnimation = rowAnimation - binder.rowReloadAnimation = rowAnimation - return bind(to: tableView, using: binder) - } else { - let binder = TableViewBinderDataSource.ReloadingBinder(createCell) - return bind(to: tableView, using: binder) + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - binder: A `TableViewBinder` or its subclass that will manage the binding. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + @discardableResult + public func bind(to tableView: UITableView, using binderDataSource: TableViewBinderDataSource) -> Disposable { + binderDataSource.tableView = tableView + return bind(to: tableView) { _, changeset in + binderDataSource.changeset = changeset.asSectionedDataSourceChangeset + } } } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - binder: A `TableViewBinder` or its subclass that will manage the binding. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - @discardableResult - public func bind(to tableView: UITableView, using binderDataSource: TableViewBinderDataSource) -> Disposable { - binderDataSource.tableView = tableView - return bind(to: tableView) { (_, changeset) in - binderDataSource.changeset = changeset.asSectionedDataSourceChangeset + extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - cellType: A type of the cells that should display the data. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to tableView: UITableView, cellType: Cell.Type, animated: Bool = true, rowAnimation: UITableView.RowAnimation = .automatic, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifier = String(describing: Cell.self) + let bundle = Bundle(for: Cell.self) + if let _ = bundle.path(forResource: identifier, ofType: "nib") { + let nib = UINib(nibName: identifier, bundle: bundle) + tableView.register(nib, forCellReuseIdentifier: identifier) + } else { + tableView.register(cellType as AnyClass, forCellReuseIdentifier: identifier) + } + return bind(to: tableView, animated: animated, rowAnimation: rowAnimation, createCell: { (dataSource, indexPath, tableView) -> UITableViewCell in + guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { + fatalError( + "Failed to dequeue a cell with identifier \(identifier) matching type \(cellType.self). " + + "Check that the reuseIdentifier is set properly if using XIBs or Storyboards." + ) + } + let item = dataSource.item(at: indexPath) + configureCell(cell, item) + return cell + }) } - } -} -extension SignalProtocol where Element: SectionedDataSourceChangesetConvertible, Element.Changeset.Collection: QueryableSectionedDataSourceProtocol, Error == Never { + /// Binds the signal of data source elements to the given table view. + /// + /// - parameters: + /// - tableView: A table view that should display the data from the data source. + /// - cellType: A type of the cells that should display the data. + /// - animated: Animate partial or batched updates. Default is `true`. + /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. + /// - configureCell: A closure that configures the cell with the data source item at the respective index path. + /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. + /// + /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. + /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. + @discardableResult + public func bind(to tableView: UITableView, cellType: Cell.Type, using binderDataSource: TableViewBinderDataSource, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { + let identifier = String(describing: Cell.self) - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - cellType: A type of the cells that should display the data. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to tableView: UITableView, cellType: Cell.Type, animated: Bool = true, rowAnimation: UITableView.RowAnimation = .automatic, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifier = String(describing: Cell.self) - let bundle = Bundle(for: Cell.self) - if let _ = bundle.path(forResource: identifier, ofType: "nib") { - let nib = UINib(nibName: identifier, bundle: bundle) - tableView.register(nib, forCellReuseIdentifier: identifier) - } else { - tableView.register(cellType as AnyClass, forCellReuseIdentifier: identifier) - } - return bind(to: tableView, animated: animated, rowAnimation: rowAnimation, createCell: { (dataSource, indexPath, tableView) -> UITableViewCell in - guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { - fatalError( - "Failed to dequeue a cell with identifier \(identifier) matching type \(cellType.self). " - + "Check that the reuseIdentifier is set properly if using XIBs or Storyboards." - ) + let bundle = Bundle(for: Cell.self) + if let _ = bundle.path(forResource: identifier, ofType: "nib") { + let nib = UINib(nibName: identifier, bundle: bundle) + tableView.register(nib, forCellReuseIdentifier: identifier) + } else { + tableView.register(cellType as AnyClass, forCellReuseIdentifier: identifier) } - let item = dataSource.item(at: indexPath) - configureCell(cell, item) - return cell - }) - } - /// Binds the signal of data source elements to the given table view. - /// - /// - parameters: - /// - tableView: A table view that should display the data from the data source. - /// - cellType: A type of the cells that should display the data. - /// - animated: Animate partial or batched updates. Default is `true`. - /// - rowAnimation: Row animation for partial or batched updates. Relevant only when `animated` is `true`. Default is `.automatic`. - /// - configureCell: A closure that configures the cell with the data source item at the respective index path. - /// - returns: A disposable object that can terminate the binding. Safe to ignore - the binding will be automatically terminated when the table view is deallocated. - /// - /// Note that the cell type name will be used as a reusable identifier and the binding will automatically register and dequeue the cell. - /// If there exists a nib file in the bundle with the same name as the cell type name, the framework will load the cell from the nib file. - @discardableResult - public func bind(to tableView: UITableView, cellType: Cell.Type, using binderDataSource: TableViewBinderDataSource, configureCell: @escaping (Cell, Element.Changeset.Collection.Item) -> Void) -> Disposable { - let identifier = String(describing: Cell.self) - - let bundle = Bundle(for: Cell.self) - if let _ = bundle.path(forResource: identifier, ofType: "nib") { - let nib = UINib(nibName: identifier, bundle: bundle) - tableView.register(nib, forCellReuseIdentifier: identifier) - } else { - tableView.register(cellType as AnyClass, forCellReuseIdentifier: identifier) - } - - binderDataSource.createCell = { (dataSource, indexPath, tableView) -> UITableViewCell in - guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { - fatalError( - "Failed to dequeue a cell with identifier \(identifier) matching type \(cellType.self). " - + "Check that the reuseIdentifier is set properly if using XIBs or Storyboards." - ) + binderDataSource.createCell = { (dataSource, indexPath, tableView) -> UITableViewCell in + guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? Cell else { + fatalError( + "Failed to dequeue a cell with identifier \(identifier) matching type \(cellType.self). " + + "Check that the reuseIdentifier is set properly if using XIBs or Storyboards." + ) + } + let item = dataSource.item(at: indexPath) + configureCell(cell, item) + return cell } - let item = dataSource.item(at: indexPath) - configureCell(cell, item) - return cell + return bind(to: tableView, using: binderDataSource) } - return bind(to: tableView, using: binderDataSource) } -} -private var TableViewBinderDataSourceAssociationKey = "TableViewBinderDataSource" + private var TableViewBinderDataSourceAssociationKey = "TableViewBinderDataSource" -open class TableViewBinderDataSource: NSObject, UITableViewDataSource { + open class TableViewBinderDataSource: NSObject, UITableViewDataSource { + public var createCell: ((Changeset.Collection, IndexPath, UITableView) -> UITableViewCell)? - public var createCell: ((Changeset.Collection, IndexPath, UITableView) -> UITableViewCell)? - - public var changeset: Changeset? = nil { - didSet { - if let changeset = changeset, oldValue != nil { - applyChangeset(changeset) - } else { - tableView?.reloadData() + public var changeset: Changeset? { + didSet { + if let changeset = changeset, oldValue != nil { + applyChangeset(changeset) + } else { + tableView?.reloadData() + } } } - } - open weak var tableView: UITableView? = nil { - didSet { - guard let tableView = tableView else { return } - associateWithTableView(tableView) + open weak var tableView: UITableView? { + didSet { + guard let tableView = tableView else { return } + associateWithTableView(tableView) + } } - } - open var rowInsertionAnimation: UITableView.RowAnimation = .automatic - open var rowDeletionAnimation: UITableView.RowAnimation = .automatic - open var rowReloadAnimation: UITableView.RowAnimation = .automatic + open var rowInsertionAnimation: UITableView.RowAnimation = .automatic + open var rowDeletionAnimation: UITableView.RowAnimation = .automatic + open var rowReloadAnimation: UITableView.RowAnimation = .automatic - public override init() { - createCell = nil - } - - /// - parameter createCell: A closure that creates cell for a given table view and configures it with the given data source at the given index path. - public init(_ createCell: @escaping (Changeset.Collection, IndexPath, UITableView) -> UITableViewCell) { - self.createCell = createCell - } - - open func numberOfSections(in tableView: UITableView) -> Int { - return changeset?.collection.numberOfSections ?? 0 - } - - open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return changeset?.collection.numberOfItems(inSection: section) ?? 0 - } - - open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let changeset = changeset else { fatalError() } - if let createCell = createCell { - return createCell(changeset.collection, indexPath, tableView) - } else { - fatalError("Subclass of TableViewBinderDataSource should override and implement tableView(_:cellForRowAt:) method if they do not initialize `createCell` closure.") + public override init() { + createCell = nil } - } - open func applyChangeset(_ changeset: Changeset) { - guard let tableView = tableView else { return } - let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } - if diff.isEmpty { - tableView.reloadData() - } else if diff.count == 1 { - applyChangesetDiff(diff) - } else { - tableView.beginUpdates() - applyChangesetDiff(diff) - tableView.endUpdates() + /// - parameter createCell: A closure that creates cell for a given table view and configures it with the given data source at the given index path. + public init(_ createCell: @escaping (Changeset.Collection, IndexPath, UITableView) -> UITableViewCell) { + self.createCell = createCell } - } - open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { - guard let tableView = tableView else { return } - let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } - if !insertedSections.isEmpty { - tableView.insertSections(IndexSet(insertedSections), with: rowInsertionAnimation) - } - let insertedItems = diff.inserts.filter { $0.count == 2 } - if !insertedItems.isEmpty { - tableView.insertRows(at: insertedItems, with: rowInsertionAnimation) + open func numberOfSections(in _: UITableView) -> Int { + return changeset?.collection.numberOfSections ?? 0 } - let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } - if !deletedSections.isEmpty { - tableView.deleteSections(IndexSet(deletedSections), with: rowDeletionAnimation) - } - let deletedItems = diff.deletes.filter { $0.count == 2 } - if !deletedItems.isEmpty { - tableView.deleteRows(at: deletedItems, with: rowDeletionAnimation) + + open func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { + return changeset?.collection.numberOfItems(inSection: section) ?? 0 } - let updatedItems = diff.updates.filter { $0.count == 2 } - if !updatedItems.isEmpty { - tableView.reloadRows(at: updatedItems, with: rowReloadAnimation) + + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let changeset = changeset else { fatalError() } + if let createCell = createCell { + return createCell(changeset.collection, indexPath, tableView) + } else { + fatalError("Subclass of TableViewBinderDataSource should override and implement tableView(_:cellForRowAt:) method if they do not initialize `createCell` closure.") + } } - let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } - if !updatedSections.isEmpty { - tableView.reloadSections(IndexSet(updatedSections), with: rowReloadAnimation) + + open func applyChangeset(_ changeset: Changeset) { + guard let tableView = tableView else { return } + let diff = changeset.diff.asOrderedCollectionDiff.map { $0.asSectionDataIndexPath } + if diff.isEmpty { + tableView.reloadData() + } else if diff.count == 1 { + applyChangesetDiff(diff) + } else { + tableView.beginUpdates() + applyChangesetDiff(diff) + tableView.endUpdates() + } } - for move in diff.moves { - if move.from.count == 2 && move.to.count == 2 { - tableView.moveRow(at: move.from, to: move.to) - } else if move.from.count == 1 && move.to.count == 1 { - tableView.moveSection(move.from[0], toSection: move.to[0]) + + open func applyChangesetDiff(_ diff: OrderedCollectionDiff) { + guard let tableView = tableView else { return } + let insertedSections = diff.inserts.filter { $0.count == 1 }.map { $0[0] } + if !insertedSections.isEmpty { + tableView.insertSections(IndexSet(insertedSections), with: rowInsertionAnimation) + } + let insertedItems = diff.inserts.filter { $0.count == 2 } + if !insertedItems.isEmpty { + tableView.insertRows(at: insertedItems, with: rowInsertionAnimation) + } + let deletedSections = diff.deletes.filter { $0.count == 1 }.map { $0[0] } + if !deletedSections.isEmpty { + tableView.deleteSections(IndexSet(deletedSections), with: rowDeletionAnimation) + } + let deletedItems = diff.deletes.filter { $0.count == 2 } + if !deletedItems.isEmpty { + tableView.deleteRows(at: deletedItems, with: rowDeletionAnimation) + } + let updatedItems = diff.updates.filter { $0.count == 2 } + if !updatedItems.isEmpty { + tableView.reloadRows(at: updatedItems, with: rowReloadAnimation) + } + let updatedSections = diff.updates.filter { $0.count == 1 }.map { $0[0] } + if !updatedSections.isEmpty { + tableView.reloadSections(IndexSet(updatedSections), with: rowReloadAnimation) + } + for move in diff.moves { + if move.from.count == 2, move.to.count == 2 { + tableView.moveRow(at: move.from, to: move.to) + } else if move.from.count == 1, move.to.count == 1 { + tableView.moveSection(move.from[0], toSection: move.to[0]) + } } } - } - private func associateWithTableView(_ tableView: UITableView) { - objc_setAssociatedObject(tableView, &TableViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - if tableView.reactive.hasProtocolProxy(for: UITableViewDataSource.self) { - tableView.reactive.dataSource.forwardTo = self - } else { - tableView.dataSource = self + private func associateWithTableView(_ tableView: UITableView) { + objc_setAssociatedObject(tableView, &TableViewBinderDataSourceAssociationKey, self, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if tableView.reactive.hasProtocolProxy(for: UITableViewDataSource.self) { + tableView.reactive.dataSource.forwardTo = self + } else { + tableView.dataSource = self + } } } -} - -extension TableViewBinderDataSource { - public class ReloadingBinder: TableViewBinderDataSource { - - public override func applyChangeset(_ changeset: Changeset) { - tableView?.reloadData() + extension TableViewBinderDataSource { + public class ReloadingBinder: TableViewBinderDataSource { + public override func applyChangeset(_: Changeset) { + tableView?.reloadData() + } } } -} #endif diff --git a/Sources/Bond/UIKit/UITableView.swift b/Sources/Bond/UIKit/UITableView.swift index 4b89232c..680401ce 100644 --- a/Sources/Bond/UIKit/UITableView.swift +++ b/Sources/Bond/UIKit/UITableView.swift @@ -24,37 +24,36 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension ReactiveExtensions where Base: UITableView { - - /// A `ProtocolProxy` for the table view delegate. - /// - /// - Note: Accessing this property for the first time will replace table view's current delegate - /// with a protocol proxy object (an object that is stored in this property). - /// Current delegate will be used as `forwardTo` delegate of protocol proxy. - public var delegate: ProtocolProxy { - return protocolProxy(for: UITableViewDelegate.self, keyPath: \.delegate) - } + extension ReactiveExtensions where Base: UITableView { + /// A `ProtocolProxy` for the table view delegate. + /// + /// - Note: Accessing this property for the first time will replace table view's current delegate + /// with a protocol proxy object (an object that is stored in this property). + /// Current delegate will be used as `forwardTo` delegate of protocol proxy. + public var delegate: ProtocolProxy { + return protocolProxy(for: UITableViewDelegate.self, keyPath: \.delegate) + } - /// A `ProtocolProxy` for the table view data source. - /// - /// - Note: Accessing this property for the first time will replace table view's current data source - /// with a protocol proxy object (an object that is stored in this property). - /// Current data source will be used as `forwardTo` data source of protocol proxy. - public var dataSource: ProtocolProxy { - return protocolProxy(for: UITableViewDataSource.self, keyPath: \.dataSource) - } + /// A `ProtocolProxy` for the table view data source. + /// + /// - Note: Accessing this property for the first time will replace table view's current data source + /// with a protocol proxy object (an object that is stored in this property). + /// Current data source will be used as `forwardTo` data source of protocol proxy. + public var dataSource: ProtocolProxy { + return protocolProxy(for: UITableViewDataSource.self, keyPath: \.dataSource) + } - /// A signal that emits index paths of selected table view cells. - /// - /// - Note: Uses table view's `delegate` protocol proxy to observe calls made to `UITableViewDelegate.tableView(_:didSelectRowAt:)` method. - public var selectedRowIndexPath: SafeSignal { - return delegate.signal(for: #selector(UITableViewDelegate.tableView(_:didSelectRowAt:))) { (subject: PassthroughSubject, _: UITableView, indexPath: IndexPath) in - subject.send(indexPath) + /// A signal that emits index paths of selected table view cells. + /// + /// - Note: Uses table view's `delegate` protocol proxy to observe calls made to `UITableViewDelegate.tableView(_:didSelectRowAt:)` method. + public var selectedRowIndexPath: SafeSignal { + return delegate.signal(for: #selector(UITableViewDelegate.tableView(_:didSelectRowAt:))) { (subject: PassthroughSubject, _: UITableView, indexPath: IndexPath) in + subject.send(indexPath) + } } } -} #endif diff --git a/Sources/Bond/UIKit/UITextField.swift b/Sources/Bond/UIKit/UITextField.swift index 260049c5..d9b60da2 100644 --- a/Sources/Bond/UIKit/UITextField.swift +++ b/Sources/Bond/UIKit/UITextField.swift @@ -24,37 +24,35 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit - -extension ReactiveExtensions where Base: UITextField { - - public var text: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.allEditingEvents).eraseType(), - get: { $0.text }, - set: { $0.text = $1 } - ) - } - - public var attributedText: DynamicSubject { - return dynamicSubject( - signal: controlEvents(.allEditingEvents).eraseType(), - get: { $0.attributedText }, - set: { $0.attributedText = $1 } - ) + import ReactiveKit + import UIKit + + extension ReactiveExtensions where Base: UITextField { + public var text: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.allEditingEvents).eraseType(), + get: { $0.text }, + set: { $0.text = $1 } + ) + } + + public var attributedText: DynamicSubject { + return dynamicSubject( + signal: controlEvents(.allEditingEvents).eraseType(), + get: { $0.attributedText }, + set: { $0.attributedText = $1 } + ) + } + + public var textColor: Bond { + return bond { $0.textColor = $1 } + } } - public var textColor: Bond { - return bond { $0.textColor = $1 } - } -} - -extension UITextField: BindableProtocol { - - public func bind(signal: Signal) -> Disposable { - return reactive.text.bind(signal: signal) + extension UITextField: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.text.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UITextView.swift b/Sources/Bond/UIKit/UITextView.swift index 17172eed..36722683 100644 --- a/Sources/Bond/UIKit/UITextView.swift +++ b/Sources/Bond/UIKit/UITextView.swift @@ -24,39 +24,37 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit - -extension ReactiveExtensions where Base: UITextView { - - public var text: DynamicSubject { - let notificationName = UITextView.textDidChangeNotification - return dynamicSubject( - signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), - get: { $0.text }, - set: { $0.text = $1 } - ) - } - - public var attributedText: DynamicSubject { - let notificationName = UITextView.textDidChangeNotification - return dynamicSubject( - signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), - get: { $0.attributedText }, - set: { $0.attributedText = $1 } - ) + import ReactiveKit + import UIKit + + extension ReactiveExtensions where Base: UITextView { + public var text: DynamicSubject { + let notificationName = UITextView.textDidChangeNotification + return dynamicSubject( + signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), + get: { $0.text }, + set: { $0.text = $1 } + ) + } + + public var attributedText: DynamicSubject { + let notificationName = UITextView.textDidChangeNotification + return dynamicSubject( + signal: NotificationCenter.default.reactive.notification(name: notificationName, object: base).eraseType(), + get: { $0.attributedText }, + set: { $0.attributedText = $1 } + ) + } + + public var textColor: Bond { + return bond { $0.textColor = $1 } + } } - public var textColor: Bond { - return bond { $0.textColor = $1 } - } -} - -extension UITextView: BindableProtocol { - - public func bind(signal: Signal) -> Disposable { - return reactive.text.bind(signal: signal) + extension UITextView: BindableProtocol { + public func bind(signal: Signal) -> Disposable { + return reactive.text.bind(signal: signal) + } } -} #endif diff --git a/Sources/Bond/UIKit/UIView.swift b/Sources/Bond/UIKit/UIView.swift index 9bbcc6fc..78fc3c8e 100644 --- a/Sources/Bond/UIKit/UIView.swift +++ b/Sources/Bond/UIKit/UIView.swift @@ -24,34 +24,33 @@ #if os(iOS) || os(tvOS) -import UIKit -import ReactiveKit + import ReactiveKit + import UIKit -extension UIResponder: BindingExecutionContextProvider { - public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } -} - -extension ReactiveExtensions where Base: UIView { - - public var alpha: Bond { - return bond { $0.alpha = $1 } + extension UIResponder: BindingExecutionContextProvider { + public var bindingExecutionContext: ExecutionContext { return .immediateOnMain } } - public var backgroundColor: Bond { - return bond { $0.backgroundColor = $1 } - } + extension ReactiveExtensions where Base: UIView { + public var alpha: Bond { + return bond { $0.alpha = $1 } + } - public var isHidden: Bond { - return bond { $0.isHidden = $1 } - } + public var backgroundColor: Bond { + return bond { $0.backgroundColor = $1 } + } - public var isUserInteractionEnabled: Bond { - return bond { $0.isUserInteractionEnabled = $1 } - } + public var isHidden: Bond { + return bond { $0.isHidden = $1 } + } + + public var isUserInteractionEnabled: Bond { + return bond { $0.isUserInteractionEnabled = $1 } + } - public var tintColor: Bond { - return bond { $0.tintColor = $1 } + public var tintColor: Bond { + return bond { $0.tintColor = $1 } + } } -} #endif diff --git a/Tests/BondTests/BondTests.swift b/Tests/BondTests/BondTests.swift index bce11778..b8071418 100644 --- a/Tests/BondTests/BondTests.swift +++ b/Tests/BondTests/BondTests.swift @@ -6,46 +6,45 @@ // Copyright © 2016 Swift Bond. All rights reserved. // -import XCTest -import ReactiveKit @testable import Bond +import ReactiveKit +import XCTest private class DummyTarget: NSObject { var recordedElements: [Int] = [] } class BondTypeTests: XCTestCase { - // Update closure is called on each next element func testExecutes() { let target = DummyTarget() let bond = Bond(target: target, context: .immediate) { target, element in target.recordedElements.append(element) } - + SafeSignal(sequence: [1, 2, 3]).bind(to: bond) XCTAssert(target.recordedElements == [1, 2, 3]) } - + // Target is weakly referenced // Disposable is disposed when target is deallocated func testDisposesOnTargetDeallocation() { var target: DummyTarget! = DummyTarget() weak var weakTarget = target - + let bond = Bond(target: target, context: .immediate) { target, element in target.recordedElements.append(element) } - + let subject = PassthroughSubject() - + let disposable = subject.bind(to: bond) - + subject.send(1) XCTAssert(weakTarget != nil) XCTAssert(disposable.isDisposed == false) XCTAssert(target.recordedElements == [1]) - + target = nil subject.send(2) XCTAssert(weakTarget == nil) diff --git a/Tests/BondTests/CollectionChangesetDiffAndPatchTest.swift b/Tests/BondTests/CollectionChangesetDiffAndPatchTest.swift index d7b1ce91..578f364d 100644 --- a/Tests/BondTests/CollectionChangesetDiffAndPatchTest.swift +++ b/Tests/BondTests/CollectionChangesetDiffAndPatchTest.swift @@ -6,29 +6,28 @@ // Copyright © 2018 Swift Bond. All rights reserved. // -import XCTest @testable import Bond +import XCTest extension OrderedCollectionOperation where Index == Int, Element == Int { - static func randomOperation(collectionSize: Int) -> OrderedCollectionOperation { - let element = Int.random(in: 11..<100) + let element = Int.random(in: 11 ..< 100) guard collectionSize > 1 else { return .insert(element, at: 0) } switch [0, 1, 2, 3].randomElement() { case 0: - let at = Int.random(in: 0..<(collectionSize + 1)) + let at = Int.random(in: 0 ..< (collectionSize + 1)) return .insert(element, at: at) case 1: - let at = Int.random(in: 0...Operation] = [] - for _ in 0...randomOperation(collectionSize: collection.count) collection.apply(operation) operations.append(operation) diff --git a/Tests/BondTests/DynamicSubjectTests.swift b/Tests/BondTests/DynamicSubjectTests.swift index 86f67686..125a9b80 100644 --- a/Tests/BondTests/DynamicSubjectTests.swift +++ b/Tests/BondTests/DynamicSubjectTests.swift @@ -6,9 +6,9 @@ // Copyright © 2016 Swift Bond. All rights reserved. // -import XCTest -import ReactiveKit @testable import Bond +import ReactiveKit +import XCTest private class DummyTarget: NSObject { var value: Int = 5 @@ -17,7 +17,6 @@ private class DummyTarget: NSObject { } class DynamicSubjectTests: XCTestCase { - // Starts with current value // Update signal triggers next event // Bound signal events are propagated @@ -29,7 +28,7 @@ class DynamicSubjectTests: XCTestCase { signal: target.changes.toSignal(), context: .immediate, get: { (target) -> Int in target.value }, - set: { (target, new) in target.value = new; target.recordedElements.append(new) } + set: { target, new in target.value = new; target.recordedElements.append(new) } ) subject.expectNext([5, 6, 7, 1, 2, 3]) @@ -51,13 +50,13 @@ class DynamicSubjectTests: XCTestCase { func testDisposesOnTargetDeallocation() { var target: DummyTarget! = DummyTarget() weak var weakTarget = target - + let subject = DynamicSubject( target: target, signal: target.changes.toSignal(), context: .immediate, get: { (target) -> Int in target.value }, - set: { (target, new) in target.value = new; target.recordedElements.append(new) } + set: { target, new in target.value = new; target.recordedElements.append(new) } ) let signal = PassthroughSubject() diff --git a/Tests/BondTests/Helpers.swift b/Tests/BondTests/Helpers.swift index c06b4dc2..d99c2226 100644 --- a/Tests/BondTests/Helpers.swift +++ b/Tests/BondTests/Helpers.swift @@ -6,23 +6,22 @@ // Copyright © 2016 Swift Bond. All rights reserved. // -import XCTest -import ReactiveKit @testable import Bond +import ReactiveKit +import XCTest func XCTAssertEqual(_ lhs: CGFloat, _ rhs: CGFloat, precision: CGFloat = 0.01, file: StaticString = #file, line: UInt = #line) { XCTAssert(abs(lhs - rhs) < precision, file: file, line: line) } -extension Signal.Event { +extension Signal.Event { func isEqualTo(_ event: Signal.Event) -> Bool { - switch (self, event) { case (.completed, .completed): return true case (.failed, .failed): return true - case (.next(let left), .next(let right)): + case let (.next(left), .next(right)): if let left = left as? Int, let right = right as? Int { return left == right } else if let left = left as? Bool, let right = right as? Bool { @@ -41,7 +40,7 @@ extension Signal.Event { return left == right } else if let left = left as? [String], let right = right as? [String] { return left == right - } else if let left = asOptional(left) as? Optional, let right = asOptional(right) as? Optional { + } else if let left = asOptional(left) as? String?, let right = asOptional(right) as? String? { return left == right } else if left is Void, right is Void { return true @@ -66,7 +65,6 @@ private func asOptional(_ object: Any) -> Any? { } extension SignalProtocol { - func expectNext(_ expectedElements: [Element], _ message: @autoclosure () -> String = "", expectation: XCTestExpectation? = nil, @@ -81,7 +79,7 @@ extension SignalProtocol { var eventsToProcess = expectedEvents var receivedEvents: [Signal.Event] = [] let message = message() - let _ = observe { event in + _ = observe { event in receivedEvents.append(event) if eventsToProcess.count == 0 { XCTFail("Got more events then expected.") diff --git a/Tests/BondTests/NSObjectTests.swift b/Tests/BondTests/NSObjectTests.swift index 1764a56b..ee146036 100644 --- a/Tests/BondTests/NSObjectTests.swift +++ b/Tests/BondTests/NSObjectTests.swift @@ -11,10 +11,8 @@ import ReactiveKit import XCTest class NSObjectTests: XCTestCase { + class TestObject: NSObject {} - class TestObject: NSObject { - } - var object: TestObject! override func setUp() { @@ -40,9 +38,7 @@ class NSObjectTests: XCTestCase { } class NSObjectKVOTests: XCTestCase { - class TestObject: NSObject, BindingExecutionContextProvider { - @objc dynamic var property: Any! = "a" @objc dynamic var propertyString: String = "a" @@ -75,7 +71,7 @@ class NSObjectKVOTests: XCTestCase { } func testOptionalObservation() { - let subject = object.reactive.keyPath("property", ofType: Optional.self) + let subject = object.reactive.keyPath("property", ofType: String?.self) subject.expectNext(["a", "b", nil, "c"]) object.property = "b" object.property = nil @@ -83,7 +79,7 @@ class NSObjectKVOTests: XCTestCase { } func testOptionalBinding() { - let subject = object.reactive.keyPath("property", ofType: Optional.self) + let subject = object.reactive.keyPath("property", ofType: String?.self) subject.expectNext(["a", "b", nil, "c"]) SafeSignal(just: "b").bind(to: subject) XCTAssert((object.property as! String) == "b") @@ -116,7 +112,7 @@ class NSObjectKVOTests: XCTestCase { } func testExpectedTypeOptionalObservation() { - let subject = object.reactive.keyPath("property", ofExpectedType: Optional.self) + let subject = object.reactive.keyPath("property", ofExpectedType: String?.self) subject.expectNext(["a", "b", nil, "c"]) object.property = "b" object.property = nil @@ -124,7 +120,7 @@ class NSObjectKVOTests: XCTestCase { } func testExpectedTypeOptionalBinding() { - let subject = object.reactive.keyPath("property", ofExpectedType: Optional.self) + let subject = object.reactive.keyPath("property", ofExpectedType: String?.self) subject.expectNext(["a", "b", nil, "c"]) SafeSignal(just: "b").bind(to: subject) XCTAssert((object.property as! String) == "b") @@ -135,7 +131,7 @@ class NSObjectKVOTests: XCTestCase { } func testExpectedTypeOptionalFailure() { - let subject = object.reactive.keyPath("property", ofExpectedType: Optional.self) + let subject = object.reactive.keyPath("property", ofExpectedType: String?.self) subject.expect([.next("a"), .failed(.notConvertible(""))]) object.property = 5 } diff --git a/Tests/BondTests/ProtocolProxyTests.swift b/Tests/BondTests/ProtocolProxyTests.swift index 820663d0..d08f6ec5 100644 --- a/Tests/BondTests/ProtocolProxyTests.swift +++ b/Tests/BondTests/ProtocolProxyTests.swift @@ -12,9 +12,9 @@ import UIKit #endif -import XCTest -import ReactiveKit @testable import Bond +import ReactiveKit +import XCTest @objc protocol TestDelegate: NSObjectProtocol { func methodA() @@ -27,7 +27,7 @@ import ReactiveKit } class TestObject: NSObject { - @objc dynamic weak var delegate: TestDelegate! = nil + @objc dynamic weak var delegate: TestDelegate! override init() { super.init() @@ -63,8 +63,7 @@ class TestObject: NSObject { } class ProtocolProxyTests: XCTestCase { - - var object: TestObject! = nil + var object: TestObject! var protocolProxy: ProtocolProxy { return object.reactive.protocolProxy(for: TestDelegate.self, keyPath: \.delegate) diff --git a/Tests/BondTests/TreeChangesetDiffAndPatchTest.swift b/Tests/BondTests/TreeChangesetDiffAndPatchTest.swift index 378f6504..1830ad20 100644 --- a/Tests/BondTests/TreeChangesetDiffAndPatchTest.swift +++ b/Tests/BondTests/TreeChangesetDiffAndPatchTest.swift @@ -6,14 +6,12 @@ // Copyright © 2018 Swift Bond. All rights reserved. // - -import XCTest @testable import Bond +import XCTest extension OrderedCollectionOperation where Element == TreeNode, Index == IndexPath { - static func randomOperation(collection: TreeNode) -> OrderedCollectionOperation, IndexPath> { - let element = TreeNode(Int.random(in: 11..<100)) + let element = TreeNode(Int.random(in: 11 ..< 100)) let indices = collection.depthFirst.indices.dropFirst() guard indices.count > 1 else { return .insert(element, at: [0]) @@ -44,12 +42,11 @@ extension OrderedCollectionOperation where Element == TreeNode, Index == In } class TreeChangesetDiffAndPatchTest: XCTestCase { - let testTree = TreeNode(0, [TreeNode(1, [TreeNode(2), TreeNode(3, [TreeNode(4)])]), TreeNode(5)]) func testRandom() { measure { - for _ in 0..<1000 { + for _ in 0 ..< 1000 { oneRandomTest() } } @@ -60,7 +57,7 @@ class TreeChangesetDiffAndPatchTest: XCTestCase { var collection = initialCollection var operations: [TreeChangeset>.Operation] = [] - for _ in 0..>.Operation.randomOperation(collection: collection) collection.apply(operation) operations.append(operation) @@ -78,7 +75,7 @@ class TreeChangesetDiffAndPatchTest: XCTestCase { let diff = TreeChangeset>.Diff(from: operations) let patch = diff.generatePatch(to: collection) - + var testCollection = initialCollection for operation in patch { testCollection.apply(operation) diff --git a/Tests/BondTests/TreeViewTests.swift b/Tests/BondTests/TreeViewTests.swift index ca712ba0..f158087c 100644 --- a/Tests/BondTests/TreeViewTests.swift +++ b/Tests/BondTests/TreeViewTests.swift @@ -6,11 +6,10 @@ // Copyright © 2019 Swift Bond. All rights reserved. // -import XCTest import Bond +import XCTest class TreeViewTests: XCTestCase { - var tree: TreeNode! var largeTree: TreeNode! @@ -28,7 +27,7 @@ class TreeViewTests: XCTestCase { ]) ]) largeTree = tree - for _ in 0..<5 { + for _ in 0 ..< 5 { largeTree.append(largeTree) } } @@ -44,8 +43,8 @@ class TreeViewTests: XCTestCase { } func testSearchEfficiency() { - self.measure { - for _ in 0..<1000 { + measure { + for _ in 0 ..< 1000 { let test = largeTree.depthFirst.firstIndex(where: { $0.value == "010" }) XCTAssertEqual(test, [1, 0]) } diff --git a/Tests/BondTests/UICollectionViewTests.swift b/Tests/BondTests/UICollectionViewTests.swift index 600cd1ba..322cdf40 100644 --- a/Tests/BondTests/UICollectionViewTests.swift +++ b/Tests/BondTests/UICollectionViewTests.swift @@ -8,101 +8,99 @@ #if os(iOS) || os(tvOS) -import XCTest -import ReactiveKit -@testable import Bond + @testable import Bond + import ReactiveKit + import XCTest -class TestCollectionView: UICollectionView { + class TestCollectionView: UICollectionView { + var observedEvents: [OrderedCollectionDiff] = [] - var observedEvents: [OrderedCollectionDiff] = [] - - override func reloadData() { - super.reloadData() - observedEvents.append(OrderedCollectionDiff()) - } + override func reloadData() { + super.reloadData() + observedEvents.append(OrderedCollectionDiff()) + } - open override func insertSections(_ sections: IndexSet) { - super.insertSections(sections) - observedEvents.append(OrderedCollectionDiff(inserts: sections.map { [$0] })) - } + open override func insertSections(_ sections: IndexSet) { + super.insertSections(sections) + observedEvents.append(OrderedCollectionDiff(inserts: sections.map { [$0] })) + } - open override func deleteSections(_ sections: IndexSet) { - super.deleteSections(sections) - observedEvents.append(OrderedCollectionDiff(deletes: sections.map { [$0] })) - } + open override func deleteSections(_ sections: IndexSet) { + super.deleteSections(sections) + observedEvents.append(OrderedCollectionDiff(deletes: sections.map { [$0] })) + } - open override func reloadSections(_ sections: IndexSet) { - super.reloadSections(sections) - observedEvents.append(OrderedCollectionDiff(updates: sections.map { [$0] })) - } + open override func reloadSections(_ sections: IndexSet) { + super.reloadSections(sections) + observedEvents.append(OrderedCollectionDiff(updates: sections.map { [$0] })) + } - open override func moveSection(_ section: Int, toSection newSection: Int) { - super.moveSection(section, toSection: newSection) - observedEvents.append(OrderedCollectionDiff(moves: [(from: [section], to: [newSection])])) - } + open override func moveSection(_ section: Int, toSection newSection: Int) { + super.moveSection(section, toSection: newSection) + observedEvents.append(OrderedCollectionDiff(moves: [(from: [section], to: [newSection])])) + } - open override func insertItems(at indexPaths: [IndexPath]) { - super.insertItems(at: indexPaths) - observedEvents.append(OrderedCollectionDiff(inserts: indexPaths)) - } + open override func insertItems(at indexPaths: [IndexPath]) { + super.insertItems(at: indexPaths) + observedEvents.append(OrderedCollectionDiff(inserts: indexPaths)) + } - open override func deleteItems(at indexPaths: [IndexPath]) { - super.deleteItems(at: indexPaths) - observedEvents.append(OrderedCollectionDiff(deletes: indexPaths)) - } + open override func deleteItems(at indexPaths: [IndexPath]) { + super.deleteItems(at: indexPaths) + observedEvents.append(OrderedCollectionDiff(deletes: indexPaths)) + } - open override func reloadItems(at indexPaths: [IndexPath]) { - super.reloadItems(at: indexPaths) - observedEvents.append(OrderedCollectionDiff(updates: indexPaths)) - } + open override func reloadItems(at indexPaths: [IndexPath]) { + super.reloadItems(at: indexPaths) + observedEvents.append(OrderedCollectionDiff(updates: indexPaths)) + } - open override func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) { - super.moveItem(at: indexPath, to: newIndexPath) - observedEvents.append(OrderedCollectionDiff(moves: [(from: indexPath, to: newIndexPath)])) + open override func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) { + super.moveItem(at: indexPath, to: newIndexPath) + observedEvents.append(OrderedCollectionDiff(moves: [(from: indexPath, to: newIndexPath)])) + } } -} -class UICollectionViewTests: XCTestCase { + class UICollectionViewTests: XCTestCase { + var array: MutableObservableArray! + var collectionView: TestCollectionView! - var array: MutableObservableArray! - var collectionView: TestCollectionView! + override func setUp() { + array = MutableObservableArray([1, 2, 3]) + collectionView = TestCollectionView(frame: CGRect(x: 0, y: 0, width: 100, height: 1000), collectionViewLayout: UICollectionViewFlowLayout()) + array.bind(to: collectionView, cellType: UICollectionViewCell.self) { _, _ in } + } - override func setUp() { - array = MutableObservableArray([1, 2, 3]) - collectionView = TestCollectionView(frame: CGRect(x: 0, y: 0, width: 100, height: 1000), collectionViewLayout: UICollectionViewFlowLayout()) - array.bind(to: collectionView, cellType: UICollectionViewCell.self) { _, _ in } - } + func testInsertItems() { + array.insert(4, at: 1) + XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0)])]) + } - func testInsertItems() { - array.insert(4, at: 1) - XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0)])]) - } + func testDeleteItems() { + _ = array.remove(at: 2) + XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(deletes: [IndexPath(row: 2, section: 0)])]) + } - func testDeleteItems() { - let _ = array.remove(at: 2) - XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(deletes: [IndexPath(row: 2, section: 0)])]) - } + func testReloadItems() { + array[2] = 5 + XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(updates: [IndexPath(row: 2, section: 0)])]) + } - func testReloadItems() { - array[2] = 5 - XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(updates: [IndexPath(row: 2, section: 0)])]) - } + func testMoveRow() { + array.move(from: 1, to: 2) + XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(moves: [(from: IndexPath(row: 1, section: 0), to: IndexPath(row: 2, section: 0))])]) + } - func testMoveRow() { - array.move(from: 1, to: 2) - XCTAssert(collectionView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(moves: [(from: IndexPath(row: 1, section: 0), to: IndexPath(row: 2, section: 0))])]) - } + func testBatchUpdates() { + array.batchUpdate { array in + array.insert(0, at: 0) + array.insert(1, at: 0) + } - func testBatchUpdates() { - array.batchUpdate { (array) in - array.insert(0, at: 0) - array.insert(1, at: 0) + let possibleResultA = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0), IndexPath(row: 0, section: 0)])] + let possibleResultB = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 0, section: 0), IndexPath(row: 1, section: 0)])] + XCTAssert(collectionView.observedEvents == possibleResultA || collectionView.observedEvents == possibleResultB) } - - let possibleResultA = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0), IndexPath(row: 0, section: 0)])] - let possibleResultB = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 0, section: 0), IndexPath(row: 1, section: 0)])] - XCTAssert(collectionView.observedEvents == possibleResultA || collectionView.observedEvents == possibleResultB) } -} #endif diff --git a/Tests/BondTests/UIKitTests.swift b/Tests/BondTests/UIKitTests.swift index 042dd6ff..57f2a6d8 100644 --- a/Tests/BondTests/UIKitTests.swift +++ b/Tests/BondTests/UIKitTests.swift @@ -8,330 +8,328 @@ #if os(iOS) || os(tvOS) -import XCTest -import ReactiveKit -@testable import Bond + @testable import Bond + import ReactiveKit + import XCTest -class BondTests: XCTestCase { + class BondTests: XCTestCase { + func testUIView() { + let view = UIView() - func testUIView() { - let view = UIView() + SafeSignal(just: UIColor.red).bind(to: view.reactive.backgroundColor) + XCTAssertEqual(view.backgroundColor!, UIColor.red) - SafeSignal(just: UIColor.red).bind(to: view.reactive.backgroundColor) - XCTAssertEqual(view.backgroundColor!, UIColor.red) + SafeSignal(just: 0.3).bind(to: view.reactive.alpha) + XCTAssertEqual(view.alpha, 0.3) - SafeSignal(just: 0.3).bind(to: view.reactive.alpha) - XCTAssertEqual(view.alpha, 0.3) + SafeSignal(just: true).bind(to: view.reactive.isHidden) + XCTAssertEqual(view.isHidden, true) - SafeSignal(just: true).bind(to: view.reactive.isHidden) - XCTAssertEqual(view.isHidden, true) + SafeSignal(just: true).bind(to: view.reactive.isUserInteractionEnabled) + XCTAssertEqual(view.isUserInteractionEnabled, true) - SafeSignal(just: true).bind(to: view.reactive.isUserInteractionEnabled) - XCTAssertEqual(view.isUserInteractionEnabled, true) + SafeSignal(just: UIColor.red).bind(to: view.reactive.tintColor) + XCTAssertEqual(view.tintColor, UIColor.red) + } - SafeSignal(just: UIColor.red).bind(to: view.reactive.tintColor) - XCTAssertEqual(view.tintColor, UIColor.red) - } + func testUIActivityIndicatorView() { + let subject = PassthroughSubject() - func testUIActivityIndicatorView() { - let subject = PassthroughSubject() + let view = UIActivityIndicatorView() + subject.bind(to: view.reactive.isAnimating) - let view = UIActivityIndicatorView() - subject.bind(to: view.reactive.isAnimating) + XCTAssertEqual(view.isAnimating, false) - XCTAssertEqual(view.isAnimating, false) + subject.send(true) + XCTAssertEqual(view.isAnimating, true) - subject.send(true) - XCTAssertEqual(view.isAnimating, true) + subject.send(false) + XCTAssertEqual(view.isAnimating, false) + } - subject.send(false) - XCTAssertEqual(view.isAnimating, false) - } + func testUIBarItem() { + let view = UIBarButtonItem() - func testUIBarItem() { - let view = UIBarButtonItem() + SafeSignal(just: "test").bind(to: view.reactive.title) + XCTAssertEqual(view.title!, "test") - SafeSignal(just: "test").bind(to: view.reactive.title) - XCTAssertEqual(view.title!, "test") + let image = UIImage() + SafeSignal(just: image).bind(to: view.reactive.image) + XCTAssertEqual(view.image!, image) - let image = UIImage() - SafeSignal(just: image).bind(to: view.reactive.image) - XCTAssertEqual(view.image!, image) + SafeSignal(just: true).bind(to: view.reactive.isEnabled) + XCTAssertEqual(view.isEnabled, true) + SafeSignal(just: false).bind(to: view.reactive.isEnabled) + XCTAssertEqual(view.isEnabled, false) - SafeSignal(just: true).bind(to: view.reactive.isEnabled) - XCTAssertEqual(view.isEnabled, true) - SafeSignal(just: false).bind(to: view.reactive.isEnabled) - XCTAssertEqual(view.isEnabled, false) + view.reactive.tap.expectNext([(), ()]) + view.reactive.tap.expectNext([(), ()]) // second observer + _ = view.target!.perform(view.action!) + _ = view.target!.perform(view.action!) + } - view.reactive.tap.expectNext([(), ()]) - view.reactive.tap.expectNext([(), ()]) // second observer - _ = view.target!.perform(view.action!) - _ = view.target!.perform(view.action!) - } + func testUIButton() { + let view = UIButton() - func testUIButton() { - let view = UIButton() + SafeSignal(just: "test").bind(to: view.reactive.title) + XCTAssertEqual(view.titleLabel?.text, "test") - SafeSignal(just: "test").bind(to: view.reactive.title) - XCTAssertEqual(view.titleLabel?.text, "test") + SafeSignal(just: true).bind(to: view.reactive.isSelected) + XCTAssertEqual(view.isSelected, true) + SafeSignal(just: false).bind(to: view.reactive.isSelected) + XCTAssertEqual(view.isSelected, false) - SafeSignal(just: true).bind(to: view.reactive.isSelected) - XCTAssertEqual(view.isSelected, true) - SafeSignal(just: false).bind(to: view.reactive.isSelected) - XCTAssertEqual(view.isSelected, false) + SafeSignal(just: true).bind(to: view.reactive.isHighlighted) + XCTAssertEqual(view.isHighlighted, true) + SafeSignal(just: false).bind(to: view.reactive.isHighlighted) + XCTAssertEqual(view.isHighlighted, false) - SafeSignal(just: true).bind(to: view.reactive.isHighlighted) - XCTAssertEqual(view.isHighlighted, true) - SafeSignal(just: false).bind(to: view.reactive.isHighlighted) - XCTAssertEqual(view.isHighlighted, false) + let image = UIImage() + let image2 = UIImage() - let image = UIImage() - let image2 = UIImage() + SafeSignal(just: image).bind(to: view.reactive.backgroundImage) + XCTAssertEqual(view.backgroundImage(for: .normal), image) - SafeSignal(just: image).bind(to: view.reactive.backgroundImage) - XCTAssertEqual(view.backgroundImage(for: .normal), image) + SafeSignal(just: image2).bind(to: view.reactive.image) + XCTAssertEqual(view.image(for: .normal), image2) - SafeSignal(just: image2).bind(to: view.reactive.image) - XCTAssertEqual(view.image(for: .normal), image2) + view.reactive.tap.expectNext([(), ()]) + view.sendActions(for: .touchUpInside) + view.sendActions(for: .touchUpInside) + view.sendActions(for: .touchUpOutside) + } - view.reactive.tap.expectNext([(), ()]) - view.sendActions(for: .touchUpInside) - view.sendActions(for: .touchUpInside) - view.sendActions(for: .touchUpOutside) - } + func testUIControl() { + let view = UIControl() - func testUIControl() { - let view = UIControl() + SafeSignal(just: true).bind(to: view.reactive.isEnabled) + XCTAssertEqual(view.isEnabled, true) + SafeSignal(just: false).bind(to: view.reactive.isEnabled) + XCTAssertEqual(view.isEnabled, false) - SafeSignal(just: true).bind(to: view.reactive.isEnabled) - XCTAssertEqual(view.isEnabled, true) - SafeSignal(just: false).bind(to: view.reactive.isEnabled) - XCTAssertEqual(view.isEnabled, false) + view.reactive.controlEvents(UIControl.Event.touchUpInside).expectNext([(), ()]) + view.sendActions(for: .touchUpInside) + view.sendActions(for: .touchUpOutside) + view.sendActions(for: .touchUpInside) + } - view.reactive.controlEvents(UIControl.Event.touchUpInside).expectNext([(), ()]) - view.sendActions(for: .touchUpInside) - view.sendActions(for: .touchUpOutside) - view.sendActions(for: .touchUpInside) - } + func testUIDatePicker() { + let date1 = Date(timeIntervalSince1970: 10) + let date2 = Date(timeIntervalSince1970: 1000) - func testUIDatePicker() { - let date1 = Date(timeIntervalSince1970: 10) - let date2 = Date(timeIntervalSince1970: 1000) + let subject = PassthroughSubject() - let subject = PassthroughSubject() + let view = UIDatePicker() + subject.bind(to: view) - let view = UIDatePicker() - subject.bind(to: view) + subject.send(date1) + XCTAssertEqual(view.date, date1) - subject.send(date1) - XCTAssertEqual(view.date, date1) + subject.send(date2) + XCTAssertEqual(view.date, date2) - subject.send(date2) - XCTAssertEqual(view.date, date2) + view.reactive.date.expectNext([date2, date1]) + view.date = date1 + view.sendActions(for: .valueChanged) + } - view.reactive.date.expectNext([date2, date1]) - view.date = date1 - view.sendActions(for: .valueChanged) - } + func testUIImageView() { + let image1 = UIImage() + let image2 = UIImage() - func testUIImageView() { - let image1 = UIImage() - let image2 = UIImage() + let subject = PassthroughSubject() - let subject = PassthroughSubject() + let view = UIImageView() + subject.bind(to: view) - let view = UIImageView() - subject.bind(to: view) + subject.send(image1) + XCTAssertEqual(view.image!, image1) - subject.send(image1) - XCTAssertEqual(view.image!, image1) + subject.send(image2) + XCTAssertEqual(view.image!, image2) - subject.send(image2) - XCTAssertEqual(view.image!, image2) + subject.send(nil) + XCTAssertEqual(view.image, nil) + } - subject.send(nil) - XCTAssertEqual(view.image, nil) - } + func testUILabel() { + let subject = PassthroughSubject() - func testUILabel() { - let subject = PassthroughSubject() + let view = UILabel() + subject.bind(to: view) - let view = UILabel() - subject.bind(to: view) + subject.send("a") + XCTAssertEqual(view.text!, "a") - subject.send("a") - XCTAssertEqual(view.text!, "a") + subject.send("b") + XCTAssertEqual(view.text!, "b") - subject.send("b") - XCTAssertEqual(view.text!, "b") + subject.send(nil) + XCTAssertEqual(view.text, nil) + } - subject.send(nil) - XCTAssertEqual(view.text, nil) - } + func testUINavigationBar() { + let subject = PassthroughSubject() - func testUINavigationBar() { - let subject = PassthroughSubject() + let view = UINavigationBar() + subject.bind(to: view.reactive.barTintColor) - let view = UINavigationBar() - subject.bind(to: view.reactive.barTintColor) + subject.send(.red) + XCTAssertEqual(view.barTintColor!, .red) - subject.send(.red) - XCTAssertEqual(view.barTintColor!, .red) + subject.send(.blue) + XCTAssertEqual(view.barTintColor!, .blue) - subject.send(.blue) - XCTAssertEqual(view.barTintColor!, .blue) + subject.send(nil) + XCTAssertEqual(view.barTintColor, nil) + } - subject.send(nil) - XCTAssertEqual(view.barTintColor, nil) - } + func testUINavigationItem() { + let subject = PassthroughSubject() + let view = UINavigationItem() + subject.bind(to: view.reactive.title) - func testUINavigationItem() { - let subject = PassthroughSubject() + subject.send("a") + XCTAssertEqual(view.title!, "a") - let view = UINavigationItem() - subject.bind(to: view.reactive.title) + subject.send("b") + XCTAssertEqual(view.title!, "b") - subject.send("a") - XCTAssertEqual(view.title!, "a") + subject.send(nil) + XCTAssertEqual(view.title, nil) + } - subject.send("b") - XCTAssertEqual(view.title!, "b") + func testUIProgressView() { + let subject = PassthroughSubject() - subject.send(nil) - XCTAssertEqual(view.title, nil) - } + let view = UIProgressView() + subject.bind(to: view) - func testUIProgressView() { - let subject = PassthroughSubject() + subject.send(0.2) + XCTAssertEqual(view.progress, 0.2) - let view = UIProgressView() - subject.bind(to: view) + subject.send(0.4) + XCTAssertEqual(view.progress, 0.4) + } - subject.send(0.2) - XCTAssertEqual(view.progress, 0.2) + func testUIRefreshControl() { + let subject = PassthroughSubject() - subject.send(0.4) - XCTAssertEqual(view.progress, 0.4) - } + let view = UIRefreshControl() + subject.bind(to: view) - func testUIRefreshControl() { - let subject = PassthroughSubject() + subject.send(true) + XCTAssertEqual(view.isRefreshing, true) - let view = UIRefreshControl() - subject.bind(to: view) + subject.send(false) + XCTAssertEqual(view.isRefreshing, false) - subject.send(true) - XCTAssertEqual(view.isRefreshing, true) + view.reactive.refreshing.expectNext([false, true]) + view.beginRefreshing() + view.sendActions(for: .valueChanged) + } - subject.send(false) - XCTAssertEqual(view.isRefreshing, false) + func testUISegmentedControl() { + let subject = PassthroughSubject() - view.reactive.refreshing.expectNext([false, true]) - view.beginRefreshing() - view.sendActions(for: .valueChanged) - } + let view = UISegmentedControl(items: ["a", "b"]) + subject.bind(to: view) - func testUISegmentedControl() { - let subject = PassthroughSubject() + subject.send(1) + XCTAssertEqual(view.selectedSegmentIndex, 1) - let view = UISegmentedControl(items: ["a", "b"]) - subject.bind(to: view) + subject.send(0) + XCTAssertEqual(view.selectedSegmentIndex, 0) - subject.send(1) - XCTAssertEqual(view.selectedSegmentIndex, 1) + view.reactive.selectedSegmentIndex.expectNext([0, 1]) + view.selectedSegmentIndex = 1 + view.sendActions(for: .valueChanged) + } - subject.send(0) - XCTAssertEqual(view.selectedSegmentIndex, 0) + func testUISlider() { + let subject = PassthroughSubject() - view.reactive.selectedSegmentIndex.expectNext([0, 1]) - view.selectedSegmentIndex = 1 - view.sendActions(for: .valueChanged) - } + let view = UISlider() + subject.bind(to: view) - func testUISlider() { - let subject = PassthroughSubject() + subject.send(0.2) + XCTAssertEqual(view.value, 0.2) - let view = UISlider() - subject.bind(to: view) + subject.send(0.4) + XCTAssertEqual(view.value, 0.4) - subject.send(0.2) - XCTAssertEqual(view.value, 0.2) + view.reactive.value.expectNext([0.4, 0.6]) + view.value = 0.6 + view.sendActions(for: .valueChanged) + } - subject.send(0.4) - XCTAssertEqual(view.value, 0.4) + func testUISwitch() { + let subject = PassthroughSubject() - view.reactive.value.expectNext([0.4, 0.6]) - view.value = 0.6 - view.sendActions(for: .valueChanged) - } + let view = UISwitch() + subject.bind(to: view) - func testUISwitch() { - let subject = PassthroughSubject() + subject.send(false) + XCTAssertEqual(view.isOn, false) - let view = UISwitch() - subject.bind(to: view) + subject.send(true) + XCTAssertEqual(view.isOn, true) - subject.send(false) - XCTAssertEqual(view.isOn, false) + view.reactive.isOn.expectNext([true, false]) + view.isOn = false + view.sendActions(for: .valueChanged) + } - subject.send(true) - XCTAssertEqual(view.isOn, true) + func testUITextField() { + let subject = PassthroughSubject() - view.reactive.isOn.expectNext([true, false]) - view.isOn = false - view.sendActions(for: .valueChanged) - } + let view = UITextField() + subject.bind(to: view) - func testUITextField() { - let subject = PassthroughSubject() + subject.send("a") + XCTAssertEqual(view.text!, "a") - let view = UITextField() - subject.bind(to: view) + subject.send("b") + XCTAssertEqual(view.text!, "b") - subject.send("a") - XCTAssertEqual(view.text!, "a") + view.reactive.text.expectNext(["b", "c"]) + view.text = "c" + view.sendActions(for: .allEditingEvents) + } - subject.send("b") - XCTAssertEqual(view.text!, "b") + func testUITextView() { + let subject = PassthroughSubject() - view.reactive.text.expectNext(["b", "c"]) - view.text = "c" - view.sendActions(for: .allEditingEvents) - } + let view = UITextView() + subject.bind(to: view) - func testUITextView() { - let subject = PassthroughSubject() + subject.send("a") + XCTAssertEqual(view.text!, "a") - let view = UITextView() - subject.bind(to: view) + subject.send("b") + XCTAssertEqual(view.text!, "b") - subject.send("a") - XCTAssertEqual(view.text!, "a") + view.reactive.text.expectNext(["b", "c"]) + view.text = "c" + NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: view) + } - subject.send("b") - XCTAssertEqual(view.text!, "b") + func testUISearchBar() { + let subject = PassthroughSubject() - view.reactive.text.expectNext(["b", "c"]) - view.text = "c" - NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: view) - } - - func testUISearchBar() { - let subject = PassthroughSubject() - - let view = UISearchBar() - subject.bind(to: view) - - subject.send("a") - XCTAssertEqual(view.text!, "a") - - subject.send("b") - XCTAssertEqual(view.text!, "b") - - view.text = "c" - view.reactive.text.expectNext(["c"]) + let view = UISearchBar() + subject.bind(to: view) + + subject.send("a") + XCTAssertEqual(view.text!, "a") + + subject.send("b") + XCTAssertEqual(view.text!, "b") + + view.text = "c" + view.reactive.text.expectNext(["c"]) + } } -} #endif diff --git a/Tests/BondTests/UIPickerViewTests.swift b/Tests/BondTests/UIPickerViewTests.swift index abd0db81..407d319b 100644 --- a/Tests/BondTests/UIPickerViewTests.swift +++ b/Tests/BondTests/UIPickerViewTests.swift @@ -8,43 +8,42 @@ #if os(iOS) || os(tvOS) -import XCTest -import ReactiveKit -@testable import Bond + @testable import Bond + import ReactiveKit + import XCTest -class UIPickerViewTests: XCTestCase { + class UIPickerViewTests: XCTestCase { + var array: MutableObservableArray! + var pickerView: UIPickerView! - var array: MutableObservableArray! - var pickerView: UIPickerView! - - override func setUp() { - array = MutableObservableArray([1, 2, 3]) - pickerView = UIPickerView() - } + override func setUp() { + array = MutableObservableArray([1, 2, 3]) + pickerView = UIPickerView() + } - func testBind() { - array.bind(to: pickerView) - } + func testBind() { + array.bind(to: pickerView) + } - func testBindUsingCreateTitle() { - array.bind(to: pickerView) { (dataSource, row, component, pickerView) -> String? in - let indexPath = IndexPath(row: row, section: component) - let item = dataSource.item(at: indexPath) + func testBindUsingCreateTitle() { + array.bind(to: pickerView) { (dataSource, row, component, _) -> String? in + let indexPath = IndexPath(row: row, section: component) + let item = dataSource.item(at: indexPath) - return String(describing: item) + return String(describing: item) + } } - } - func testBindUsingBinderDataSource() { - let createTitle: ([Int], Int, Int, UIPickerView) -> String? = { (dataSource, row, component, pickerView) in - let indexPath = IndexPath(row: row, section: component) - let item = dataSource.item(at: indexPath) + func testBindUsingBinderDataSource() { + let createTitle: ([Int], Int, Int, UIPickerView) -> String? = { dataSource, row, component, _ in + let indexPath = IndexPath(row: row, section: component) + let item = dataSource.item(at: indexPath) - return String(describing: item) - } + return String(describing: item) + } - array.bind(to: pickerView, using: PickerViewBinderDataSource(createTitle)) + array.bind(to: pickerView, using: PickerViewBinderDataSource(createTitle)) + } } -} #endif diff --git a/Tests/BondTests/UITableViewTests.swift b/Tests/BondTests/UITableViewTests.swift index b81d2584..dabd59af 100644 --- a/Tests/BondTests/UITableViewTests.swift +++ b/Tests/BondTests/UITableViewTests.swift @@ -8,101 +8,99 @@ #if os(iOS) || os(tvOS) -import XCTest -import ReactiveKit -@testable import Bond + @testable import Bond + import ReactiveKit + import XCTest -class TestTableView: UITableView { + class TestTableView: UITableView { + var observedEvents: [OrderedCollectionDiff] = [] - var observedEvents: [OrderedCollectionDiff] = [] - - override func reloadData() { - super.reloadData() - observedEvents.append(OrderedCollectionDiff()) - } + override func reloadData() { + super.reloadData() + observedEvents.append(OrderedCollectionDiff()) + } - open override func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { - super.insertSections(sections, with: animation) - observedEvents.append(OrderedCollectionDiff(inserts: sections.map { [$0] })) - } + open override func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { + super.insertSections(sections, with: animation) + observedEvents.append(OrderedCollectionDiff(inserts: sections.map { [$0] })) + } - open override func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { - super.deleteSections(sections, with: animation) - observedEvents.append(OrderedCollectionDiff(deletes: sections.map { [$0] })) - } + open override func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { + super.deleteSections(sections, with: animation) + observedEvents.append(OrderedCollectionDiff(deletes: sections.map { [$0] })) + } - open override func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { - super.reloadSections(sections, with: animation) - observedEvents.append(OrderedCollectionDiff(updates: sections.map { [$0] })) - } + open override func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) { + super.reloadSections(sections, with: animation) + observedEvents.append(OrderedCollectionDiff(updates: sections.map { [$0] })) + } - open override func moveSection(_ section: Int, toSection newSection: Int) { - super.moveSection(section, toSection: newSection) - observedEvents.append(OrderedCollectionDiff(moves: [(from: [section], to: [newSection])])) - } + open override func moveSection(_ section: Int, toSection newSection: Int) { + super.moveSection(section, toSection: newSection) + observedEvents.append(OrderedCollectionDiff(moves: [(from: [section], to: [newSection])])) + } - open override func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { - super.insertRows(at: indexPaths, with: animation) - observedEvents.append(OrderedCollectionDiff(inserts: indexPaths)) - } + open override func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { + super.insertRows(at: indexPaths, with: animation) + observedEvents.append(OrderedCollectionDiff(inserts: indexPaths)) + } - open override func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { - super.deleteRows(at: indexPaths, with: animation) - observedEvents.append(OrderedCollectionDiff(deletes: indexPaths)) - } + open override func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { + super.deleteRows(at: indexPaths, with: animation) + observedEvents.append(OrderedCollectionDiff(deletes: indexPaths)) + } - open override func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { - super.reloadRows(at: indexPaths, with: animation) - observedEvents.append(OrderedCollectionDiff(updates: indexPaths)) - } + open override func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) { + super.reloadRows(at: indexPaths, with: animation) + observedEvents.append(OrderedCollectionDiff(updates: indexPaths)) + } - open override func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath) { - super.moveRow(at: indexPath, to: newIndexPath) - observedEvents.append(OrderedCollectionDiff(moves: [(from: indexPath, to: newIndexPath)])) + open override func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath) { + super.moveRow(at: indexPath, to: newIndexPath) + observedEvents.append(OrderedCollectionDiff(moves: [(from: indexPath, to: newIndexPath)])) + } } -} -class UITableViewTests: XCTestCase { + class UITableViewTests: XCTestCase { + var array: MutableObservableArray! + var tableView: TestTableView! - var array: MutableObservableArray! - var tableView: TestTableView! + override func setUp() { + array = MutableObservableArray([1, 2, 3]) + tableView = TestTableView() + array.bind(to: tableView, cellType: UITableViewCell.self) { _, _ in } + } - override func setUp() { - array = MutableObservableArray([1, 2, 3]) - tableView = TestTableView() - array.bind(to: tableView, cellType: UITableViewCell.self) { _, _ in } - } + func testInsertRows() { + array.insert(4, at: 1) + XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0)])]) + } - func testInsertRows() { - array.insert(4, at: 1) - XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0)])]) - } + func testDeleteRows() { + _ = array.remove(at: 2) + XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(deletes: [IndexPath(row: 2, section: 0)])]) + } - func testDeleteRows() { - let _ = array.remove(at: 2) - XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(deletes: [IndexPath(row: 2, section: 0)])]) - } + func testReloadRows() { + array[2] = 5 + XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(updates: [IndexPath(row: 2, section: 0)])]) + } - func testReloadRows() { - array[2] = 5 - XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(updates: [IndexPath(row: 2, section: 0)])]) - } + func testMoveRow() { + array.move(from: 1, to: 2) + XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(moves: [(from: IndexPath(row: 1, section: 0), to: IndexPath(row: 2, section: 0))])]) + } - func testMoveRow() { - array.move(from: 1, to: 2) - XCTAssert(tableView.observedEvents == [OrderedCollectionDiff(), OrderedCollectionDiff(moves: [(from: IndexPath(row: 1, section: 0), to: IndexPath(row: 2, section: 0))])]) - } + func testBatchUpdates() { + array.batchUpdate { array in + array.insert(0, at: 0) + array.insert(1, at: 0) + } - func testBatchUpdates() { - array.batchUpdate { (array) in - array.insert(0, at: 0) - array.insert(1, at: 0) + let possibleResultA = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0), IndexPath(row: 0, section: 0)])] + let possibleResultB = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 0, section: 0), IndexPath(row: 1, section: 0)])] + XCTAssert(tableView.observedEvents == possibleResultA || tableView.observedEvents == possibleResultB) } - - let possibleResultA = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 1, section: 0), IndexPath(row: 0, section: 0)])] - let possibleResultB = [OrderedCollectionDiff(), OrderedCollectionDiff(inserts: [IndexPath(row: 0, section: 0), IndexPath(row: 1, section: 0)])] - XCTAssert(tableView.observedEvents == possibleResultA || tableView.observedEvents == possibleResultB) } -} #endif