diff --git a/.travis.yml b/.travis.yml index 265cfaf..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 @@ -25,3 +21,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 diff --git a/Cartfile b/Cartfile index d637b9a..fb56d4c 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "ReactiveCocoa/ReactiveCocoa" ~> 4.1 +github "ReactiveCocoa/ReactiveCocoa" ~> 4.2.1 diff --git a/Cartfile.resolved b/Cartfile.resolved index 4726855..421a6de 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "antitypical/Result" "2.0.0" -github "ReactiveCocoa/ReactiveCocoa" "v4.1.0" +github "antitypical/Result" "2.1.1" +github "ReactiveCocoa/ReactiveCocoa" "v4.2.1" 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 } } 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 b208e35..db44715 100644 --- a/Rex.xcodeproj/project.pbxproj +++ b/Rex.xcodeproj/project.pbxproj @@ -8,8 +8,20 @@ /* 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 */; }; + 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 */; }; + 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 */; }; @@ -48,8 +60,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 */; }; @@ -63,8 +75,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 */; }; @@ -117,8 +129,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 */; }; @@ -132,12 +144,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 */ @@ -165,45 +177,54 @@ /* 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 */ + 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 = ""; }; 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 = ""; }; @@ -261,11 +282,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 */ @@ -343,6 +364,14 @@ name = AppKit; sourceTree = ""; }; + 7D0DABA81CCC380700B6CD2B /* Helpers */ = { + isa = PBXGroup; + children = ( + 7D0DABA91CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift */, + ); + path = Helpers; + sourceTree = ""; + }; D8003E841AFEC3D400D7D3C5 = { isa = PBXGroup; children = ( @@ -386,6 +415,7 @@ D8003EBE1AFED2F800D7D3C5 /* SignalTests.swift */, D8003EC01AFED30100D7D3C5 /* SignalProducerTests.swift */, D8F097461B17F5BF002E15BA /* Foundation */, + 7D0DABA81CCC380700B6CD2B /* Helpers */, D8F073131B861B110047D546 /* UIKit */, D8003E9E1AFEC3D400D7D3C5 /* Supporting Files */, ); @@ -437,16 +467,19 @@ D86FFBD31B34B0E2001A89B3 /* UIKit */ = { isa = PBXGroup; children = ( + 45CED46B1D27BB8700788BDC /* UIActivityIndicatorView.swift */, 5173EBC71B625A6800C9B48E /* UIBarButtonItem.swift */, 5173EBC51B625A2600C9B48E /* UIBarItem.swift */, D86FFBDC1B34B691001A89B3 /* UIButton.swift */, 7DCF5B311CC80D0E004AEE75 /* UICollectionReusableView.swift */, D86FFBD41B34B0FE001A89B3 /* UIControl.swift */, 9DA915A31CA6301C003723B9 /* UIDatePicker.swift */, + 5F27396C1CF6373000C44108 /* UIGestureRecognizer.swift */, 8289A2E21BD7EF740097FB60 /* UIImageView.swift */, D86FFBD71B34B242001A89B3 /* UILabel.swift */, E6933BE61CD9C0B2006F7CE7 /* UIProgressView.swift */, 7D2AA99A1CB6EFEB008AB5C9 /* UISwitch.swift */, + 5B1C882D1D0715CE000B888F /* UISegmentedControl.swift */, 7DC325721CC6FCF100746D88 /* UITableViewCell.swift */, 7DC325731CC6FCF100746D88 /* UITableViewHeaderFooterView.swift */, C7932E811C4B3EDB00086F3C /* UITextField.swift */, @@ -481,15 +514,18 @@ D8F073131B861B110047D546 /* UIKit */ = { isa = PBXGroup; children = ( + 45CED46D1D27C1D400788BDC /* UIActivityIndicatorViewTests.swift */, 8295FD8B1B873748007C9000 /* UIBarButtonItemTests.swift */, 8295FD881B873490007C9000 /* UIButtonTests.swift */, 7DCF5B351CC80E8E004AEE75 /* UICollectionReusableViewTests.swift */, 8295FD851B873081007C9000 /* UIControlTests.swift */, 9DA915A51CA63046003723B9 /* UIDatePickerTests.swift */, + 5F27396E1CF6374500C44108 /* UIGestureRecognizerTests.swift */, 8289A2E01BD7EF1F0097FB60 /* UIImageViewTests.swift */, D8F073141B861B3A0047D546 /* UILabelTests.swift */, E6933BE81CD9C1F1006F7CE7 /* UIProgressViewTests.swift */, 7D2AA99C1CB6F275008AB5C9 /* UISwitchTests.swift */, + 5B1C882F1D071639000B888F /* UISegmentedControlTests.swift */, 7DC325781CC6FD0A00746D88 /* UITableViewCellTests.swift */, 7DC325791CC6FD0A00746D88 /* UITableViewHeaderFooterViewTests.swift */, C7932E851C4B420A00086F3C /* UITextFieldTests.swift */, @@ -582,7 +618,7 @@ D8003E951AFEC3D400D7D3C5 /* Sources */, D8003E961AFEC3D400D7D3C5 /* Frameworks */, D8003E971AFEC3D400D7D3C5 /* Resources */, - D8003EB21AFEC6A800D7D3C5 /* CopyFiles */, + D8003EB21AFEC6A800D7D3C5 /* (null) */, ); buildRules = ( ); @@ -619,7 +655,7 @@ D834571A1AFEE44E0070616A /* Sources */, D834571B1AFEE44E0070616A /* Frameworks */, D834571C1AFEE44E0070616A /* Resources */, - D83457371AFEE4B80070616A /* CopyFiles */, + D83457371AFEE4B80070616A /* (null) */, ); buildRules = ( ); @@ -674,7 +710,7 @@ D8715DCD1C21160A005F4191 /* Sources */, D8715DCE1C21160A005F4191 /* Frameworks */, D8715DCF1C21160A005F4191 /* Resources */, - D8715DE81C21172A005F4191 /* CopyFiles */, + D8715DE81C21172A005F4191 /* (null) */, ); buildRules = ( ); @@ -719,7 +755,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; @@ -836,6 +872,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 */, @@ -849,11 +886,14 @@ 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 */, D8F0973F1B17F31E002E15BA /* NSData.swift in Sources */, D86FFBDD1B34B691001A89B3 /* UIButton.swift in Sources */, + 45CED4711D27C1EB00788BDC /* UIActivityIndicatorView.swift in Sources */, + 7D0DABAA1CCC381F00B6CD2B /* UIControl+EnableSendActionsForControlEvents.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +902,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 */, @@ -870,6 +911,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 */, @@ -881,6 +923,7 @@ 8295FD8D1B87374A007C9000 /* UIBarButtonItemTests.swift in Sources */, 8295FD871B87309F007C9000 /* UIControlTests.swift in Sources */, C7DCE2B71CB3C9D6001217D8 /* UITextViewTests.swift in Sources */, + 5B7F81E41D0842B50014B06D /* UISegmentedControlTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -926,6 +969,7 @@ D8715DC81C211553005F4191 /* UIButton.swift in Sources */, D8715DC71C211553005F4191 /* UIBarItem.swift in Sources */, D8715DC11C2112D6005F4191 /* NSUserDefaults.swift in Sources */, + 45CED4721D27C1EC00788BDC /* UIActivityIndicatorView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -938,6 +982,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 */, @@ -947,6 +992,7 @@ 7DC325811CC6FD2300746D88 /* UITableViewHeaderFooterViewTests.swift in Sources */, D8715DE41C211643005F4191 /* UIImageViewTests.swift in Sources */, D8715DE31C211643005F4191 /* UILabelTests.swift in Sources */, + 45CED4701D27C1E400788BDC /* UIActivityIndicatorViewTests.swift in Sources */, D8715DE01C211643005F4191 /* UIBarButtonItemTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1378,7 +1424,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/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/Source/UIKit/UIActivityIndicatorView.swift b/Source/UIKit/UIActivityIndicatorView.swift new file mode 100644 index 0000000..179b795 --- /dev/null +++ b/Source/UIKit/UIActivityIndicatorView.swift @@ -0,0 +1,29 @@ +// +// UIActivityIndicatorView.swift +// Rex +// +// Created by Evgeny Kazakov on 02/07/16. +// Copyright © 2016 Neil Pankey. All rights reserved. +// + +import ReactiveCocoa +import UIKit + +extension UIActivityIndicatorView { + + /// 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 { + host.stopAnimating() + } + }) + } + +} + +private var animatingKey: UInt8 = 0 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/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/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/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/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/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/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/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/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/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() diff --git a/Tests/UIKit/UIActivityIndicatorViewTests.swift b/Tests/UIKit/UIActivityIndicatorViewTests.swift new file mode 100644 index 0000000..d47d1cb --- /dev/null +++ b/Tests/UIKit/UIActivityIndicatorViewTests.swift @@ -0,0 +1,34 @@ +// +// 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 testAnimatingProperty() { + let indicatorView = UIActivityIndicatorView(frame: CGRectZero) + _activityIndicatorView = indicatorView + + let (pipeSignal, observer) = Signal.pipe() + indicatorView.rex_animating <~ SignalProducer(signal: pipeSignal) + + observer.sendNext(true) + XCTAssertTrue(indicatorView.isAnimating()) + observer.sendNext(false) + XCTAssertFalse(indicatorView.isAnimating()) + } +} 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) } } 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/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) + } +} 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/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) + } +} 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/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") 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 } } 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) + } }