From 6b6bb739de2bdde277e980155bf549e43cf121f0 Mon Sep 17 00:00:00 2001 From: Siemen Sikkema Date: Wed, 25 May 2016 23:02:54 +0200 Subject: [PATCH 01/15] Add rex_action and rex_enabled to UIGestureRecognizer --- Rex.xcodeproj/project.pbxproj | 8 +++ Source/UIKit/UIGestureRecognizer.swift | 43 +++++++++++ Tests/UIKit/UIGestureRecognizerTests.swift | 84 ++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 Source/UIKit/UIGestureRecognizer.swift create mode 100644 Tests/UIKit/UIGestureRecognizerTests.swift diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index b208e35..9118241 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 4238D5961B4D5950008534C0 /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4238D5951B4D5950008534C0 /* NSTextField.swift */; }; + 5F2739701CF6375500C44108 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */; }; + 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */; }; 7D2AA99B1CB6EFEB008AB5C9 /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */; }; 7D2AA99D1CB6F275008AB5C9 /* UISwitchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */; }; 7DBD48F31CC8141D0077AD4F /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBD48F21CC8141D0077AD4F /* Reusable.swift */; }; @@ -204,6 +206,8 @@ 4238D5951B4D5950008534C0 /* NSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NSTextField.swift; path = AppKit/NSTextField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5173EBC51B625A2600C9B48E /* UIBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarButtonItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizerTests.swift; sourceTree = ""; }; 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UISwitch.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UISwitchTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7DBD48F21CC8141D0077AD4F /* Reusable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; @@ -443,6 +447,7 @@ 7DCF5B311CC80D0E004AEE75 /* UICollectionReusableView.swift */, D86FFBD41B34B0FE001A89B3 /* UIControl.swift */, 9DA915A31CA6301C003723B9 /* UIDatePicker.swift */, + 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */, 8289A2E21BD7EF740097FB60 /* UIImageView.swift */, D86FFBD71B34B242001A89B3 /* UILabel.swift */, E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */, @@ -486,6 +491,7 @@ 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, 8295FD851B873081007C9000 /* UIControlTests.swift */, 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */, + 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */, 8289A2E01BD7EF1F0097FB60 /* UIImageViewTests.swift */, D8F073141B861B3A0047D546 /* UILabelTests.swift */, E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */, @@ -849,6 +855,7 @@ C7945F111CC192E800DC9E37 /* UIViewController.swift in Sources */, E6933BEC1CD9C37D006F7CE7 /* UIProgressView.swift in Sources */, D834572E1AFEE45B0070616A /* SignalProducer.swift in Sources */, + 5F2739701CF6375500C44108 /* UIGestureRecognizer.swift in Sources */, C7DCE2B41CB3C89A001217D8 /* UITextView.swift in Sources */, 8289A2E51BD7F6DD0097FB60 /* UIView.swift in Sources */, D86FFBD61B34B116001A89B3 /* UIControl.swift in Sources */, @@ -870,6 +877,7 @@ 8289A2E81BD7F7900097FB60 /* UIViewTests.swift in Sources */, D8F073161B863CE70047D546 /* UILabelTests.swift in Sources */, 7D2AA99D1CB6F275008AB5C9 /* UISwitchTests.swift in Sources */, + 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */, C7932E871C4B42F500086F3C /* UITextFieldTests.swift in Sources */, 8295FD8A1B87352D007C9000 /* UIButtonTests.swift in Sources */, D8A4540A1BD2772700C9E790 /* PropertyTests.swift in Sources */, diff --git a/Source/UIKit/UIGestureRecognizer.swift b/Source/UIKit/UIGestureRecognizer.swift new file mode 100644 index 0000000..9611159 --- /dev/null +++ b/Source/UIKit/UIGestureRecognizer.swift @@ -0,0 +1,43 @@ +// +// UIGestureRecognizer.swift +// Rex +// +// Created by Siemen Sikkema on 25/05/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa +import UIKit + +extension UIGestureRecognizer { + + /// Wraps a gesture recognizer's `enabled` state in a bindable property. + public var rex_enabled: MutableProperty { + return associatedProperty(self, key: &enabledKey, initial: { $0.enabled }, setter: { $0.enabled = $1 }) + } + + /// Exposes a property that binds an action to gesture events. The action is set as + /// a target of the gesture recognizer. When property changes occur the + /// previous action is removed as a target. This also binds the enabled state of the + /// action to the `rex_enabled` property on the button. + public var rex_action: MutableProperty { + return associatedObject(self, key: &actionKey) { host in + let initial = CocoaAction.rex_disabled + let property = MutableProperty(initial) + + property.producer + .combinePrevious(initial) + .startWithNext { [weak host] previous, next in + host?.removeTarget(previous, action: CocoaAction.selector) + host?.addTarget(next, action: CocoaAction.selector) + } + + host.rex_enabled <~ property.producer.flatMap(.Latest) { $0.rex_enabledProducer } + + return property + } + } +} + +private var actionKey: UInt8 = 0 +private var enabledKey: UInt8 = 0 diff --git a/Tests/UIKit/UIGestureRecognizerTests.swift b/Tests/UIKit/UIGestureRecognizerTests.swift new file mode 100644 index 0000000..31823c0 --- /dev/null +++ b/Tests/UIKit/UIGestureRecognizerTests.swift @@ -0,0 +1,84 @@ +// +// UIGestureRecognizerTests.swift +// Rex +// +// Created by Siemen Sikkema on 25/05/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa +import UIKit +import XCTest +import enum Result.NoError + +private extension UIGestureRecognizer { + func sendActionToTarget() { + guard let + targets = valueForKey("targets") as? [NSObject], + cocoaAction = targets.first?.valueForKey("target") as? CocoaAction + else { + return + } + + cocoaAction.execute(nil) + } +} + +class UIGestureRecognizerTests: XCTestCase { + + weak var _gestureRecognizer: UITapGestureRecognizer? + + override func tearDown() { + XCTAssert(_gestureRecognizer == nil, "Retain cycle detected in UIButton properties") + super.tearDown() + } + + func testEnabledPropertyDoesntCreateRetainCycle() { + let gestureRecognizer = UITapGestureRecognizer() + _gestureRecognizer = gestureRecognizer + + gestureRecognizer.rex_enabled <~ SignalProducer(value: false) + XCTAssert(_gestureRecognizer?.enabled == false) + } + + func testEnabledProperty() { + let gestureRecognizer = UITapGestureRecognizer() + gestureRecognizer.enabled = true + + let (pipeSignal, observer) = Signal.pipe() + gestureRecognizer.rex_enabled <~ SignalProducer(signal: pipeSignal) + + + observer.sendNext(false) + XCTAssertFalse(gestureRecognizer.enabled) + observer.sendNext(true) + XCTAssertTrue(gestureRecognizer.enabled) + } + + func testActionPropertyDoesntCreateRetainCycle() { + let gestureRecognizer = UITapGestureRecognizer() + _gestureRecognizer = gestureRecognizer + + let action = Action<(),(),NoError> { + SignalProducer(value: ()) + } + gestureRecognizer.rex_action <~ SignalProducer(value: CocoaAction(action, input: ())) + } + + func testActionProperty() { + let gestureRecognizer = UITapGestureRecognizer() + gestureRecognizer.enabled = true + + let passed = MutableProperty(false) + let action = Action<(), Bool, NoError> { _ in + SignalProducer(value: true) + } + + passed <~ SignalProducer(signal: action.values) + gestureRecognizer.rex_action <~ SignalProducer(value: CocoaAction(action, input: ())) + + gestureRecognizer.sendActionToTarget() + + XCTAssertTrue(passed.value) + } +} From 3e35d5b1b57f085023c505e33a338cf6af128e23 Mon Sep 17 00:00:00 2001 From: Siemen Sikkema Date: Wed, 25 May 2016 23:03:16 +0200 Subject: [PATCH 02/15] Clean up white line and unused code --- Tests/UIKit/UIButtonTests.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/UIKit/UIButtonTests.swift b/Tests/UIKit/UIButtonTests.swift index b8d33f7..582338b 100644 --- a/Tests/UIKit/UIButtonTests.swift +++ b/Tests/UIKit/UIButtonTests.swift @@ -12,11 +12,6 @@ import XCTest import enum Result.NoError extension UIButton { - static func button() -> UIButton { - let button = UIButton(type: UIButtonType.Custom) - return button; - } - override public func sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) { target?.performSelector(action, withObject: nil) } @@ -92,7 +87,6 @@ class UIButtonTests: XCTestCase { button.sendActionsForControlEvents(.TouchUpInside) - XCTAssertTrue(passed.value) } } From 54e39d8b866c3a7f2234185364af1dc5b3d3bb3e Mon Sep 17 00:00:00 2001 From: Andrew Ware Date: Wed, 8 Jun 2016 04:53:05 -0400 Subject: [PATCH 03/15] Add userInteractionEnabled to UITableViewCell (#127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add userInteractionEnabled to UITableViewCell This allows the binding of a Bool to the cell's userInteractionEnabled attribute. * Move code into correct file * Remove UITableViewCell.swift file from root dir * Revert UITableViewCell.swift to original * Add userInteractionEnabled to UIView * Update comments in UIView.swift sorry for the commit spamming 😁 * Add testUserInteractionEnabledProperty --- Source/UIKit/UITableViewCell.swift | 2 ++ Source/UIKit/UIView.swift | 7 +++++++ Tests/UIKit/UIViewTests.swift | 13 +++++++++++++ 3 files changed, 22 insertions(+) diff --git a/Source/UIKit/UITableViewCell.swift b/Source/UIKit/UITableViewCell.swift index d59c372..122df01 100644 --- a/Source/UIKit/UITableViewCell.swift +++ b/Source/UIKit/UITableViewCell.swift @@ -3,9 +3,11 @@ // Rex // // Created by David Rodrigues on 19/04/16. +// modified by Andrew Ware on 6/7/16. // Copyright © 2016 Neil Pankey. All rights reserved. // +import ReactiveCocoa import UIKit extension UITableViewCell: Reusable {} diff --git a/Source/UIKit/UIView.swift b/Source/UIKit/UIView.swift index a790339..f0a67f7 100644 --- a/Source/UIKit/UIView.swift +++ b/Source/UIKit/UIView.swift @@ -19,7 +19,14 @@ extension UIView { public var rex_hidden: MutableProperty { return associatedProperty(self, key: &hiddenKey, initial: { $0.hidden }, setter: { $0.hidden = $1 }) } + + + /// Wraps a view's `userInteractionEnabled` state in a bindable property. + public var rex_userInteractionEnabled: MutableProperty { + return associatedProperty(self, key: &userInteractionEnabledKey, initial: { $0.userInteractionEnabled }, setter: { $0.userInteractionEnabled = $1 }) + } } private var alphaKey: UInt8 = 0 private var hiddenKey: UInt8 = 0 +private var userInteractionEnabledKey: UInt8 = 0 diff --git a/Tests/UIKit/UIViewTests.swift b/Tests/UIKit/UIViewTests.swift index f67bc69..acd51c3 100644 --- a/Tests/UIKit/UIViewTests.swift +++ b/Tests/UIKit/UIViewTests.swift @@ -64,4 +64,17 @@ class UIViewTests: XCTestCase { observer.sendNext(secondChange) XCTAssertEqualWithAccuracy(view.alpha, secondChange, accuracy: 0.01) } + + func testUserInteractionEnabledProperty() { + let view = UIView(frame: CGRectZero) + view.userInteractionEnabled = true + + let (pipeSignal, observer) = Signal.pipe() + view.rex_userInteractionEnabled <~ SignalProducer(signal: pipeSignal) + + observer.sendNext(true) + XCTAssertTrue(view.userInteractionEnabled) + observer.sendNext(false) + XCTAssertFalse(view.userInteractionEnabled) + } } From 5a68ddef532c326337aaa4c9f66947e26645d975 Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Wed, 8 Jun 2016 09:53:19 +0100 Subject: [PATCH 04/15] bump RAC to 4.2 (#124) --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index d637b9a..a4583d1 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "ReactiveCocoa/ReactiveCocoa" ~> 4.1 +github "ReactiveCocoa/ReactiveCocoa" ~> 4.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 4726855..a82c40d 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ github "antitypical/Result" "2.0.0" -github "ReactiveCocoa/ReactiveCocoa" "v4.1.0" +github "ReactiveCocoa/ReactiveCocoa" "v4.2.0" From dae2ae25fd2f214074c38538c795f5e1147efbff Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Tue, 7 Jun 2016 20:16:18 +0200 Subject: [PATCH 05/15] Implement `rex_selectedSegmentIndex` for UISegmentedControl --- Rex.podspec | 2 +- Rex.xcodeproj/project.pbxproj | 8 +++++++ Source/UIKit/UISegmentedControl.swift | 22 +++++++++++++++++ Tests/UIKit/UISegmentedControlTests.swift | 29 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Source/UIKit/UISegmentedControl.swift create mode 100644 Tests/UIKit/UISegmentedControlTests.swift diff --git a/Rex.podspec b/Rex.podspec index 5020b7c..917d3e6 100644 --- a/Rex.podspec +++ b/Rex.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.source_files = 'Source/**/*.swift' s.ios.exclude_files = 'Source/AppKit/*' - s.tvos.exclude_files = 'Source/AppKit/*', 'Source/UIKit/UIDatePicker.swift', 'Source/UIKit/UISwitch.swift' + s.tvos.exclude_files = 'Source/AppKit/*', 'Source/UIKit/UIDatePicker.swift', 'Source/UIKit/UISwitch.swift', 'Source/UIKit/UISegmentedControl.swift' s.watchos.exclude_files = 'Source/AppKit/*', 'Source/UIKit/*' s.osx.exclude_files = 'Source/UIKit/*' diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index 9118241..d172069 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 4238D5961B4D5950008534C0 /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4238D5951B4D5950008534C0 /* NSTextField.swift */; }; 5F2739701CF6375500C44108 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */; }; 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */; }; + 5B7F81E31D0842AD0014B06D /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */; }; + 5B7F81E41D0842B50014B06D /* UISegmentedControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */; }; 7D2AA99B1CB6EFEB008AB5C9 /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */; }; 7D2AA99D1CB6F275008AB5C9 /* UISwitchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */; }; 7DBD48F31CC8141D0077AD4F /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBD48F21CC8141D0077AD4F /* Reusable.swift */; }; @@ -208,6 +210,8 @@ 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarButtonItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizerTests.swift; sourceTree = ""; }; + 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = ""; }; + 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControlTests.swift; sourceTree = ""; }; 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UISwitch.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UISwitchTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7DBD48F21CC8141D0077AD4F /* Reusable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; }; @@ -452,6 +456,7 @@ D86FFBD71B34B242001A89B3 /* UILabel.swift */, E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */, 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */, + 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */, 7DC325721CC6FCF100746D88 /* UITableViewCell.swift */, 7DC325731CC6FCF100746D88 /* UITableViewHeaderFooterView.swift */, C7932E811C4B3EDB00086F3C /* UITextField.swift */, @@ -496,6 +501,7 @@ D8F073141B861B3A0047D546 /* UILabelTests.swift */, E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */, 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */, + 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */, 7DC325781CC6FD0A00746D88 /* UITableViewCellTests.swift */, 7DC325791CC6FD0A00746D88 /* UITableViewHeaderFooterViewTests.swift */, C7932E851C4B420A00086F3C /* UITextFieldTests.swift */, @@ -842,6 +848,7 @@ C7932E831C4B3F3000086F3C /* UITextField.swift in Sources */, 7DC325761CC6FCF100746D88 /* UITableViewHeaderFooterView.swift in Sources */, 7DC325741CC6FCF100746D88 /* UITableViewCell.swift in Sources */, + 5B7F81E31D0842AD0014B06D /* UISegmentedControl.swift in Sources */, 8289A2E31BD7EF740097FB60 /* UIImageView.swift in Sources */, D8F0973E1B17F30D002E15BA /* NSUserDefaults.swift in Sources */, D834572D1AFEE45B0070616A /* Signal.swift in Sources */, @@ -889,6 +896,7 @@ 8295FD8D1B87374A007C9000 /* UIBarButtonItemTests.swift in Sources */, 8295FD871B87309F007C9000 /* UIControlTests.swift in Sources */, C7DCE2B71CB3C9D6001217D8 /* UITextViewTests.swift in Sources */, + 5B7F81E41D0842B50014B06D /* UISegmentedControlTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/UIKit/UISegmentedControl.swift b/Source/UIKit/UISegmentedControl.swift new file mode 100644 index 0000000..465c76e --- /dev/null +++ b/Source/UIKit/UISegmentedControl.swift @@ -0,0 +1,22 @@ +// +// UISegmentedControl.swift +// Rex +// +// Created by Markus Chmelar on 07/06/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa +import UIKit + +extension UISegmentedControl { + /// Wraps a segmentedControls `selectedSegmentIndex` state in a bindable property. + public var rex_selectedSegmentIndex: MutableProperty { + let property = associatedProperty(self, key: &selectedSegmentIndexKey, initial: { $0.selectedSegmentIndex }, setter: { $0.selectedSegmentIndex = $1 }) + property <~ rex_controlEvents(.ValueChanged) + .filterMap { ($0 as? UISegmentedControl)?.selectedSegmentIndex } + return property + } +} + +private var selectedSegmentIndexKey: UInt8 = 0 diff --git a/Tests/UIKit/UISegmentedControlTests.swift b/Tests/UIKit/UISegmentedControlTests.swift new file mode 100644 index 0000000..d7c7bfa --- /dev/null +++ b/Tests/UIKit/UISegmentedControlTests.swift @@ -0,0 +1,29 @@ +// +// UISegmentedControlTests.swift +// Rex +// +// Created by Markus Chmelar on 07/06/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import XCTest +import ReactiveCocoa +import Result + +class UISegmentedControlTests: XCTestCase { + + func testSelectedSegmentIndexProperty() { + let s = UISegmentedControl(items: ["0", "1", "2"]) + s.selectedSegmentIndex = UISegmentedControlNoSegment + XCTAssertEqual(s.numberOfSegments, 3) + + let (pipeSignal, observer) = Signal.pipe() + s.rex_selectedSegmentIndex <~ SignalProducer(signal: pipeSignal) + + XCTAssertEqual(s.selectedSegmentIndex, UISegmentedControlNoSegment) + observer.sendNext(1) + XCTAssertEqual(s.selectedSegmentIndex, 1) + observer.sendNext(2) + XCTAssertEqual(s.selectedSegmentIndex, 2) + } +} From 1cf75e37497eb44e1bbe40f22824a13b6cf25b5e Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Wed, 29 Jun 2016 14:40:23 +0100 Subject: [PATCH 06/15] bumped RAC to 4.2.1 --- Cartfile | 2 +- Cartfile.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile b/Cartfile index a4583d1..fb56d4c 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "ReactiveCocoa/ReactiveCocoa" ~> 4.2 +github "ReactiveCocoa/ReactiveCocoa" ~> 4.2.1 diff --git a/Cartfile.resolved b/Cartfile.resolved index a82c40d..421a6de 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "antitypical/Result" "2.0.0" -github "ReactiveCocoa/ReactiveCocoa" "v4.2.0" +github "antitypical/Result" "2.1.1" +github "ReactiveCocoa/ReactiveCocoa" "v4.2.1" From 916043b68fe2b58602dfd23ddb2f08974b3f942d Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Wed, 29 Jun 2016 14:40:39 +0100 Subject: [PATCH 07/15] RAC 4.2.1 now provides debounce --- Source/Signal.swift | 17 +------------ Source/SignalProducer.swift | 11 -------- Tests/SignalTests.swift | 51 ------------------------------------- 3 files changed, 1 insertion(+), 78 deletions(-) diff --git a/Source/Signal.swift b/Source/Signal.swift index 7ae00bf..4bc4837 100644 --- a/Source/Signal.swift +++ b/Source/Signal.swift @@ -77,22 +77,7 @@ extension SignalType { return disposable } } - - /// Enforces that at least `interval` time passes before forwarding a value. If a - /// new value arrives, the previous one is dropped and the `interval` delay starts - /// again. Error events are immediately forwarded, even if there's a queued value. - /// - /// This operator is useful for scenarios like type-to-search where you want to - /// wait for a "lull" in typing before kicking off a search request. - @warn_unused_result(message="Did you forget to call `observe` on the signal?") - public func debounce(interval: NSTimeInterval, onScheduler scheduler: DateSchedulerType) -> Signal { - precondition(interval >= 0) - - return flatMap(.Latest) { - SignalProducer(value: $0).delay(interval, onScheduler: scheduler) - } - } - + /// Forwards a value and then mutes the signal by dropping all subsequent values /// for `interval` seconds. Once time elapses the next new value will be forwarded /// and repeat the muting process. Error events are immediately forwarded even while diff --git a/Source/SignalProducer.swift b/Source/SignalProducer.swift index f94c3e8..c977428 100644 --- a/Source/SignalProducer.swift +++ b/Source/SignalProducer.swift @@ -81,17 +81,6 @@ extension SignalProducerType { return lift { $0.timeoutAfter(interval, withEvent: event, onScheduler: scheduler) } } - /// Enforces that at least `interval` time passes before forwarding a value. If a - /// new value arrives, the previous one is dropped and the `interval` delay starts - /// again. Error events are immediately forwarded, even if there's a queued value. - /// - /// This operator is useful for scenarios like type-to-search where you want to - /// wait for a "lull" in typing before kicking off a search request. - @warn_unused_result(message="Did you forget to call `start` on the producer?") - public func debounce(interval: NSTimeInterval, onScheduler scheduler: DateSchedulerType) -> SignalProducer { - return lift { $0.debounce(interval, onScheduler: scheduler) } - } - /// Forwards a value and then mutes the producer by dropping all subsequent values /// for `interval` seconds. Once time elapses the next new value will be forwarded /// and repeat the muting process. Error events are immediately forwarded even while diff --git a/Tests/SignalTests.swift b/Tests/SignalTests.swift index 3a1d796..af55e1b 100644 --- a/Tests/SignalTests.swift +++ b/Tests/SignalTests.swift @@ -130,57 +130,6 @@ final class SignalTests: XCTestCase { XCTAssert(values == [1, 2, 3]) } - func testDebounceValues() { - let scheduler = TestScheduler() - let (signal, observer) = Signal.pipe() - var value = -1 - - signal - .debounce(1, onScheduler: scheduler) - .observeNext { value = $0 } - - scheduler.schedule { observer.sendNext(1) } - scheduler.advance() - XCTAssertEqual(value, -1) - - scheduler.advanceByInterval(1) - XCTAssertEqual(value, 1) - - scheduler.schedule { observer.sendNext(2) } - scheduler.advance() - XCTAssertEqual(value, 1) - - scheduler.schedule { observer.sendNext(3) } - scheduler.advance() - XCTAssertEqual(value, 1) - - scheduler.advanceByInterval(1) - XCTAssertEqual(value, 3) - } - - func testDebounceFailure() { - let scheduler = TestScheduler() - let (signal, observer) = Signal.pipe() - var value = -1 - var failed = false - - signal - .debounce(1, onScheduler: scheduler) - .observe(Observer( - next: { value = $0 }, - failed: { _ in failed = true } - )) - - scheduler.schedule { observer.sendNext(1) } - scheduler.advance() - XCTAssertEqual(value, -1) - - scheduler.schedule { observer.sendFailed(.Default) } - scheduler.advance() - XCTAssertTrue(failed) - XCTAssertEqual(value, -1) - } - func testMuteForValues() { let scheduler = TestScheduler() let (signal, observer) = Signal.pipe() From a7f29efc1f9d00f0e2dce14d7ed3cd89abf968f8 Mon Sep 17 00:00:00 2001 From: David Rodrigues Date: Tue, 5 Jul 2016 10:53:40 +0100 Subject: [PATCH 08/15] Changed `rex_text` bindable property to be an optional string (#133) * Changed `rex_text` bindable property to be an optional string Fixes #125, #129. * Updated UILabel's text property test to include nullability --- Source/UIKit/UILabel.swift | 5 +++-- Tests/UIKit/UILabelTests.swift | 4 +++- Tests/UIKit/UITableViewCellTests.swift | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/UIKit/UILabel.swift b/Source/UIKit/UILabel.swift index 1ec0c25..c47746f 100644 --- a/Source/UIKit/UILabel.swift +++ b/Source/UIKit/UILabel.swift @@ -11,8 +11,8 @@ import UIKit extension UILabel { /// Wraps a label's `text` value in a bindable property. - public var rex_text: MutableProperty { - return associatedProperty(self, keyPath: "text") + public var rex_text: MutableProperty { + return associatedProperty(self, key: &attributedTextKey, initial: { $0.text }, setter: { $0.text = $1 }) } /// Wraps a label's `attributedText` value in a bindable property. @@ -26,5 +26,6 @@ extension UILabel { } } +private var textKey: UInt8 = 0 private var attributedTextKey: UInt8 = 0 private var textColorKey: UInt8 = 0 diff --git a/Tests/UIKit/UILabelTests.swift b/Tests/UIKit/UILabelTests.swift index 8d10099..9356b9f 100644 --- a/Tests/UIKit/UILabelTests.swift +++ b/Tests/UIKit/UILabelTests.swift @@ -35,13 +35,15 @@ class UILabelTests: XCTestCase { let label = UILabel(frame: CGRectZero) label.text = "" - let (pipeSignal, observer) = Signal.pipe() + let (pipeSignal, observer) = Signal.pipe() label.rex_text <~ SignalProducer(signal: pipeSignal) observer.sendNext(firstChange) XCTAssertEqual(label.text, firstChange) observer.sendNext(secondChange) XCTAssertEqual(label.text, secondChange) + observer.sendNext(nil) + XCTAssertNil(label.text) } func testAttributedTextPropertyDoesntCreateRetainCycle() { diff --git a/Tests/UIKit/UITableViewCellTests.swift b/Tests/UIKit/UITableViewCellTests.swift index 72eba7b..d08767f 100644 --- a/Tests/UIKit/UITableViewCellTests.swift +++ b/Tests/UIKit/UITableViewCellTests.swift @@ -24,6 +24,7 @@ class UITableViewCellTests: XCTestCase { label.rex_text <~ titleProperty .producer + .map(Optional.init) // TODO: Remove in the future, binding with optionals will be available soon in RAC 5. Reference: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/2852 .takeUntil(cell.rex_prepareForReuse) XCTAssertEqual(label.text, "John") From 2a957a4d7523878d307e799a6284f3d374c18007 Mon Sep 17 00:00:00 2001 From: Neil Pankey Date: Tue, 5 Jul 2016 13:17:57 -0700 Subject: [PATCH 09/15] Update README to point at RACCommunity --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c9f5cd1..a7670db 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Rex [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) Extensions for [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) that may not fit in the core framework. -New development targets RAC 4/Swift 2/Xcode 7. For RAC 3 support [see the 0.5 release](https://github.com/neilpa/Rex/releases/tag/v0.5.0). +New development targets RAC 4/Swift 2/Xcode 7. For RAC 3 support [see the 0.5 +release](https://github.com/RACCommunity/Rex/releases/tag/v0.5.0). ## Signal All `Signal` operators are available for `SignaProducer`s too via explicit `lift`ing. @@ -54,7 +55,7 @@ Flexible way to bind `CocoaAction` to the press of button. In addition the butto ```swift let downloadAction = Action { _ in - let url = NSURL(string: "https://github.com/neilpa/Rex/archive/master.zip") + let url = NSURL(string: "https://github.com/RACCommunity/Rex/archive/master.zip") let request = NSURLRequest(URL: url!) return NSURLSession.sharedSession().rac_dataWithRequest(request).map { $0.0 } } From acd90055e9678cc93b1f4f7cecf55a42bdd7b61b Mon Sep 17 00:00:00 2001 From: Evgeny Kazakov Date: Wed, 6 Jul 2016 13:34:11 +0300 Subject: [PATCH 10/15] Implement rex_animating for UIActivityIndicatorView (#134) --- Rex.xcodeproj/project.pbxproj | 12 +++++ Source/UIKit/UIActivityIndicatorView.swift | 39 ++++++++++++++++ .../UIKit/UIActivityIndicatorViewTests.swift | 44 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Source/UIKit/UIActivityIndicatorView.swift create mode 100644 Tests/UIKit/UIActivityIndicatorViewTests.swift diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index d172069..d36ecdf 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -8,6 +8,10 @@ /* Begin PBXBuildFile section */ 4238D5961B4D5950008534C0 /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4238D5951B4D5950008534C0 /* NSTextField.swift */; }; + 45CED46F1D27C1E300788BDC /* UIActivityIndicatorViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */; }; + 45CED4701D27C1E400788BDC /* UIActivityIndicatorViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */; }; + 45CED4711D27C1EB00788BDC /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */; }; + 45CED4721D27C1EC00788BDC /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */; }; 5F2739701CF6375500C44108 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */; }; 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */; }; 5B7F81E31D0842AD0014B06D /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */; }; @@ -206,6 +210,8 @@ /* Begin PBXFileReference section */ 4238D5951B4D5950008534C0 /* NSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NSTextField.swift; path = AppKit/NSTextField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; + 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorViewTests.swift; sourceTree = ""; }; 5173EBC51B625A2600C9B48E /* UIBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarButtonItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; @@ -445,6 +451,7 @@ D86FFBD31B34B0E2001A89B3 /* UIKit */ = { isa = PBXGroup; children = ( + 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */, 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */, 5173EBC51B625A2600C9B48E /* UIBarItem.swift */, D86FFBDC1B34B691001A89B3 /* UIButton.swift */, @@ -491,6 +498,7 @@ D8F073131B861B110047D546 /* UIKit */ = { isa = PBXGroup; children = ( + 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */, 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */, 8295FD881B873490007C9000 /* UIButtonTests.swift */, 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, @@ -868,6 +876,7 @@ D86FFBD61B34B116001A89B3 /* UIControl.swift in Sources */, D8F0973F1B17F31E002E15BA /* NSData.swift in Sources */, D86FFBDD1B34B691001A89B3 /* UIButton.swift in Sources */, + 45CED4711D27C1EB00788BDC /* UIActivityIndicatorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -876,6 +885,7 @@ buildActionMask = 2147483647; files = ( E6933BEA1CD9C335006F7CE7 /* UIProgressViewTests.swift in Sources */, + 45CED46F1D27C1E300788BDC /* UIActivityIndicatorViewTests.swift in Sources */, CC02C18B1CCA704F0025CC04 /* ActionTests.swift in Sources */, 7DC325801CC6FD2100746D88 /* UITableViewHeaderFooterViewTests.swift in Sources */, 8289A2E11BD7EF1F0097FB60 /* UIImageViewTests.swift in Sources */, @@ -942,6 +952,7 @@ D8715DC81C211553005F4191 /* UIButton.swift in Sources */, D8715DC71C211553005F4191 /* UIBarItem.swift in Sources */, D8715DC11C2112D6005F4191 /* NSUserDefaults.swift in Sources */, + 45CED4721D27C1EC00788BDC /* UIActivityIndicatorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -964,6 +975,7 @@ D8715DE41C211643005F4191 /* UIImageViewTests.swift in Sources */, D8715DE31C211643005F4191 /* UILabelTests.swift in Sources */, D8715DE01C211643005F4191 /* UIBarButtonItemTests.swift in Sources */, + 45CED4701D27C1E400788BDC /* UIActivityIndicatorViewTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/UIKit/UIActivityIndicatorView.swift b/Source/UIKit/UIActivityIndicatorView.swift new file mode 100644 index 0000000..e9c8308 --- /dev/null +++ b/Source/UIKit/UIActivityIndicatorView.swift @@ -0,0 +1,39 @@ +// +// UIActivityIndicatorView.swift +// Rex +// +// Created by Evgeny Kazakov on 02/07/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa +import UIKit + +extension UIActivityIndicatorView { + + /// Returns whether the receiver is animating. + /// `true` if the receiver is animating, otherwise `false`. + /// + /// Setting the value of this property to `true` starts animation of the progress indicator, + /// and setting it to `false` stops animation. + public var animating: Bool { + get { + return isAnimating() + } + set { + if newValue { + startAnimating() + } else { + stopAnimating() + } + } + } + + /// Wraps an indicator's `animating` state in a bindable property. + public var rex_animating: MutableProperty { + return associatedProperty(self, key: &animatingKey, initial: { $0.animating }, setter: { $0.animating = $1 }) + } + +} + +private var animatingKey: UInt8 = 0 diff --git a/Tests/UIKit/UIActivityIndicatorViewTests.swift b/Tests/UIKit/UIActivityIndicatorViewTests.swift new file mode 100644 index 0000000..82b4df5 --- /dev/null +++ b/Tests/UIKit/UIActivityIndicatorViewTests.swift @@ -0,0 +1,44 @@ +// +// UIActivityIndicatorViewTests.swift +// Rex +// +// Created by Evgeny Kazakov on 02/07/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import XCTest +import ReactiveCocoa +import Result + +class UIActivityIndicatorTests: XCTestCase { + + weak var _activityIndicatorView: UIActivityIndicatorView? + + override func tearDown() { + XCTAssert(_activityIndicatorView == nil, "Retain cycle detected in UIActivityIndicatorView properties") + super.tearDown() + } + + func testRexAnimatingProperty() { + let indicatorView = UIActivityIndicatorView(frame: CGRectZero) + _activityIndicatorView = indicatorView + + let (pipeSignal, observer) = Signal.pipe() + indicatorView.rex_animating <~ SignalProducer(signal: pipeSignal) + + observer.sendNext(true) + XCTAssertTrue(indicatorView.animating) + observer.sendNext(false) + XCTAssertFalse(indicatorView.animating) + } + + func testAnimatingProperty() { + let indicatorView = UIActivityIndicatorView(frame: CGRectZero) + + indicatorView.animating = true + XCTAssertTrue(indicatorView.isAnimating()) + + indicatorView.animating = false + XCTAssertFalse(indicatorView.isAnimating()) + } +} From 8a3389d5e00cb16fdd5fc08c03a2a4b844e3761f Mon Sep 17 00:00:00 2001 From: Syo Ikeda Date: Wed, 6 Jul 2016 20:24:01 +0900 Subject: [PATCH 11/15] Remove confusing UIActivityIndicatorView.animating extended property (#139) --- Source/UIKit/UIActivityIndicatorView.swift | 28 ++++++------------- .../UIKit/UIActivityIndicatorViewTests.swift | 14 ++-------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/Source/UIKit/UIActivityIndicatorView.swift b/Source/UIKit/UIActivityIndicatorView.swift index e9c8308..179b795 100644 --- a/Source/UIKit/UIActivityIndicatorView.swift +++ b/Source/UIKit/UIActivityIndicatorView.swift @@ -11,27 +11,17 @@ import UIKit extension UIActivityIndicatorView { - /// Returns whether the receiver is animating. - /// `true` if the receiver is animating, otherwise `false`. - /// - /// Setting the value of this property to `true` starts animation of the progress indicator, - /// and setting it to `false` stops animation. - public var animating: Bool { - get { - return isAnimating() - } - set { - if newValue { - startAnimating() + /// Wraps an indicator's `isAnimating()` state in a bindable property. + /// Setting a new value to the property would call `startAnimating()` or + /// `stopAnimating()` depending on the value. + public var rex_animating: MutableProperty { + return associatedProperty(self, key: &animatingKey, initial: { $0.isAnimating() }, setter: { host, animating in + if animating { + host.startAnimating() } else { - stopAnimating() + host.stopAnimating() } - } - } - - /// Wraps an indicator's `animating` state in a bindable property. - public var rex_animating: MutableProperty { - return associatedProperty(self, key: &animatingKey, initial: { $0.animating }, setter: { $0.animating = $1 }) + }) } } diff --git a/Tests/UIKit/UIActivityIndicatorViewTests.swift b/Tests/UIKit/UIActivityIndicatorViewTests.swift index 82b4df5..d47d1cb 100644 --- a/Tests/UIKit/UIActivityIndicatorViewTests.swift +++ b/Tests/UIKit/UIActivityIndicatorViewTests.swift @@ -19,7 +19,7 @@ class UIActivityIndicatorTests: XCTestCase { super.tearDown() } - func testRexAnimatingProperty() { + func testAnimatingProperty() { let indicatorView = UIActivityIndicatorView(frame: CGRectZero) _activityIndicatorView = indicatorView @@ -27,18 +27,8 @@ class UIActivityIndicatorTests: XCTestCase { indicatorView.rex_animating <~ SignalProducer(signal: pipeSignal) observer.sendNext(true) - XCTAssertTrue(indicatorView.animating) - observer.sendNext(false) - XCTAssertFalse(indicatorView.animating) - } - - func testAnimatingProperty() { - let indicatorView = UIActivityIndicatorView(frame: CGRectZero) - - indicatorView.animating = true XCTAssertTrue(indicatorView.isAnimating()) - - indicatorView.animating = false + observer.sendNext(false) XCTAssertFalse(indicatorView.isAnimating()) } } From 877ac02e09c988a2e495c8605e7f64f729891c7f Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Wed, 6 Jul 2016 12:06:48 +0100 Subject: [PATCH 12/15] Automatically upload .framework when tagging --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index 265cfaf..2f4d87f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,3 +25,15 @@ script: notifications: email: false +before_deploy: + - carthage build --no-skip-current + - carthage archive Rex +deploy: + provider: releases + api_key: + secure: lNuRUOxMrw2YSsibAAK+8GH35KfvgheiyBGsqD7Rqwjzf3orhTmNbWeRt38YiUxzmNZSzhQdcglwJnw9ymXuGyynOwWxRPbQnX0KE+fTIyoyZrxIwxkqyU6aGzgi1bGa/URKU83pDZsBrfPeLa89w5PYZ8UVPVs+alD796WTjNoXFhxvj4cZtT65Pqk4usSgq3l6GRGzVDmaOgiiDT509LdTi+x+BjRuQcP2wvxCKWGWtaR4COo+PH96mQ/vcykL97zyxScWOBRbVq5YEeqHC/OHV7kXMLRK6X0SBcpB8otV9ObxN76zqZjpxQ59/g0WN4bUogd5VYT11dxjSAQDNAtS/H0iHcw6ojDuAobQbD1W4Um6tHBPaT4ZVXDack8J2gSa2DhiFBt198XRSEWFczff5LevxFJaDwqLwEj5qtB6bkdvarsaZdlUzaPfmBfEjmLQdQmiEe82xYb+VcZK0SlgbNulvSt8J2FpLRcVQSs7Ef75zMKQECtxJCsOhSFGT+1Zal2YEk70HFdbkNE0DS57AX0hmgDFF0WhK1ZzpBgy432Hyo71srAJMyalMfF1zuc5kHSssezQ30p7ZdegDnkvbt0lhjGFgUlbKIoLS9e21uo3i96XZQagL5k/mZPxaq1hf1bsH+ow+Jcg3X7b8RJRqniHxASyffdzHYbmfyE= + file: Rex.framework.zip + skip_cleanup: true + on: + repo: RACCommunity/Rex + tags: true From 7d61133d636c61442b15b03c85e8ff265ddc8b07 Mon Sep 17 00:00:00 2001 From: David Rodrigues Date: Wed, 6 Jul 2016 14:20:41 +0100 Subject: [PATCH 13/15] Add bindable property to wrap a control's value (#119) * Provide a `setUp` to properly configure the associated property The introduced `setUp` is extremely helpful to setup any signals that may affect the property once. * Add bindable property to wrap a control's value This property is common to every `UIControl` and can be used by providing a getter and a setter for the value wrapped. This property uses `UIControlEvents.ValueChanged` and `UIControlEvents.EditingChanged` events to detect changes and keep the value up-to-date. This kind of logic can be reused instead of being defined and used for each control. * Use `setUp` to install the dependent signal of `rex_dismissAnimated` This prevents repetition of events in cases where the property is accessed more than once (each access adds a new `rac_signalForSelector` for the dismiss of the view controller). * Fixes #129 - UITextField's text bindable property should be of optional type --- Rex.xcodeproj/project.pbxproj | 72 +++++++++++-------- Source/Foundation/Association.swift | 4 +- Source/UIKit/UIControl.swift | 17 +++++ Source/UIKit/UIDatePicker.swift | 20 ++---- Source/UIKit/UISwitch.swift | 13 +--- Source/UIKit/UITextField.swift | 23 ++++-- Source/UIKit/UIViewController.swift | 13 ++-- ...ol+EnableSendActionsForControlEvents.swift | 57 +++++++++++++++ Tests/UIKit/UIDatePickerTests.swift | 3 +- Tests/UIKit/UISwitchTests.swift | 14 ++-- Tests/UIKit/UITextFieldTests.swift | 12 ++-- 11 files changed, 169 insertions(+), 79 deletions(-) create mode 100644 Tests/Helpers/UIControl+EnableSendActionsForControlEvents.swift diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index d36ecdf..f53ccb7 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -16,8 +16,10 @@ 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */; }; 5B7F81E31D0842AD0014B06D /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */; }; 5B7F81E41D0842B50014B06D /* UISegmentedControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */; }; + 7D0DABAA1CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0DABA91CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift */; }; 7D2AA99B1CB6EFEB008AB5C9 /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */; }; 7D2AA99D1CB6F275008AB5C9 /* UISwitchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */; }; + 7D5FE3081CD4B04E00834675 /* UITextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7932E851C4B420A00086F3C /* UITextFieldTests.swift */; }; 7DBD48F31CC8141D0077AD4F /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBD48F21CC8141D0077AD4F /* Reusable.swift */; }; 7DBD48F41CC8141D0077AD4F /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBD48F21CC8141D0077AD4F /* Reusable.swift */; }; 7DC325741CC6FCF100746D88 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC325721CC6FCF100746D88 /* UITableViewCell.swift */; }; @@ -56,8 +58,8 @@ CC02C18B1CCA704F0025CC04 /* ActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC02C1881CCA704C0025CC04 /* ActionTests.swift */; }; CC02C18C1CCA704F0025CC04 /* ActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC02C1881CCA704C0025CC04 /* ActionTests.swift */; }; D8003E941AFEC3D400D7D3C5 /* Rex.h in Headers */ = {isa = PBXBuildFile; fileRef = D8003E931AFEC3D400D7D3C5 /* Rex.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D8003EB41AFEC6B000D7D3C5 /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8003EAD1AFEC68A00D7D3C5 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D8003EB51AFEC6B000D7D3C5 /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8003EAE1AFEC68A00D7D3C5 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D8003EB41AFEC6B000D7D3C5 /* ReactiveCocoa.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8003EAD1AFEC68A00D7D3C5 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D8003EB51AFEC6B000D7D3C5 /* Result.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8003EAE1AFEC68A00D7D3C5 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D8003EB91AFEC7A900D7D3C5 /* SignalProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8003EB81AFEC7A900D7D3C5 /* SignalProducer.swift */; }; D8003EBD1AFED01000D7D3C5 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8003EBC1AFED01000D7D3C5 /* Signal.swift */; }; D8003EC21AFED30F00D7D3C5 /* SignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8003EBE1AFED2F800D7D3C5 /* SignalTests.swift */; }; @@ -71,8 +73,8 @@ D83457331AFEE4930070616A /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8003EAE1AFEC68A00D7D3C5 /* Result.framework */; }; D83457351AFEE4B20070616A /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8003EC91AFEE3ED00D7D3C5 /* ReactiveCocoa.framework */; }; D83457361AFEE4B20070616A /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8003ECA1AFEE3ED00D7D3C5 /* Result.framework */; }; - D83457391AFEE4BE0070616A /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8003EC91AFEE3ED00D7D3C5 /* ReactiveCocoa.framework */; }; - D834573A1AFEE4BE0070616A /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8003ECA1AFEE3ED00D7D3C5 /* Result.framework */; }; + D83457391AFEE4BE0070616A /* ReactiveCocoa.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8003EC91AFEE3ED00D7D3C5 /* ReactiveCocoa.framework */; }; + D834573A1AFEE4BE0070616A /* Result.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8003ECA1AFEE3ED00D7D3C5 /* Result.framework */; }; D834573C1AFEE57E0070616A /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8003EC91AFEE3ED00D7D3C5 /* ReactiveCocoa.framework */; }; D834573D1AFEE57E0070616A /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8003ECA1AFEE3ED00D7D3C5 /* Result.framework */; }; D83457411AFEE6050070616A /* SignalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8003EBE1AFED2F800D7D3C5 /* SignalTests.swift */; }; @@ -125,8 +127,8 @@ D8715DE51C211643005F4191 /* UIViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8289A2E61BD7F7730097FB60 /* UIViewTests.swift */; }; D8715DE61C21170C005F4191 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8715DC21C211310005F4191 /* ReactiveCocoa.framework */; }; D8715DE71C21170C005F4191 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8715DC31C211310005F4191 /* Result.framework */; }; - D8715DE91C211739005F4191 /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8715DC21C211310005F4191 /* ReactiveCocoa.framework */; }; - D8715DEA1C211739005F4191 /* Result.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8715DC31C211310005F4191 /* Result.framework */; }; + D8715DE91C211739005F4191 /* ReactiveCocoa.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8715DC21C211310005F4191 /* ReactiveCocoa.framework */; }; + D8715DEA1C211739005F4191 /* Result.framework in (null) */ = {isa = PBXBuildFile; fileRef = D8715DC31C211310005F4191 /* Result.framework */; }; D8A454061BD26A1A00C9E790 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454051BD26A1A00C9E790 /* Property.swift */; }; D8A454071BD26A1A00C9E790 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454051BD26A1A00C9E790 /* Property.swift */; }; D8A454091BD2772700C9E790 /* PropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A454081BD2772700C9E790 /* PropertyTests.swift */; }; @@ -140,12 +142,12 @@ D8F0973F1B17F31E002E15BA /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0973A1B17F2F7002E15BA /* NSData.swift */; }; D8F097441B17F3C8002E15BA /* NSObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097431B17F3C8002E15BA /* NSObject.swift */; }; D8F097451B17F3C8002E15BA /* NSObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097431B17F3C8002E15BA /* NSObject.swift */; }; - D8F0974A1B17F5E1002E15BA /* NSObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097471B17F5DD002E15BA /* NSObjectTests.swift */; }; - D8F0974B1B17F5E2002E15BA /* NSObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097471B17F5DD002E15BA /* NSObjectTests.swift */; }; E6933BEA1CD9C335006F7CE7 /* UIProgressViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */; }; E6933BEB1CD9C363006F7CE7 /* UIProgressViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */; }; E6933BEC1CD9C37D006F7CE7 /* UIProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */; }; E6933BED1CD9C37D006F7CE7 /* UIProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */; }; + D8F0974A1B17F5E1002E15BA /* NSObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097471B17F5DD002E15BA /* NSObjectTests.swift */; }; + D8F0974B1B17F5E2002E15BA /* NSObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F097471B17F5DD002E15BA /* NSObjectTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -173,48 +175,51 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - D8003EB21AFEC6A800D7D3C5 /* CopyFiles */ = { + D8003EB21AFEC6A800D7D3C5 /* (null) */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 16; files = ( - D8003EB41AFEC6B000D7D3C5 /* ReactiveCocoa.framework in CopyFiles */, - D8003EB51AFEC6B000D7D3C5 /* Result.framework in CopyFiles */, + D8003EB41AFEC6B000D7D3C5 /* ReactiveCocoa.framework in (null) */, + D8003EB51AFEC6B000D7D3C5 /* Result.framework in (null) */, ); runOnlyForDeploymentPostprocessing = 0; }; - D83457371AFEE4B80070616A /* CopyFiles */ = { + D83457371AFEE4B80070616A /* (null) */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 16; files = ( - D83457391AFEE4BE0070616A /* ReactiveCocoa.framework in CopyFiles */, - D834573A1AFEE4BE0070616A /* Result.framework in CopyFiles */, + D83457391AFEE4BE0070616A /* ReactiveCocoa.framework in (null) */, + D834573A1AFEE4BE0070616A /* Result.framework in (null) */, ); runOnlyForDeploymentPostprocessing = 0; }; - D8715DE81C21172A005F4191 /* CopyFiles */ = { + D8715DE81C21172A005F4191 /* (null) */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 16; files = ( - D8715DE91C211739005F4191 /* ReactiveCocoa.framework in CopyFiles */, - D8715DEA1C211739005F4191 /* Result.framework in CopyFiles */, + D8715DE91C211739005F4191 /* ReactiveCocoa.framework in (null) */, + D8715DEA1C211739005F4191 /* Result.framework in (null) */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 4238D5951B4D5950008534C0 /* NSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NSTextField.swift; path = AppKit/NSTextField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorViewTests.swift; sourceTree = ""; }; + 4238D5951B4D5950008534C0 /* NSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NSTextField.swift; path = AppKit/NSTextField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = ""; }; + 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControlTests.swift; sourceTree = ""; }; 5173EBC51B625A2600C9B48E /* UIBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarButtonItem.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + 7D0DABA91CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+EnableSendActionsForControlEvents.swift"; sourceTree = ""; }; 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizerTests.swift; sourceTree = ""; }; 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = ""; }; 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControlTests.swift; sourceTree = ""; }; @@ -275,11 +280,11 @@ D8A454081BD2772700C9E790 /* PropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PropertyTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D8F073141B861B3A0047D546 /* UILabelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UILabelTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D8F0973A1B17F2F7002E15BA /* NSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NSData.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProgressView.swift; sourceTree = ""; }; + E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProgressViewTests.swift; sourceTree = ""; }; D8F0973C1B17F30D002E15BA /* NSUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NSUserDefaults.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D8F097431B17F3C8002E15BA /* NSObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NSObject.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D8F097471B17F5DD002E15BA /* NSObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NSObjectTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProgressView.swift; sourceTree = ""; }; - E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProgressViewTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -357,6 +362,14 @@ name = AppKit; sourceTree = ""; }; + 7D0DABA81CCC380700B6CD2B /* Helpers */ = { + isa = PBXGroup; + children = ( + 7D0DABA91CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift */, + ); + path = Helpers; + sourceTree = ""; + }; D8003E841AFEC3D400D7D3C5 = { isa = PBXGroup; children = ( @@ -400,6 +413,7 @@ D8003EBE1AFED2F800D7D3C5 /* SignalTests.swift */, D8003EC01AFED30100D7D3C5 /* SignalProducerTests.swift */, D8F097461B17F5BF002E15BA /* Foundation */, + 7D0DABA81CCC380700B6CD2B /* Helpers */, D8F073131B861B110047D546 /* UIKit */, D8003E9E1AFEC3D400D7D3C5 /* Supporting Files */, ); @@ -501,8 +515,8 @@ 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */, 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */, 8295FD881B873490007C9000 /* UIButtonTests.swift */, - 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, 8295FD851B873081007C9000 /* UIControlTests.swift */, + 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */, 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */, 8289A2E01BD7EF1F0097FB60 /* UIImageViewTests.swift */, @@ -514,8 +528,8 @@ 7DC325791CC6FD0A00746D88 /* UITableViewHeaderFooterViewTests.swift */, C7932E851C4B420A00086F3C /* UITextFieldTests.swift */, C7DCE2B51CB3C9C1001217D8 /* UITextViewTests.swift */, - C7945F121CC1DFB400DC9E37 /* UIViewControllerTests.swift */, 8289A2E61BD7F7730097FB60 /* UIViewTests.swift */, + C7945F121CC1DFB400DC9E37 /* UIViewControllerTests.swift */, ); path = UIKit; sourceTree = ""; @@ -602,7 +616,7 @@ D8003E951AFEC3D400D7D3C5 /* Sources */, D8003E961AFEC3D400D7D3C5 /* Frameworks */, D8003E971AFEC3D400D7D3C5 /* Resources */, - D8003EB21AFEC6A800D7D3C5 /* CopyFiles */, + D8003EB21AFEC6A800D7D3C5 /* (null) */, ); buildRules = ( ); @@ -639,7 +653,7 @@ D834571A1AFEE44E0070616A /* Sources */, D834571B1AFEE44E0070616A /* Frameworks */, D834571C1AFEE44E0070616A /* Resources */, - D83457371AFEE4B80070616A /* CopyFiles */, + D83457371AFEE4B80070616A /* (null) */, ); buildRules = ( ); @@ -694,7 +708,7 @@ D8715DCD1C21160A005F4191 /* Sources */, D8715DCE1C21160A005F4191 /* Frameworks */, D8715DCF1C21160A005F4191 /* Resources */, - D8715DE81C21172A005F4191 /* CopyFiles */, + D8715DE81C21172A005F4191 /* (null) */, ); buildRules = ( ); @@ -739,7 +753,7 @@ }; }; }; - buildConfigurationList = D8003E881AFEC3D400D7D3C5 /* Build configuration list for PBXProject "Rex" */; + buildConfigurationList = D8003E881AFEC3D400D7D3C5 /* Build configuration list for PBXProject "Rex-Mac" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -877,6 +891,7 @@ D8F0973F1B17F31E002E15BA /* NSData.swift in Sources */, D86FFBDD1B34B691001A89B3 /* UIButton.swift in Sources */, 45CED4711D27C1EB00788BDC /* UIActivityIndicatorView.swift in Sources */, + 7D0DABAA1CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -965,6 +980,7 @@ 7DCF5B371CC80E8E004AEE75 /* UICollectionReusableViewTests.swift in Sources */, D8715DDE1C211637005F4191 /* SignalProducerTests.swift in Sources */, D8715DE11C211643005F4191 /* UIButtonTests.swift in Sources */, + 7D5FE3081CD4B04E00834675 /* UITextFieldTests.swift in Sources */, D8715DE21C211643005F4191 /* UIControlTests.swift in Sources */, CC02C18C1CCA704F0025CC04 /* ActionTests.swift in Sources */, D8715DDF1C21163B005F4191 /* NSObjectTests.swift in Sources */, @@ -974,8 +990,8 @@ 7DC325811CC6FD2300746D88 /* UITableViewHeaderFooterViewTests.swift in Sources */, D8715DE41C211643005F4191 /* UIImageViewTests.swift in Sources */, D8715DE31C211643005F4191 /* UILabelTests.swift in Sources */, - D8715DE01C211643005F4191 /* UIBarButtonItemTests.swift in Sources */, 45CED4701D27C1E400788BDC /* UIActivityIndicatorViewTests.swift in Sources */, + D8715DE01C211643005F4191 /* UIBarButtonItemTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1406,7 +1422,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D8003E881AFEC3D400D7D3C5 /* Build configuration list for PBXProject "Rex" */ = { + D8003E881AFEC3D400D7D3C5 /* Build configuration list for PBXProject "Rex-Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( D8003EA21AFEC3D400D7D3C5 /* Debug */, diff --git a/Source/Foundation/Association.swift b/Source/Foundation/Association.swift index 0f71e3f..976b6c9 100644 --- a/Source/Foundation/Association.swift +++ b/Source/Foundation/Association.swift @@ -50,10 +50,12 @@ public func associatedProperty(host: AnyObject, keyPath: StaticStr /// This can be used as an alternative to `DynamicProperty` for creating strongly typed /// bindings on Cocoa objects. @warn_unused_result(message="Did you forget to use the property?") -public func associatedProperty(host: Host, key: UnsafePointer<()>, @noescape initial: Host -> T, setter: (Host, T) -> ()) -> MutableProperty { +public func associatedProperty(host: Host, key: UnsafePointer<()>, @noescape initial: Host -> T, setter: (Host, T) -> (), @noescape setUp: MutableProperty -> () = { _ in }) -> MutableProperty { return associatedObject(host, key: key) { host in let property = MutableProperty(initial(host)) + setUp(property) + property.producer.startWithNext { [weak host] next in if let host = host { setter(host, next) diff --git a/Source/UIKit/UIControl.swift b/Source/UIKit/UIControl.swift index d9cbc20..1ad6e9a 100644 --- a/Source/UIKit/UIControl.swift +++ b/Source/UIKit/UIControl.swift @@ -11,6 +11,7 @@ import UIKit import enum Result.NoError extension UIControl { + #if os(iOS) /// Creates a producer for the sender whenever a specified control event is triggered. @warn_unused_result(message="Did you forget to use the property?") @@ -20,6 +21,21 @@ extension UIControl { .map { $0 as? UIControl } .flatMapError { _ in SignalProducer(value: nil) } } + + /// Creates a bindable property to wrap a control's value. + /// + /// This property uses `UIControlEvents.ValueChanged` and `UIControlEvents.EditingChanged` + /// events to detect changes and keep the value up-to-date. + // + @warn_unused_result(message="Did you forget to use the property?") + class func rex_value(host: Host, getter: Host -> T, setter: (Host, T) -> ()) -> MutableProperty { + return associatedProperty(host, key: &valueChangedKey, initial: getter, setter: setter) { property in + property <~ + host.rex_controlEvents([.ValueChanged, .EditingChanged]) + .filterMap { $0 as? Host } + .filterMap(getter) + } + } #endif /// Wraps a control's `enabled` state in a bindable property. @@ -41,3 +57,4 @@ extension UIControl { private var enabledKey: UInt8 = 0 private var selectedKey: UInt8 = 0 private var highlightedKey: UInt8 = 0 +private var valueChangedKey: UInt8 = 0 diff --git a/Source/UIKit/UIDatePicker.swift b/Source/UIKit/UIDatePicker.swift index 014223c..75f0d38 100644 --- a/Source/UIKit/UIDatePicker.swift +++ b/Source/UIKit/UIDatePicker.swift @@ -5,24 +5,14 @@ // Created by Guido Marucci Blas on 3/25/16. // Copyright © 2016 Neil Pankey. All rights reserved. // -import UIKit + import ReactiveCocoa +import UIKit extension UIDatePicker { - + + // Wraps a datePicker's `date` value in a bindable property. public var rex_date: MutableProperty { - let initial = { (picker: UIDatePicker) -> NSDate in - picker.addTarget(self, action: #selector(UIDatePicker.rex_changedDate), forControlEvents: .ValueChanged) - return picker.date - } - return associatedProperty(self, key: &dateKey, initial: initial) { $0.date = $1 } - } - - @objc - private func rex_changedDate() { - rex_date.value = date + return UIControl.rex_value(self, getter: { $0.date }, setter: { $0.date = $1 }) } - } - -private var dateKey: UInt8 = 0 diff --git a/Source/UIKit/UISwitch.swift b/Source/UIKit/UISwitch.swift index 89f0b92..e02c890 100644 --- a/Source/UIKit/UISwitch.swift +++ b/Source/UIKit/UISwitch.swift @@ -11,17 +11,8 @@ import UIKit extension UISwitch { - /// Wraps a switch's `on` state in a bindable property. + /// Wraps a switch's `on` value in a bindable property. public var rex_on: MutableProperty { - - let property = associatedProperty(self, key: &onKey, initial: { $0.on }, setter: { $0.on = $1 }) - - property <~ rex_controlEvents(.ValueChanged) - .filterMap { ($0 as? UISwitch)?.on } - - return property + return UIControl.rex_value(self, getter: { $0.on }, setter: { $0.on = $1 }) } - } - -private var onKey: UInt8 = 0 diff --git a/Source/UIKit/UITextField.swift b/Source/UIKit/UITextField.swift index f73c4f4..c9fa38c 100644 --- a/Source/UIKit/UITextField.swift +++ b/Source/UIKit/UITextField.swift @@ -8,14 +8,25 @@ import ReactiveCocoa import UIKit -import enum Result.NoError extension UITextField { - /// Sends the field's string value whenever it changes. - public var rex_text: SignalProducer { - return NSNotificationCenter.defaultCenter() - .rac_notifications(UITextFieldTextDidChangeNotification, object: self) - .filterMap { ($0.object as? UITextField)?.text } + /// Wraps a textField's `text` value in a bindable property. + public var rex_text: MutableProperty { + let getter: UITextField -> String? = { $0.text } + let setter: (UITextField, String?) -> () = { $0.text = $1 } +#if os(iOS) + return UIControl.rex_value(self, getter: getter, setter: setter) +#else + return associatedProperty(self, key: &textKey, initial: getter, setter: setter) { property in + property <~ + NSNotificationCenter.defaultCenter() + .rac_notifications(UITextFieldTextDidChangeNotification, object: self) + .filterMap { ($0.object as? UITextField)?.text } + } +#endif } + } + +private var textKey: UInt8 = 0 diff --git a/Source/UIKit/UIViewController.swift b/Source/UIKit/UIViewController.swift index 63a5435..f16264b 100644 --- a/Source/UIKit/UIViewController.swift +++ b/Source/UIKit/UIViewController.swift @@ -63,13 +63,12 @@ extension UIViewController { host.dismissViewControllerAnimated(unwrapped.animated, completion: unwrapped.completion) } - let property = associatedProperty(self, key: &dismissModally, initial: initial, setter: setter) - - property <~ rac_signalForSelector(#selector(UIViewController.dismissViewControllerAnimated(_:completion:))) - .takeUntilBlock { _ in property.value != nil } - .rex_toTriggerSignal() - .map { _ in return nil } - + let property = associatedProperty(self, key: &dismissModally, initial: initial, setter: setter) { property in + property <~ self.rac_signalForSelector(#selector(UIViewController.dismissViewControllerAnimated(_:completion:))) + .takeUntilBlock { _ in property.value != nil } + .rex_toTriggerSignal() + .map { _ in return nil } + } return property } diff --git a/Tests/Helpers/UIControl+EnableSendActionsForControlEvents.swift b/Tests/Helpers/UIControl+EnableSendActionsForControlEvents.swift new file mode 100644 index 0000000..24d5db1 --- /dev/null +++ b/Tests/Helpers/UIControl+EnableSendActionsForControlEvents.swift @@ -0,0 +1,57 @@ +// +// UIControl+EnableSendActionsForControlEvents.swift +// Rex +// +// Created by David Rodrigues on 24/04/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import UIKit + +/// Unfortunately, there's an apparent limitation in using `sendActionsForControlEvents` +/// on unit-tests for any control besides `UIButton` which is very unfortunate since we +/// want test our bindings for `UIDatePicker`, `UISwitch`, `UITextField` and others +/// in the future. To be able to test them, we're now using swizzling to manually invoke +/// the pair target+action. +extension UIControl { + + public override class func initialize() { + + struct Static { + static var token: dispatch_once_t = 0 + } + + if self !== UIControl.self { + return + } + + dispatch_once(&Static.token) { + + let originalSelector = #selector(UIControl.sendAction(_:to:forEvent:)) + let swizzledSelector = #selector(UIControl.rex_sendAction(_:to:forEvent:)) + + let originalMethod = class_getInstanceMethod(self, originalSelector) + let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) + + let didAddMethod = class_addMethod(self, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)) + + if didAddMethod { + class_replaceMethod(self, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)) + } else { + method_exchangeImplementations(originalMethod, swizzledMethod) + } + } + } + + // MARK: - Method Swizzling + + func rex_sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) { + target?.performSelector(action, withObject: self) + } +} diff --git a/Tests/UIKit/UIDatePickerTests.swift b/Tests/UIKit/UIDatePickerTests.swift index e15375f..f29dbd6 100644 --- a/Tests/UIKit/UIDatePickerTests.swift +++ b/Tests/UIKit/UIDatePickerTests.swift @@ -30,8 +30,7 @@ class UIDatePickerTests: XCTestCase { XCTAssertEqual(picker.date, date) } - // FIXME Can this actually be made to work inside XCTest? - func _testUpdatePropertyFromPicker() { + func testUpdatePropertyFromPicker() { let expectation = self.expectationWithDescription("Expected rex_date to send an event when picker's date value is changed by a UI event") defer { self.waitForExpectationsWithTimeout(2, handler: nil) } diff --git a/Tests/UIKit/UISwitchTests.swift b/Tests/UIKit/UISwitchTests.swift index ba95956..9391052 100644 --- a/Tests/UIKit/UISwitchTests.swift +++ b/Tests/UIKit/UISwitchTests.swift @@ -13,15 +13,19 @@ import Result class UISwitchTests: XCTestCase { func testOnProperty() { - let s = UISwitch(frame: CGRectZero) - s.on = false + let `switch` = UISwitch(frame: CGRectZero) + `switch`.on = false let (pipeSignal, observer) = Signal.pipe() - s.rex_on <~ SignalProducer(signal: pipeSignal) + `switch`.rex_on <~ SignalProducer(signal: pipeSignal) observer.sendNext(true) - XCTAssertTrue(s.on) + XCTAssertTrue(`switch`.on) observer.sendNext(false) - XCTAssertFalse(s.on) + XCTAssertFalse(`switch`.on) + + `switch`.on = true + `switch`.sendActionsForControlEvents(.ValueChanged) + XCTAssertTrue(`switch`.rex_on.value) } } diff --git a/Tests/UIKit/UITextFieldTests.swift b/Tests/UIKit/UITextFieldTests.swift index bed69d3..fee8e85 100644 --- a/Tests/UIKit/UITextFieldTests.swift +++ b/Tests/UIKit/UITextFieldTests.swift @@ -11,19 +11,23 @@ import UIKit import XCTest class UITextFieldTests: XCTestCase { - + func testTextProperty() { let expectation = self.expectationWithDescription("Expected `rex_text`'s value to equal to the textField's text") defer { self.waitForExpectationsWithTimeout(2, handler: nil) } - + let textField = UITextField(frame: CGRectZero) textField.text = "Test" - textField.rex_text.startWithNext { text in + textField.rex_text.signal.observeNext { text in XCTAssertEqual(text, textField.text) expectation.fulfill() } - + +#if os(iOS) + textField.sendActionsForControlEvents(.EditingChanged) +#else NSNotificationCenter.defaultCenter().postNotificationName(UITextFieldTextDidChangeNotification, object: textField) +#endif } } From 8b3e6098fa4f517dd40f27434e751190390bbce5 Mon Sep 17 00:00:00 2001 From: Rui Peres Date: Wed, 6 Jul 2016 15:41:25 +0100 Subject: [PATCH 14/15] Removed before_install step for carthage setup --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f4d87f..47dd0d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,6 @@ env: git: submodules: false -before_install: - - curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg" - - sudo installer -pkg "Carthage.pkg" -target / - - rm "Carthage.pkg" script: - set -o pipefail From 67a000f6618f0c013de3f68512ee0eb899869286 Mon Sep 17 00:00:00 2001 From: Siemen Sikkema Date: Wed, 25 May 2016 23:02:54 +0200 Subject: [PATCH 15/15] Add rex_action and rex_enabled to UIGestureRecognizer --- Rex.xcodeproj/project.pbxproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Rex.xcodeproj/project.pbxproj b/Rex.xcodeproj/project.pbxproj index f53ccb7..db44715 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 4238D5961B4D5950008534C0 /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4238D5951B4D5950008534C0 /* NSTextField.swift */; }; + 5F2739701CF6375500C44108 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */; }; + 5F2739711CF6375E00C44108 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */; }; 45CED46F1D27C1E300788BDC /* UIActivityIndicatorViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */; }; 45CED4701D27C1E400788BDC /* UIActivityIndicatorViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */; }; 45CED4711D27C1EB00788BDC /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */; }; @@ -515,8 +517,8 @@ 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */, 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */, 8295FD881B873490007C9000 /* UIButtonTests.swift */, - 8295FD851B873081007C9000 /* UIControlTests.swift */, 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, + 8295FD851B873081007C9000 /* UIControlTests.swift */, 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */, 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */, 8289A2E01BD7EF1F0097FB60 /* UIImageViewTests.swift */, @@ -528,8 +530,8 @@ 7DC325791CC6FD0A00746D88 /* UITableViewHeaderFooterViewTests.swift */, C7932E851C4B420A00086F3C /* UITextFieldTests.swift */, C7DCE2B51CB3C9C1001217D8 /* UITextViewTests.swift */, - 8289A2E61BD7F7730097FB60 /* UIViewTests.swift */, C7945F121CC1DFB400DC9E37 /* UIViewControllerTests.swift */, + 8289A2E61BD7F7730097FB60 /* UIViewTests.swift */, ); path = UIKit; sourceTree = "";