diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Bind.xcodeproj/project.pbxproj b/Bind.xcodeproj/project.pbxproj
index 4d3b448..7b85810 100644
--- a/Bind.xcodeproj/project.pbxproj
+++ b/Bind.xcodeproj/project.pbxproj
@@ -78,6 +78,10 @@
8755A7462347A93800C8AD07 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A73E2347A88D00C8AD07 /* RelayTests.swift */; };
8755A7472347A93800C8AD07 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A73E2347A88D00C8AD07 /* RelayTests.swift */; };
8755A7482347A93E00C8AD07 /* BindableTestsUIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A73C23479B5700C8AD07 /* BindableTestsUIKit.swift */; };
+ 8755A74A234B508900C8AD07 /* UnbindableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A749234B508900C8AD07 /* UnbindableMock.swift */; };
+ 8755A74B234B509000C8AD07 /* UnbindableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A749234B508900C8AD07 /* UnbindableMock.swift */; };
+ 8755A74C234B509000C8AD07 /* UnbindableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A749234B508900C8AD07 /* UnbindableMock.swift */; };
+ 8755A74D234B509000C8AD07 /* UnbindableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8755A749234B508900C8AD07 /* UnbindableMock.swift */; };
8774D75FE1168D2F1D4CE056 /* Bindable+UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9B94AF56A71DFBCA50413B /* Bindable+UIImageView.swift */; };
8916D29F0AEF2B864EED6B53 /* Bindable+UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8B8A3AE507973B94E0E2A1 /* Bindable+UILabel.swift */; };
8A9566D706D206E76327E3DB /* Bindable+NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC8C0368846079A6AB4E3A8 /* Bindable+NSLayoutConstraint.swift */; };
@@ -233,6 +237,7 @@
8755A73C23479B5700C8AD07 /* BindableTestsUIKit.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = BindableTestsUIKit.swift; sourceTree = ""; tabWidth = 4; };
8755A73E2347A88D00C8AD07 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = ""; };
8755A7402347A92500C8AD07 /* SubscriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionTests.swift; sourceTree = ""; };
+ 8755A749234B508900C8AD07 /* UnbindableMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbindableMock.swift; sourceTree = ""; };
87BCBA1C96D1499A2368C6C4 /* Bindable+UIViewController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = "Bindable+UIViewController.swift"; sourceTree = ""; tabWidth = 4; };
8C9B94AF56A71DFBCA50413B /* Bindable+UIImageView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = "Bindable+UIImageView.swift"; sourceTree = ""; tabWidth = 4; };
91D93EE6FE99882FF6B5A4E3 /* BindTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BindTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -372,6 +377,7 @@
children = (
643F9E946B7E3D2F8E2975BD /* PrinterMock.swift */,
DBCF98045AC9F17CD5EF1563 /* BindableMock.swift */,
+ 8755A749234B508900C8AD07 /* UnbindableMock.swift */,
);
path = Mock;
sourceTree = "";
@@ -667,6 +673,7 @@
DEEB7E76CA6E133DCE59BD82 /* PrinterMock.swift in Sources */,
8755A73F2347A88D00C8AD07 /* RelayTests.swift in Sources */,
8755A7412347A92500C8AD07 /* SubscriptionTests.swift in Sources */,
+ 8755A74A234B508900C8AD07 /* UnbindableMock.swift in Sources */,
2D1C7E116D41AC6269445022 /* BindableMock.swift in Sources */,
637549C2D38B6A9E34E3C05E /* Output+Extension.swift in Sources */,
8755A73D23479B5700C8AD07 /* BindableTestsUIKit.swift in Sources */,
@@ -741,6 +748,7 @@
16B22C259512A6A4DB492648 /* BindableMock.swift in Sources */,
F37FB2DC1E254424AC2BAE03 /* Output+Extension.swift in Sources */,
1DB68B189147E165DF6BB137 /* OutputTests.swift in Sources */,
+ 8755A74D234B509000C8AD07 /* UnbindableMock.swift in Sources */,
8755A7442347A93500C8AD07 /* SubscriptionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -810,6 +818,7 @@
A8428486B13DF705F151FDD6 /* PrinterMock.swift in Sources */,
8755A7452347A93700C8AD07 /* RelayTests.swift in Sources */,
28AB432723BFCC656F66C488 /* BindableMock.swift in Sources */,
+ 8755A74B234B509000C8AD07 /* UnbindableMock.swift in Sources */,
B603ED1DF83FB4467FB9B868 /* Output+Extension.swift in Sources */,
6F853E9E0052D86B01B6F4F3 /* OutputTests.swift in Sources */,
8755A7482347A93E00C8AD07 /* BindableTestsUIKit.swift in Sources */,
@@ -826,6 +835,7 @@
9D059CF3C7783B55F63C17CB /* BindableMock.swift in Sources */,
23ED5475B3FF87CBAF7B1701 /* Output+Extension.swift in Sources */,
42DCB57764A902476445A1ED /* OutputTests.swift in Sources */,
+ 8755A74C234B509000C8AD07 /* UnbindableMock.swift in Sources */,
8755A7432347A93400C8AD07 /* SubscriptionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Package.swift b/Package.swift
index b616a3b..80b359d 100644
--- a/Package.swift
+++ b/Package.swift
@@ -4,13 +4,18 @@
import PackageDescription
let package = Package(
- name: "Trigger",
+ name: "Bind",
+ platforms: [
+ .iOS(.v11),
+ .macOS(.v10_12)
+ ],
products: [
.library(
- name: "Trigger", targets: ["Trigger"])
+ name: "Bind", targets: ["Bind"])
],
targets: [
- .target( name: "Trigger"),
- .testTarget( name: "TriggerTests", dependencies: ["Trigger"])
- ]
+ .target( name: "Bind"),
+ .testTarget( name: "BindTests", dependencies: ["Bind"])
+ ],
+ swiftLanguageVersions: [.v5]
)
diff --git a/Sources/Bind/Bindables/UIKit/Bindable+UIControl.swift b/Sources/Bind/Bindables/UIKit/Bindable+UIControl.swift
index f57c0dd..df9ce43 100644
--- a/Sources/Bind/Bindables/UIKit/Bindable+UIControl.swift
+++ b/Sources/Bind/Bindables/UIKit/Bindable+UIControl.swift
@@ -2,7 +2,6 @@
import UIKit
public extension Bindable where TargetType: UIControl {
-
var isSelected: Binder {
return Binder(self.target) { control, isSelected in
control.isSelected = isSelected
diff --git a/Sources/Bind/Output.swift b/Sources/Bind/Output.swift
index 3833709..40d6c41 100644
--- a/Sources/Bind/Output.swift
+++ b/Sources/Bind/Output.swift
@@ -187,6 +187,12 @@ public extension Output {
return output
}
+ /**
+ `filter` passes the Value through a predicate, if the functions true the `Value` is output, otherwise
+ it is filtered out.
+ - Parameter filter: The function that predicates on the `Value` to determine if it is output
+ - Returns: A new `Output` which predicates on the `Value` before outputting
+ */
func filter(_ filter: @escaping (Value) -> Bool) -> Output {
let output = Output()
@@ -198,14 +204,25 @@ public extension Output {
return output
}
-}
-// MARK: - Typed extensions
-public extension Output where Value == Bool {
- func invert() {
- guard let currentValue = value else {
- return
+ /**
+ Returns a new output that is the result of combining the outputted elements of the receiver
+ using the given closure.
+ - Parameter initial: The value to use as the initial accumulating value.
+ initialResult is passed to nextPartialResult the first time the closure is executed.
+ - Parameter nextPartialResult:
+ A closure that combines an accumulating value and the next value of the Output into a new accumulating
+ value that is then output to any binders.
+ - Returns: A new `Output` which acts as a tap of the combined accumulating values
+ */
+ func reduce(initial: Result, nextPartialResult: @escaping (Result, Value) -> Result) -> Output {
+ let output = Output()
+
+ bind { value in
+ let result = nextPartialResult(output.value ?? initial, value)
+ output.update(withValue: result)
}
- update(withValue: !currentValue)
+
+ return output
}
}
diff --git a/Sources/Bind/Subscription.swift b/Sources/Bind/Subscription.swift
index 198b65f..a9c3f00 100644
--- a/Sources/Bind/Subscription.swift
+++ b/Sources/Bind/Subscription.swift
@@ -29,7 +29,7 @@ extension Subscription: Hashable {
}
public final class SubscriptionContainer {
- private var container: [Subscription] = []
+ var container: [Subscription] = []
public init() {}
@@ -41,5 +41,7 @@ public final class SubscriptionContainer {
for subscription in container {
subscription.unsubscribe()
}
+
+ container = []
}
}
diff --git a/Tests/BindTests/BindableTestsUIKit.swift b/Tests/BindTests/BindableTestsUIKit.swift
index 97e110a..480556f 100644
--- a/Tests/BindTests/BindableTestsUIKit.swift
+++ b/Tests/BindTests/BindableTestsUIKit.swift
@@ -7,6 +7,7 @@ import XCTest
final class BindableTestsUIKit: XCTestCase {
func testLabel() {
let label = UILabel()
+ label.textColor = .black
let attributedLabel = UILabel()
let textOutput = Output()
@@ -29,5 +30,97 @@ final class BindableTestsUIKit: XCTestCase {
attributedTextOutput.update(withValue: NSAttributedString(string: "text"))
XCTAssertEqual(attributedLabel.attributedText, NSAttributedString(string: "text"))
}
+
+ func testViewBools() {
+ let view = UIView()
+
+ let hiddenOutput = Output()
+ let visibleOutput = Output()
+ let visibleAlpha = Output()
+ let visibleAlphaAnimated = Output()
+ let userInteractionEnabledOutput = Output()
+ let constraintsActive = Output()
+
+ hiddenOutput.bind(to: view.binding.isHidden)
+ visibleOutput.bind(to: view.binding.isVisible)
+ visibleAlpha.bind(to: view.binding.isVisibleAlpha(animated: false))
+ visibleAlphaAnimated.bind(to: view.binding.isVisibleAlpha(animated: true))
+ userInteractionEnabledOutput.bind(to: view.binding.isUserInteractionEnabled)
+ constraintsActive.bind(to: view.binding.areConstraintsActive)
+
+ hiddenOutput.update(withValue: true)
+ XCTAssertEqual(view.isHidden, true)
+
+ visibleOutput.update(withValue: true)
+ XCTAssertEqual(view.isHidden, false)
+
+ visibleAlpha.update(withValue: false)
+ XCTAssertEqual(view.alpha, 0)
+
+ visibleAlpha.update(withValue: true)
+ XCTAssertEqual(view.alpha, 1)
+
+ visibleAlphaAnimated.update(withValue: false)
+ XCTAssertEqual(view.alpha, 0)
+
+ visibleAlphaAnimated.update(withValue: true)
+ XCTAssertEqual(view.alpha, 1)
+
+ userInteractionEnabledOutput.update(withValue: false)
+ XCTAssertEqual(view.isUserInteractionEnabled, false)
+
+ view.heightAnchor.constraint(equalToConstant: 10).isActive = true
+ XCTAssertEqual(view.constraints.count, 1)
+ constraintsActive.update(withValue: false)
+ XCTAssertEqual(view.constraints.count, 0)
+ }
+
+ func testViewUIColors() {
+ let view = UIView()
+
+ let backgroundColorOutput = Output()
+ let borderColorOutput = Output()
+ let tintColorOutput = Output()
+
+ backgroundColorOutput.bind(to: view.binding.backgroundColor)
+ borderColorOutput.bind(to: view.binding.borderColor)
+ tintColorOutput.bind(to: view.binding.tintColor)
+
+ backgroundColorOutput.update(withValue: .red)
+ XCTAssertEqual(view.backgroundColor, .red)
+
+ borderColorOutput.update(withValue: .green)
+ XCTAssertEqual(view.layer.borderColor, UIColor.green.cgColor)
+
+ tintColorOutput.update(withValue: .blue)
+ XCTAssertEqual(view.tintColor, .blue)
+ }
+
+ func testViewCGFloat() {
+ let view = UIView()
+
+ let borderWidthOutput = Output()
+ let cornerRadiusOutput = Output()
+
+ borderWidthOutput.bind(to: view.binding.borderWidth)
+ cornerRadiusOutput.bind(to: view.binding.cornerRadius)
+
+ borderWidthOutput.update(withValue: 30)
+ XCTAssertEqual(view.layer.borderWidth, 30)
+
+ cornerRadiusOutput.update(withValue: 40)
+ XCTAssertEqual(view.layer.cornerRadius, 40)
+ }
+
+ func testViewString() {
+ let view = UIView()
+
+ let accessibilityOutput = Output()
+
+ accessibilityOutput.bind(to: view.binding.accessibilityIdentifier)
+
+ accessibilityOutput.update(withValue: "test-identifier")
+ XCTAssertEqual(view.accessibilityIdentifier, "test-identifier")
+ }
}
#endif
diff --git a/Tests/BindTests/Mock/UnbindableMock.swift b/Tests/BindTests/Mock/UnbindableMock.swift
new file mode 100644
index 0000000..5103cc8
--- /dev/null
+++ b/Tests/BindTests/Mock/UnbindableMock.swift
@@ -0,0 +1,10 @@
+@testable import Bind
+
+final class UnbindableMock: Unbindable {
+ var unbindCalledCount = 0
+ var unbindSubscriptionArray = [Subscription]()
+ func unbind(for subscription: Subscription) {
+ unbindCalledCount += 1
+ unbindSubscriptionArray.append(subscription)
+ }
+}
diff --git a/Tests/BindTests/OutputTests.swift b/Tests/BindTests/OutputTests.swift
index 63619ab..9442484 100644
--- a/Tests/BindTests/OutputTests.swift
+++ b/Tests/BindTests/OutputTests.swift
@@ -49,23 +49,6 @@ final class OutputTests: XCTestCase {
XCTAssertEqual(testObjectTwo.text, "Test")
}
- func testToggleWithValue() {
- let output = Output(value: true)
- XCTAssertEqual(output.latest, true)
-
- output.invert()
-
- XCTAssertEqual(output.latest, false)
- }
-
- func testToggleWithNoValue() {
- let output = Output()
- XCTAssertNil(output.latest)
-
- output.invert()
- XCTAssertNil(output.latest)
- }
-
func testUnbind() {
let testObject = BindableMock()
@@ -221,7 +204,7 @@ final class OutputTests: XCTestCase {
value.update(withValue: .one)
XCTAssertEqual(mappedValue.latest, "one")
- }
+ }
func testFlatMap() {
//swiftlint:disable:next nesting
@@ -292,6 +275,40 @@ final class OutputTests: XCTestCase {
XCTAssertEqual(merge.latest, 5)
}
+ func testReduceReferenceType() {
+ class TestObject { //swiftlint:disable:this nesting
+ var currentString: String = ""
+ }
+
+ let initial = Output()
+
+ let reduced = initial
+ .reduce(initial: TestObject()) { current, number -> TestObject in
+ var currentString = current.currentString
+ currentString += "\(number)"
+ current.currentString = currentString
+ return current
+ }
+
+ for value in [1, 2, 3, 4, 5] {
+ initial.update(withValue: value)
+ }
+
+ XCTAssertEqual(reduced.latest?.currentString, "12345")
+ }
+
+ func testReduceValueType() {
+ let initial = Output()
+
+ let reduced = initial.reduce(initial: 0, nextPartialResult: +)
+
+ for value in [1, 2, 3, 4, 5] {
+ initial.update(withValue: value)
+ }
+
+ XCTAssertEqual(reduced.latest, 15)
+ }
+
func testDebug() {
let printer = PrinterMock()
let output1 = Output(printer: printer)
diff --git a/Tests/BindTests/SubscriptionTests.swift b/Tests/BindTests/SubscriptionTests.swift
index 228ec0f..d4eb0e5 100644
--- a/Tests/BindTests/SubscriptionTests.swift
+++ b/Tests/BindTests/SubscriptionTests.swift
@@ -2,5 +2,39 @@ import XCTest
@testable import Bind
final class SubscriptionTests: XCTestCase {
+ func testUnsubscribe() {
+ let mockUnbindable = UnbindableMock()
+ let subscription = Subscription(unbinder: mockUnbindable)
+ subscription.unsubscribe()
+
+ XCTAssertEqual(mockUnbindable.unbindCalledCount, 1)
+ XCTAssertEqual(mockUnbindable.unbindSubscriptionArray.count, 1)
+ XCTAssertEqual(mockUnbindable.unbindSubscriptionArray.first, subscription)
+ }
+
+ func testSubscriptionContainerAdd() {
+ let mockUnbindable = UnbindableMock()
+ let subscription = Subscription(unbinder: mockUnbindable)
+ let container = SubscriptionContainer()
+
+ subscription.add(to: container)
+
+ XCTAssertEqual(container.container.count, 1)
+ }
+
+ func testSubscriptionContainerUnsubscribe() {
+ let mockUnbindable = UnbindableMock()
+ let subscription = Subscription(unbinder: mockUnbindable)
+
+ let container = SubscriptionContainer()
+ subscription.add(to: container)
+
+ XCTAssertEqual(container.container.count, 1)
+
+ container.unsubscribe()
+
+ XCTAssertEqual(container.container.count, 0)
+ XCTAssertEqual(mockUnbindable.unbindCalledCount, 1)
+ }
}