├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Action.podspec ├── Action.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Action-macOS.xcscheme │ ├── Action-tvOS.xcscheme │ ├── Action-watchOS.xcscheme │ ├── Action.xcscheme │ └── Demo.xcscheme ├── Action.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── Cartfile ├── Cartfile.resolved ├── Changelog.md ├── Demo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── License.md ├── Package.resolved ├── Package.swift ├── Readme.md ├── Sources └── Action │ ├── Action+Extensions.swift │ ├── Action+Internal.swift │ ├── Action.h │ ├── Action.swift │ ├── CommonUI │ ├── Button+Action.swift │ └── Control+Action.swift │ ├── Info.plist │ └── UIKitExtensions │ ├── UIAlertAction+Action.swift │ ├── UIBarButtonItem+Action.swift │ └── UIRefreshControl+Action.swift ├── Tests ├── ActionTests.swift ├── ActionTests │ └── Info.plist ├── Nimble+RxTest+Ext.swift ├── TestEvents.swift ├── iOS-Tests │ ├── AlertActionTests.swift │ ├── BarButtonTests.swift │ ├── BindToTests.swift │ ├── ButtonTests.swift │ └── RefreshControlTests.swift └── macOS-Tests │ ├── BindToTests.swift │ └── NSButtonTests.swift └── bin ├── bootstrap └── bootstrap-if-needed /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | xcode: 5 | runs-on: macos-13 6 | name: Action Tests (Swift 5.0) 7 | env: 8 | DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Cache Carthage 12 | uses: actions/cache@v3 13 | with: 14 | path: Carthage 15 | key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }} 16 | restore-keys: | 17 | ${{ runner.os }}-carthage- 18 | - name: Bootstrap Carthage 19 | run: carthage bootstrap --no-use-binaries --use-xcframeworks --cache-builds 20 | - name: Tests 21 | run: | 22 | set -o pipefail && xcodebuild test clean SWIFT_VERSION=5.0 -workspace Action.xcworkspace -scheme Action -destination "platform=iOS Simulator,name=iPhone 14" | xcpretty -c --test 23 | set -o pipefail && xcodebuild test clean SWIFT_VERSION=5.0 -workspace Action.xcworkspace -scheme Action-macOS -destination "arch=x86_64" | xcpretty -c --test 24 | - name: Builds 25 | run: | 26 | set -o pipefail && xcodebuild build clean SWIFT_VERSION=5.0 -workspace Action.xcworkspace -scheme Action-watchOS -destination "platform=watchOS Simulator,name=Apple Watch Series 5 (44mm)" | xcpretty -c 27 | set -o pipefail && xcodebuild build clean SWIFT_VERSION=5.0 -workspace Action.xcworkspace -scheme Action-tvOS -destination "platform=tvOS Simulator,name=Apple TV 4K (3rd generation) (at 1080p)" | xcpretty -c 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # 32 | Carthage 33 | .DS_Store 34 | 35 | # SPM 36 | # 37 | Packages 38 | .build -------------------------------------------------------------------------------- /Action.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Action" 3 | s.version = "5.0.0" 4 | s.summary = "Abstracts actions to be performed in RxSwift." 5 | s.description = <<-DESC 6 | Encapsulates an action to be performed, usually by a button press, but also useful to pass actions to execute later 7 | (once or multiple times) without having to expose other objects. 8 | DESC 9 | s.homepage = "https://github.com/RxSwiftCommunity/Action" 10 | s.license = { :type => "MIT", :file => "License.md" } 11 | s.author = { "RxSwift Community" => "community@rxswift.org" } 12 | s.social_media_url = "http://twitter.com/ashfurrow" 13 | 14 | s.ios.deployment_target = '9.0' 15 | s.osx.deployment_target = '10.10' 16 | s.watchos.deployment_target = '3.0' 17 | s.tvos.deployment_target = '9.0' 18 | 19 | s.source = { :git => "https://github.com/RxSwiftCommunity/Action.git", :tag => s.version.to_s } 20 | s.source_files = "Sources/**/*.{swift}" 21 | 22 | s.swift_version = '5.0' 23 | 24 | s.frameworks = "Foundation" 25 | s.dependency "RxSwift", '~> 6.0' 26 | s.dependency "RxCocoa", '~> 6.0' 27 | 28 | s.watchos.exclude_files = "Control+Action.swift", "Button+Action.swift", "UIBarButtonItem+Action.swift", "UIAlertAction+Action.swift" 29 | s.osx.exclude_files = "UIBarButtonItem+Action.swift", "UIAlertAction+Action.swift" 30 | s.tvos.exclude_files = "UIBarButtonItem+Action.swift", "UIAlertAction+Action.swift" 31 | end 32 | -------------------------------------------------------------------------------- /Action.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1FCDDA631EAC31DD006EB95B /* Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0569E11DE28587007E1D0D /* Action.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 1FCDDA641EAC31EF006EB95B /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E21DE28587007E1D0D /* Action.swift */; }; 12 | 1FCDDA651EAC31EF006EB95B /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E01DE28587007E1D0D /* Action+Internal.swift */; }; 13 | 1FCDDA661EAC31EF006EB95B /* UIAlertAction+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E51DE28587007E1D0D /* UIAlertAction+Action.swift */; }; 14 | 1FCDDA671EAC31EF006EB95B /* UIBarButtonItem+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E61DE28587007E1D0D /* UIBarButtonItem+Action.swift */; }; 15 | 1FCDDA8A1EAC329E006EB95B /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E21DE28587007E1D0D /* Action.swift */; }; 16 | 1FCDDA8B1EAC329E006EB95B /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E01DE28587007E1D0D /* Action+Internal.swift */; }; 17 | 1FCDDA901EAC3308006EB95B /* Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0569E11DE28587007E1D0D /* Action.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 1FCDDA9E1EAC33C5006EB95B /* Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0569E11DE28587007E1D0D /* Action.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 3D730DE31F673BE4008534D3 /* Control+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE21F673BE4008534D3 /* Control+Action.swift */; }; 20 | 3D730DE41F673C64008534D3 /* Control+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE21F673BE4008534D3 /* Control+Action.swift */; }; 21 | 3D730DE51F673C65008534D3 /* Control+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE21F673BE4008534D3 /* Control+Action.swift */; }; 22 | 3D730DE91F673FD9008534D3 /* Button+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE81F673FD9008534D3 /* Button+Action.swift */; }; 23 | 3D730DEA1F674043008534D3 /* Button+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE81F673FD9008534D3 /* Button+Action.swift */; }; 24 | 3D730DEB1F674044008534D3 /* Button+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D730DE81F673FD9008534D3 /* Button+Action.swift */; }; 25 | 3DD965C01F5DC0E400C180FE /* Action.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCDDA821EAC3295006EB95B /* Action.framework */; }; 26 | 3DD965DE1F5DF8C500C180FE /* BindToTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD965DD1F5DF86B00C180FE /* BindToTests.swift */; }; 27 | 3DD965DF1F5DF8C900C180FE /* NSButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD965DA1F5DF79800C180FE /* NSButtonTests.swift */; }; 28 | 5ED520241E1EA199007621B9 /* BindToTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED520231E1EA199007621B9 /* BindToTests.swift */; }; 29 | 7B4BFE5420C12B7800D72FB0 /* UIRefreshControl+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4BFE5320C12B7800D72FB0 /* UIRefreshControl+Action.swift */; }; 30 | 7B4BFE5520C12B7800D72FB0 /* UIRefreshControl+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4BFE5320C12B7800D72FB0 /* UIRefreshControl+Action.swift */; }; 31 | 7B4BFE6320C290BF00D72FB0 /* RefreshControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4BFE6220C290BE00D72FB0 /* RefreshControlTests.swift */; }; 32 | 7F0569E81DE28587007E1D0D /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E01DE28587007E1D0D /* Action+Internal.swift */; }; 33 | 7F0569E91DE28587007E1D0D /* Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0569E11DE28587007E1D0D /* Action.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34 | 7F0569EA1DE28587007E1D0D /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E21DE28587007E1D0D /* Action.swift */; }; 35 | 7F0569EC1DE28587007E1D0D /* UIAlertAction+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E51DE28587007E1D0D /* UIAlertAction+Action.swift */; }; 36 | 7F0569ED1DE28587007E1D0D /* UIBarButtonItem+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E61DE28587007E1D0D /* UIBarButtonItem+Action.swift */; }; 37 | 7F0569F61DE288EB007E1D0D /* AlertActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569F11DE288EB007E1D0D /* AlertActionTests.swift */; }; 38 | 7F0569F71DE288EB007E1D0D /* BarButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569F21DE288EB007E1D0D /* BarButtonTests.swift */; }; 39 | 7F0569F81DE288EB007E1D0D /* ButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569F31DE288EB007E1D0D /* ButtonTests.swift */; }; 40 | 7F612AA01D7F103A00B93BC5 /* Action.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE73AD201CDCD101006F8B98 /* Action.framework */; }; 41 | 7F612ACF1D7F13B800B93BC5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F612ACE1D7F13B800B93BC5 /* AppDelegate.swift */; }; 42 | 7F612AD11D7F13B800B93BC5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F612AD01D7F13B800B93BC5 /* ViewController.swift */; }; 43 | 7F612AD41D7F13B800B93BC5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F612AD21D7F13B800B93BC5 /* Main.storyboard */; }; 44 | 7F612AD61D7F13B800B93BC5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F612AD51D7F13B800B93BC5 /* Assets.xcassets */; }; 45 | 7F612AD91D7F13B800B93BC5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F612AD71D7F13B800B93BC5 /* LaunchScreen.storyboard */; }; 46 | 7FB791EC1D7F1BB600789D53 /* Action.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE73AD201CDCD101006F8B98 /* Action.framework */; }; 47 | C41E08EE2237D26D0039D213 /* Action+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DE00042229758700FB50AB /* Action+Extensions.swift */; }; 48 | C41E08EF2237D2700039D213 /* Action+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DE00042229758700FB50AB /* Action+Extensions.swift */; }; 49 | C41E08F02237D2740039D213 /* Action+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DE00042229758700FB50AB /* Action+Extensions.swift */; }; 50 | C4BFF33D2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BFF33C2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift */; }; 51 | C4BFF33E2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BFF33C2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift */; }; 52 | C4BFF3402282B79F00D1AA27 /* TestEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BFF33F2282B79F00D1AA27 /* TestEvents.swift */; }; 53 | C4BFF3412282B79F00D1AA27 /* TestEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BFF33F2282B79F00D1AA27 /* TestEvents.swift */; }; 54 | C4D3237E2285ADF7004B05A5 /* ActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3237D2285ADF7004B05A5 /* ActionTests.swift */; }; 55 | C4D3237F2285AEAD004B05A5 /* ActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3237D2285ADF7004B05A5 /* ActionTests.swift */; }; 56 | C4DE00052229758700FB50AB /* Action+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DE00042229758700FB50AB /* Action+Extensions.swift */; }; 57 | CF23D7362ACBB6530007A649 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = CF23D7352ACBB6530007A649 /* Quick */; }; 58 | CF23D7392ACBB67A0007A649 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = CF23D7382ACBB67A0007A649 /* Nimble */; }; 59 | CF23D73B2ACBB6850007A649 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = CF23D73A2ACBB6850007A649 /* Nimble */; }; 60 | CF23D73C2ACBB6D20007A649 /* RxTest.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB42ACBB1C2007FE582 /* RxTest.xcframework */; }; 61 | CF29D4252ACBB88700FF238B /* RxBlocking.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF29D4242ACBB88700FF238B /* RxBlocking.xcframework */; }; 62 | CF8C4C9A2ACBB139007FE582 /* RxSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */; }; 63 | CF8C4C9C2ACBB139007FE582 /* RxCocoa.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */; }; 64 | CF8C4C9F2ACBB14B007FE582 /* RxCocoa.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */; }; 65 | CF8C4CA12ACBB14B007FE582 /* RxSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */; }; 66 | CF8C4CA42ACBB15A007FE582 /* RxCocoa.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */; }; 67 | CF8C4CA62ACBB15A007FE582 /* RxSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */; }; 68 | CF8C4CA92ACBB17F007FE582 /* RxCocoa.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */; }; 69 | CF8C4CAB2ACBB17F007FE582 /* RxSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */; }; 70 | CF8C4CB52ACBB1C2007FE582 /* RxTest.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB42ACBB1C2007FE582 /* RxTest.xcframework */; }; 71 | CF8C4CB72ACBB1E2007FE582 /* RxRelay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */; }; 72 | CF8C4CB82ACBB1EF007FE582 /* RxRelay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */; }; 73 | CF8C4CBB2ACBB1FA007FE582 /* RxRelay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */; }; 74 | CF8C4CBE2ACBB205007FE582 /* RxRelay.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */; }; 75 | CF8C4CC62ACBB637007FE582 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = CF8C4CC52ACBB637007FE582 /* Quick */; }; 76 | FA3F973C1EDAF46F00A84787 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E21DE28587007E1D0D /* Action.swift */; }; 77 | FA3F973D1EDAF46F00A84787 /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E01DE28587007E1D0D /* Action+Internal.swift */; }; 78 | /* End PBXBuildFile section */ 79 | 80 | /* Begin PBXContainerItemProxy section */ 81 | 3DD965C11F5DC0E400C180FE /* PBXContainerItemProxy */ = { 82 | isa = PBXContainerItemProxy; 83 | containerPortal = BE73AD171CDCD101006F8B98 /* Project object */; 84 | proxyType = 1; 85 | remoteGlobalIDString = 1FCDDA811EAC3295006EB95B; 86 | remoteInfo = "Action-macOS"; 87 | }; 88 | 7F5E6A6D1D7F08D2000B6076 /* PBXContainerItemProxy */ = { 89 | isa = PBXContainerItemProxy; 90 | containerPortal = BE73AD171CDCD101006F8B98 /* Project object */; 91 | proxyType = 1; 92 | remoteGlobalIDString = BE73AD1F1CDCD101006F8B98; 93 | remoteInfo = Action; 94 | }; 95 | /* End PBXContainerItemProxy section */ 96 | 97 | /* Begin PBXCopyFilesBuildPhase section */ 98 | 7F612A931D7F0EB700B93BC5 /* CopyFiles */ = { 99 | isa = PBXCopyFilesBuildPhase; 100 | buildActionMask = 2147483647; 101 | dstPath = ""; 102 | dstSubfolderSpec = 10; 103 | files = ( 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXCopyFilesBuildPhase section */ 108 | 109 | /* Begin PBXFileReference section */ 110 | 1FCDDA5B1EAC315A006EB95B /* Action.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Action.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 111 | 1FCDDA821EAC3295006EB95B /* Action.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Action.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 112 | 1FCDDA961EAC33B0006EB95B /* Action.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Action.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 113 | 3D730DE21F673BE4008534D3 /* Control+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Control+Action.swift"; sourceTree = ""; }; 114 | 3D730DE81F673FD9008534D3 /* Button+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Button+Action.swift"; sourceTree = ""; }; 115 | 3DD965BB1F5DC0E400C180FE /* macOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "macOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 116 | 3DD965DA1F5DF79800C180FE /* NSButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSButtonTests.swift; sourceTree = ""; }; 117 | 3DD965DD1F5DF86B00C180FE /* BindToTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindToTests.swift; sourceTree = ""; }; 118 | 5ED520231E1EA199007621B9 /* BindToTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindToTests.swift; sourceTree = ""; }; 119 | 7B4BFE5320C12B7800D72FB0 /* UIRefreshControl+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIRefreshControl+Action.swift"; sourceTree = ""; }; 120 | 7B4BFE6220C290BE00D72FB0 /* RefreshControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshControlTests.swift; sourceTree = ""; }; 121 | 7F0569E01DE28587007E1D0D /* Action+Internal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Action+Internal.swift"; sourceTree = ""; }; 122 | 7F0569E11DE28587007E1D0D /* Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Action.h; sourceTree = ""; }; 123 | 7F0569E21DE28587007E1D0D /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 124 | 7F0569E31DE28587007E1D0D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 125 | 7F0569E51DE28587007E1D0D /* UIAlertAction+Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertAction+Action.swift"; sourceTree = ""; }; 126 | 7F0569E61DE28587007E1D0D /* UIBarButtonItem+Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Action.swift"; sourceTree = ""; }; 127 | 7F0569F11DE288EB007E1D0D /* AlertActionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertActionTests.swift; sourceTree = ""; }; 128 | 7F0569F21DE288EB007E1D0D /* BarButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonTests.swift; sourceTree = ""; }; 129 | 7F0569F31DE288EB007E1D0D /* ButtonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonTests.swift; sourceTree = ""; }; 130 | 7F0569F41DE288EB007E1D0D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ActionTests/Info.plist; sourceTree = ""; }; 131 | 7F5E6A671D7F08D2000B6076 /* iOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 132 | 7F612ACC1D7F13B800B93BC5 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 133 | 7F612ACE1D7F13B800B93BC5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 134 | 7F612AD01D7F13B800B93BC5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 135 | 7F612AD31D7F13B800B93BC5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 136 | 7F612AD51D7F13B800B93BC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 137 | 7F612AD81D7F13B800B93BC5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 138 | 7F612ADA1D7F13B800B93BC5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 139 | BE73AD201CDCD101006F8B98 /* Action.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Action.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 140 | C4BFF33C2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Nimble+RxTest+Ext.swift"; sourceTree = ""; }; 141 | C4BFF33F2282B79F00D1AA27 /* TestEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEvents.swift; sourceTree = ""; }; 142 | C4D3237D2285ADF7004B05A5 /* ActionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTests.swift; sourceTree = ""; }; 143 | C4DE00042229758700FB50AB /* Action+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Action+Extensions.swift"; sourceTree = ""; }; 144 | C4E0263F20D119EE00C8164C /* Action.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Action.podspec; sourceTree = ""; }; 145 | C4E0264020D11A0F00C8164C /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; 146 | C4E0264220D11A3B00C8164C /* Readme.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; 147 | C4E0264420D1244900C8164C /* Changelog.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Changelog.md; sourceTree = ""; }; 148 | CF29D4242ACBB88700FF238B /* RxBlocking.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxBlocking.xcframework; path = Carthage/Build/RxBlocking.xcframework; sourceTree = ""; }; 149 | CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxSwift.xcframework; path = Carthage/Build/RxSwift.xcframework; sourceTree = ""; }; 150 | CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxCocoa.xcframework; path = Carthage/Build/RxCocoa.xcframework; sourceTree = ""; }; 151 | CF8C4CB42ACBB1C2007FE582 /* RxTest.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxTest.xcframework; path = Carthage/Build/RxTest.xcframework; sourceTree = ""; }; 152 | CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxRelay.xcframework; path = Carthage/Build/RxRelay.xcframework; sourceTree = ""; }; 153 | /* End PBXFileReference section */ 154 | 155 | /* Begin PBXFrameworksBuildPhase section */ 156 | 1FCDDA571EAC315A006EB95B /* Frameworks */ = { 157 | isa = PBXFrameworksBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | CF8C4CB82ACBB1EF007FE582 /* RxRelay.xcframework in Frameworks */, 161 | CF8C4CA12ACBB14B007FE582 /* RxSwift.xcframework in Frameworks */, 162 | CF8C4C9F2ACBB14B007FE582 /* RxCocoa.xcframework in Frameworks */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | 1FCDDA7E1EAC3295006EB95B /* Frameworks */ = { 167 | isa = PBXFrameworksBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | CF8C4CBB2ACBB1FA007FE582 /* RxRelay.xcframework in Frameworks */, 171 | CF8C4CA62ACBB15A007FE582 /* RxSwift.xcframework in Frameworks */, 172 | CF8C4CA42ACBB15A007FE582 /* RxCocoa.xcframework in Frameworks */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | 1FCDDA921EAC33B0006EB95B /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | CF8C4CBE2ACBB205007FE582 /* RxRelay.xcframework in Frameworks */, 181 | CF8C4CAB2ACBB17F007FE582 /* RxSwift.xcframework in Frameworks */, 182 | CF8C4CA92ACBB17F007FE582 /* RxCocoa.xcframework in Frameworks */, 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | 3DD965B81F5DC0E400C180FE /* Frameworks */ = { 187 | isa = PBXFrameworksBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | CF23D7362ACBB6530007A649 /* Quick in Frameworks */, 191 | CF23D73B2ACBB6850007A649 /* Nimble in Frameworks */, 192 | CF29D4252ACBB88700FF238B /* RxBlocking.xcframework in Frameworks */, 193 | CF8C4CB52ACBB1C2007FE582 /* RxTest.xcframework in Frameworks */, 194 | 3DD965C01F5DC0E400C180FE /* Action.framework in Frameworks */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | 7F5E6A641D7F08D2000B6076 /* Frameworks */ = { 199 | isa = PBXFrameworksBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | CF23D7392ACBB67A0007A649 /* Nimble in Frameworks */, 203 | CF8C4CC62ACBB637007FE582 /* Quick in Frameworks */, 204 | CF23D73C2ACBB6D20007A649 /* RxTest.xcframework in Frameworks */, 205 | 7F612AA01D7F103A00B93BC5 /* Action.framework in Frameworks */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 7F612AC91D7F13B800B93BC5 /* Frameworks */ = { 210 | isa = PBXFrameworksBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 7FB791EC1D7F1BB600789D53 /* Action.framework in Frameworks */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | BE73AD1C1CDCD101006F8B98 /* Frameworks */ = { 218 | isa = PBXFrameworksBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | CF8C4CB72ACBB1E2007FE582 /* RxRelay.xcframework in Frameworks */, 222 | CF8C4C9A2ACBB139007FE582 /* RxSwift.xcframework in Frameworks */, 223 | CF8C4C9C2ACBB139007FE582 /* RxCocoa.xcframework in Frameworks */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXFrameworksBuildPhase section */ 228 | 229 | /* Begin PBXGroup section */ 230 | 3D730DE11F673BCB008534D3 /* CommonUI */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 3D730DE21F673BE4008534D3 /* Control+Action.swift */, 234 | 3D730DE81F673FD9008534D3 /* Button+Action.swift */, 235 | ); 236 | path = CommonUI; 237 | sourceTree = ""; 238 | }; 239 | 3DD965DB1F5DF83700C180FE /* iOS-Tests */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | 7F0569F11DE288EB007E1D0D /* AlertActionTests.swift */, 243 | 7F0569F21DE288EB007E1D0D /* BarButtonTests.swift */, 244 | 7F0569F31DE288EB007E1D0D /* ButtonTests.swift */, 245 | 7B4BFE6220C290BE00D72FB0 /* RefreshControlTests.swift */, 246 | 5ED520231E1EA199007621B9 /* BindToTests.swift */, 247 | ); 248 | path = "iOS-Tests"; 249 | sourceTree = ""; 250 | }; 251 | 3DD965DC1F5DF84800C180FE /* macOS-Tests */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 3DD965DA1F5DF79800C180FE /* NSButtonTests.swift */, 255 | 3DD965DD1F5DF86B00C180FE /* BindToTests.swift */, 256 | ); 257 | path = "macOS-Tests"; 258 | sourceTree = ""; 259 | }; 260 | 7F0569DE1DE28587007E1D0D /* Sources */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 7F0569DF1DE28587007E1D0D /* Action */, 264 | ); 265 | path = Sources; 266 | sourceTree = ""; 267 | }; 268 | 7F0569DF1DE28587007E1D0D /* Action */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 7F0569E21DE28587007E1D0D /* Action.swift */, 272 | C4DE00042229758700FB50AB /* Action+Extensions.swift */, 273 | 7F0569E01DE28587007E1D0D /* Action+Internal.swift */, 274 | 3D730DE11F673BCB008534D3 /* CommonUI */, 275 | 7F0569E41DE28587007E1D0D /* UIKitExtensions */, 276 | 7F0569EF1DE28598007E1D0D /* Supporting Files */, 277 | ); 278 | path = Action; 279 | sourceTree = ""; 280 | }; 281 | 7F0569E41DE28587007E1D0D /* UIKitExtensions */ = { 282 | isa = PBXGroup; 283 | children = ( 284 | 7F0569E51DE28587007E1D0D /* UIAlertAction+Action.swift */, 285 | 7F0569E61DE28587007E1D0D /* UIBarButtonItem+Action.swift */, 286 | 7B4BFE5320C12B7800D72FB0 /* UIRefreshControl+Action.swift */, 287 | ); 288 | path = UIKitExtensions; 289 | sourceTree = ""; 290 | }; 291 | 7F0569EF1DE28598007E1D0D /* Supporting Files */ = { 292 | isa = PBXGroup; 293 | children = ( 294 | 7F0569E11DE28587007E1D0D /* Action.h */, 295 | 7F0569E31DE28587007E1D0D /* Info.plist */, 296 | ); 297 | name = "Supporting Files"; 298 | sourceTree = ""; 299 | }; 300 | 7F5E6A5C1D7F06C4000B6076 /* Frameworks */ = { 301 | isa = PBXGroup; 302 | children = ( 303 | CF29D4242ACBB88700FF238B /* RxBlocking.xcframework */, 304 | CF8C4CB62ACBB1E2007FE582 /* RxRelay.xcframework */, 305 | CF8C4CB42ACBB1C2007FE582 /* RxTest.xcframework */, 306 | CF8C4C992ACBB139007FE582 /* RxCocoa.xcframework */, 307 | CF8C4C982ACBB139007FE582 /* RxSwift.xcframework */, 308 | ); 309 | name = Frameworks; 310 | sourceTree = ""; 311 | }; 312 | 7F5E6A681D7F08D2000B6076 /* Tests */ = { 313 | isa = PBXGroup; 314 | children = ( 315 | 3DD965DC1F5DF84800C180FE /* macOS-Tests */, 316 | 3DD965DB1F5DF83700C180FE /* iOS-Tests */, 317 | C4D3237D2285ADF7004B05A5 /* ActionTests.swift */, 318 | C4BFF33C2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift */, 319 | C4BFF33F2282B79F00D1AA27 /* TestEvents.swift */, 320 | 7F0569F41DE288EB007E1D0D /* Info.plist */, 321 | ); 322 | path = Tests; 323 | sourceTree = ""; 324 | }; 325 | 7F612ACD1D7F13B800B93BC5 /* Demo */ = { 326 | isa = PBXGroup; 327 | children = ( 328 | 7F612ACE1D7F13B800B93BC5 /* AppDelegate.swift */, 329 | 7F612AD01D7F13B800B93BC5 /* ViewController.swift */, 330 | 7F612AD21D7F13B800B93BC5 /* Main.storyboard */, 331 | 7F612AD71D7F13B800B93BC5 /* LaunchScreen.storyboard */, 332 | 7F612AD51D7F13B800B93BC5 /* Assets.xcassets */, 333 | 7F612ADA1D7F13B800B93BC5 /* Info.plist */, 334 | ); 335 | path = Demo; 336 | sourceTree = ""; 337 | }; 338 | BE73AD161CDCD101006F8B98 = { 339 | isa = PBXGroup; 340 | children = ( 341 | 7F0569DE1DE28587007E1D0D /* Sources */, 342 | 7F5E6A681D7F08D2000B6076 /* Tests */, 343 | 7F612ACD1D7F13B800B93BC5 /* Demo */, 344 | BE73AD211CDCD101006F8B98 /* Products */, 345 | 7F5E6A5C1D7F06C4000B6076 /* Frameworks */, 346 | C4E0263F20D119EE00C8164C /* Action.podspec */, 347 | C4E0264020D11A0F00C8164C /* Cartfile */, 348 | C4E0264220D11A3B00C8164C /* Readme.md */, 349 | C4E0264420D1244900C8164C /* Changelog.md */, 350 | ); 351 | sourceTree = ""; 352 | }; 353 | BE73AD211CDCD101006F8B98 /* Products */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | BE73AD201CDCD101006F8B98 /* Action.framework */, 357 | 7F5E6A671D7F08D2000B6076 /* iOS-Tests.xctest */, 358 | 7F612ACC1D7F13B800B93BC5 /* Demo.app */, 359 | 1FCDDA5B1EAC315A006EB95B /* Action.framework */, 360 | 1FCDDA821EAC3295006EB95B /* Action.framework */, 361 | 1FCDDA961EAC33B0006EB95B /* Action.framework */, 362 | 3DD965BB1F5DC0E400C180FE /* macOS-Tests.xctest */, 363 | ); 364 | name = Products; 365 | sourceTree = ""; 366 | }; 367 | /* End PBXGroup section */ 368 | 369 | /* Begin PBXHeadersBuildPhase section */ 370 | 1FCDDA581EAC315A006EB95B /* Headers */ = { 371 | isa = PBXHeadersBuildPhase; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | 1FCDDA631EAC31DD006EB95B /* Action.h in Headers */, 375 | ); 376 | runOnlyForDeploymentPostprocessing = 0; 377 | }; 378 | 1FCDDA7F1EAC3295006EB95B /* Headers */ = { 379 | isa = PBXHeadersBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | 1FCDDA901EAC3308006EB95B /* Action.h in Headers */, 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | 1FCDDA931EAC33B0006EB95B /* Headers */ = { 387 | isa = PBXHeadersBuildPhase; 388 | buildActionMask = 2147483647; 389 | files = ( 390 | 1FCDDA9E1EAC33C5006EB95B /* Action.h in Headers */, 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | BE73AD1D1CDCD101006F8B98 /* Headers */ = { 395 | isa = PBXHeadersBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | 7F0569E91DE28587007E1D0D /* Action.h in Headers */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | /* End PBXHeadersBuildPhase section */ 403 | 404 | /* Begin PBXNativeTarget section */ 405 | 1FCDDA5A1EAC315A006EB95B /* Action-tvOS */ = { 406 | isa = PBXNativeTarget; 407 | buildConfigurationList = 1FCDDA621EAC315A006EB95B /* Build configuration list for PBXNativeTarget "Action-tvOS" */; 408 | buildPhases = ( 409 | 1FCDDA561EAC315A006EB95B /* Sources */, 410 | 1FCDDA571EAC315A006EB95B /* Frameworks */, 411 | 1FCDDA581EAC315A006EB95B /* Headers */, 412 | 1FCDDA591EAC315A006EB95B /* Resources */, 413 | ); 414 | buildRules = ( 415 | ); 416 | dependencies = ( 417 | ); 418 | name = "Action-tvOS"; 419 | productName = "Action-tvOS"; 420 | productReference = 1FCDDA5B1EAC315A006EB95B /* Action.framework */; 421 | productType = "com.apple.product-type.framework"; 422 | }; 423 | 1FCDDA811EAC3295006EB95B /* Action-macOS */ = { 424 | isa = PBXNativeTarget; 425 | buildConfigurationList = 1FCDDA871EAC3295006EB95B /* Build configuration list for PBXNativeTarget "Action-macOS" */; 426 | buildPhases = ( 427 | 1FCDDA7D1EAC3295006EB95B /* Sources */, 428 | 1FCDDA7E1EAC3295006EB95B /* Frameworks */, 429 | 1FCDDA7F1EAC3295006EB95B /* Headers */, 430 | 1FCDDA801EAC3295006EB95B /* Resources */, 431 | ); 432 | buildRules = ( 433 | ); 434 | dependencies = ( 435 | ); 436 | name = "Action-macOS"; 437 | productName = "Action-macOS"; 438 | productReference = 1FCDDA821EAC3295006EB95B /* Action.framework */; 439 | productType = "com.apple.product-type.framework"; 440 | }; 441 | 1FCDDA951EAC33B0006EB95B /* Action-watchOS */ = { 442 | isa = PBXNativeTarget; 443 | buildConfigurationList = 1FCDDA9B1EAC33B0006EB95B /* Build configuration list for PBXNativeTarget "Action-watchOS" */; 444 | buildPhases = ( 445 | 1FCDDA911EAC33B0006EB95B /* Sources */, 446 | 1FCDDA921EAC33B0006EB95B /* Frameworks */, 447 | 1FCDDA931EAC33B0006EB95B /* Headers */, 448 | 1FCDDA941EAC33B0006EB95B /* Resources */, 449 | ); 450 | buildRules = ( 451 | ); 452 | dependencies = ( 453 | ); 454 | name = "Action-watchOS"; 455 | productName = "Action-watchOS"; 456 | productReference = 1FCDDA961EAC33B0006EB95B /* Action.framework */; 457 | productType = "com.apple.product-type.framework"; 458 | }; 459 | 3DD965BA1F5DC0E400C180FE /* macOS-Tests */ = { 460 | isa = PBXNativeTarget; 461 | buildConfigurationList = 3DD965C51F5DC0E400C180FE /* Build configuration list for PBXNativeTarget "macOS-Tests" */; 462 | buildPhases = ( 463 | 3DD965B71F5DC0E400C180FE /* Sources */, 464 | 3DD965B81F5DC0E400C180FE /* Frameworks */, 465 | 3DD965B91F5DC0E400C180FE /* Resources */, 466 | ); 467 | buildRules = ( 468 | ); 469 | dependencies = ( 470 | 3DD965C21F5DC0E400C180FE /* PBXTargetDependency */, 471 | ); 472 | name = "macOS-Tests"; 473 | packageProductDependencies = ( 474 | CF23D7352ACBB6530007A649 /* Quick */, 475 | CF23D73A2ACBB6850007A649 /* Nimble */, 476 | ); 477 | productName = "Tests-macOS1"; 478 | productReference = 3DD965BB1F5DC0E400C180FE /* macOS-Tests.xctest */; 479 | productType = "com.apple.product-type.bundle.unit-test"; 480 | }; 481 | 7F5E6A661D7F08D2000B6076 /* iOS-Tests */ = { 482 | isa = PBXNativeTarget; 483 | buildConfigurationList = 7F5E6A6F1D7F08D2000B6076 /* Build configuration list for PBXNativeTarget "iOS-Tests" */; 484 | buildPhases = ( 485 | 7F5E6A631D7F08D2000B6076 /* Sources */, 486 | 7F5E6A641D7F08D2000B6076 /* Frameworks */, 487 | 7F5E6A651D7F08D2000B6076 /* Resources */, 488 | 7F612A931D7F0EB700B93BC5 /* CopyFiles */, 489 | ); 490 | buildRules = ( 491 | ); 492 | dependencies = ( 493 | 7F5E6A6E1D7F08D2000B6076 /* PBXTargetDependency */, 494 | ); 495 | name = "iOS-Tests"; 496 | packageProductDependencies = ( 497 | CF8C4CC52ACBB637007FE582 /* Quick */, 498 | CF23D7382ACBB67A0007A649 /* Nimble */, 499 | ); 500 | productName = Tests; 501 | productReference = 7F5E6A671D7F08D2000B6076 /* iOS-Tests.xctest */; 502 | productType = "com.apple.product-type.bundle.unit-test"; 503 | }; 504 | 7F612ACB1D7F13B800B93BC5 /* Demo */ = { 505 | isa = PBXNativeTarget; 506 | buildConfigurationList = 7F612ADB1D7F13B800B93BC5 /* Build configuration list for PBXNativeTarget "Demo" */; 507 | buildPhases = ( 508 | 7F612AC81D7F13B800B93BC5 /* Sources */, 509 | 7F612AC91D7F13B800B93BC5 /* Frameworks */, 510 | 7F612ACA1D7F13B800B93BC5 /* Resources */, 511 | ); 512 | buildRules = ( 513 | ); 514 | dependencies = ( 515 | ); 516 | name = Demo; 517 | productName = Demo; 518 | productReference = 7F612ACC1D7F13B800B93BC5 /* Demo.app */; 519 | productType = "com.apple.product-type.application"; 520 | }; 521 | BE73AD1F1CDCD101006F8B98 /* Action */ = { 522 | isa = PBXNativeTarget; 523 | buildConfigurationList = BE73AD281CDCD102006F8B98 /* Build configuration list for PBXNativeTarget "Action" */; 524 | buildPhases = ( 525 | BE73AD1B1CDCD101006F8B98 /* Sources */, 526 | BE73AD1C1CDCD101006F8B98 /* Frameworks */, 527 | BE73AD1D1CDCD101006F8B98 /* Headers */, 528 | BE73AD1E1CDCD101006F8B98 /* Resources */, 529 | ); 530 | buildRules = ( 531 | ); 532 | dependencies = ( 533 | ); 534 | name = Action; 535 | productName = Action; 536 | productReference = BE73AD201CDCD101006F8B98 /* Action.framework */; 537 | productType = "com.apple.product-type.framework"; 538 | }; 539 | /* End PBXNativeTarget section */ 540 | 541 | /* Begin PBXProject section */ 542 | BE73AD171CDCD101006F8B98 /* Project object */ = { 543 | isa = PBXProject; 544 | attributes = { 545 | LastSwiftUpdateCheck = 0900; 546 | LastUpgradeCheck = 1020; 547 | ORGANIZATIONNAME = CezaryKopacz; 548 | TargetAttributes = { 549 | 1FCDDA5A1EAC315A006EB95B = { 550 | CreatedOnToolsVersion = 8.3.2; 551 | ProvisioningStyle = Automatic; 552 | }; 553 | 1FCDDA811EAC3295006EB95B = { 554 | CreatedOnToolsVersion = 8.3.2; 555 | LastSwiftMigration = 1020; 556 | ProvisioningStyle = Manual; 557 | }; 558 | 1FCDDA951EAC33B0006EB95B = { 559 | CreatedOnToolsVersion = 8.3.2; 560 | ProvisioningStyle = Automatic; 561 | }; 562 | 3DD965BA1F5DC0E400C180FE = { 563 | CreatedOnToolsVersion = 9.0; 564 | LastSwiftMigration = 1020; 565 | ProvisioningStyle = Automatic; 566 | }; 567 | 7F5E6A661D7F08D2000B6076 = { 568 | CreatedOnToolsVersion = 8.0; 569 | LastSwiftMigration = 1020; 570 | ProvisioningStyle = Automatic; 571 | }; 572 | 7F612ACB1D7F13B800B93BC5 = { 573 | CreatedOnToolsVersion = 8.0; 574 | ProvisioningStyle = Automatic; 575 | }; 576 | BE73AD1F1CDCD101006F8B98 = { 577 | CreatedOnToolsVersion = 7.3; 578 | LastSwiftMigration = 1020; 579 | }; 580 | }; 581 | }; 582 | buildConfigurationList = BE73AD1A1CDCD101006F8B98 /* Build configuration list for PBXProject "Action" */; 583 | compatibilityVersion = "Xcode 3.2"; 584 | developmentRegion = en; 585 | hasScannedForEncodings = 0; 586 | knownRegions = ( 587 | en, 588 | Base, 589 | ); 590 | mainGroup = BE73AD161CDCD101006F8B98; 591 | packageReferences = ( 592 | CF8C4CC42ACBB637007FE582 /* XCRemoteSwiftPackageReference "Quick" */, 593 | CF23D7372ACBB67A0007A649 /* XCRemoteSwiftPackageReference "Nimble" */, 594 | ); 595 | productRefGroup = BE73AD211CDCD101006F8B98 /* Products */; 596 | projectDirPath = ""; 597 | projectRoot = ""; 598 | targets = ( 599 | BE73AD1F1CDCD101006F8B98 /* Action */, 600 | 1FCDDA5A1EAC315A006EB95B /* Action-tvOS */, 601 | 1FCDDA811EAC3295006EB95B /* Action-macOS */, 602 | 1FCDDA951EAC33B0006EB95B /* Action-watchOS */, 603 | 7F5E6A661D7F08D2000B6076 /* iOS-Tests */, 604 | 3DD965BA1F5DC0E400C180FE /* macOS-Tests */, 605 | 7F612ACB1D7F13B800B93BC5 /* Demo */, 606 | ); 607 | }; 608 | /* End PBXProject section */ 609 | 610 | /* Begin PBXResourcesBuildPhase section */ 611 | 1FCDDA591EAC315A006EB95B /* Resources */ = { 612 | isa = PBXResourcesBuildPhase; 613 | buildActionMask = 2147483647; 614 | files = ( 615 | ); 616 | runOnlyForDeploymentPostprocessing = 0; 617 | }; 618 | 1FCDDA801EAC3295006EB95B /* Resources */ = { 619 | isa = PBXResourcesBuildPhase; 620 | buildActionMask = 2147483647; 621 | files = ( 622 | ); 623 | runOnlyForDeploymentPostprocessing = 0; 624 | }; 625 | 1FCDDA941EAC33B0006EB95B /* Resources */ = { 626 | isa = PBXResourcesBuildPhase; 627 | buildActionMask = 2147483647; 628 | files = ( 629 | ); 630 | runOnlyForDeploymentPostprocessing = 0; 631 | }; 632 | 3DD965B91F5DC0E400C180FE /* Resources */ = { 633 | isa = PBXResourcesBuildPhase; 634 | buildActionMask = 2147483647; 635 | files = ( 636 | ); 637 | runOnlyForDeploymentPostprocessing = 0; 638 | }; 639 | 7F5E6A651D7F08D2000B6076 /* Resources */ = { 640 | isa = PBXResourcesBuildPhase; 641 | buildActionMask = 2147483647; 642 | files = ( 643 | ); 644 | runOnlyForDeploymentPostprocessing = 0; 645 | }; 646 | 7F612ACA1D7F13B800B93BC5 /* Resources */ = { 647 | isa = PBXResourcesBuildPhase; 648 | buildActionMask = 2147483647; 649 | files = ( 650 | 7F612AD91D7F13B800B93BC5 /* LaunchScreen.storyboard in Resources */, 651 | 7F612AD61D7F13B800B93BC5 /* Assets.xcassets in Resources */, 652 | 7F612AD41D7F13B800B93BC5 /* Main.storyboard in Resources */, 653 | ); 654 | runOnlyForDeploymentPostprocessing = 0; 655 | }; 656 | BE73AD1E1CDCD101006F8B98 /* Resources */ = { 657 | isa = PBXResourcesBuildPhase; 658 | buildActionMask = 2147483647; 659 | files = ( 660 | ); 661 | runOnlyForDeploymentPostprocessing = 0; 662 | }; 663 | /* End PBXResourcesBuildPhase section */ 664 | 665 | /* Begin PBXSourcesBuildPhase section */ 666 | 1FCDDA561EAC315A006EB95B /* Sources */ = { 667 | isa = PBXSourcesBuildPhase; 668 | buildActionMask = 2147483647; 669 | files = ( 670 | 1FCDDA641EAC31EF006EB95B /* Action.swift in Sources */, 671 | 1FCDDA651EAC31EF006EB95B /* Action+Internal.swift in Sources */, 672 | 7B4BFE5420C12B7800D72FB0 /* UIRefreshControl+Action.swift in Sources */, 673 | 1FCDDA661EAC31EF006EB95B /* UIAlertAction+Action.swift in Sources */, 674 | C41E08EE2237D26D0039D213 /* Action+Extensions.swift in Sources */, 675 | 3D730DEA1F674043008534D3 /* Button+Action.swift in Sources */, 676 | 1FCDDA671EAC31EF006EB95B /* UIBarButtonItem+Action.swift in Sources */, 677 | 3D730DE41F673C64008534D3 /* Control+Action.swift in Sources */, 678 | ); 679 | runOnlyForDeploymentPostprocessing = 0; 680 | }; 681 | 1FCDDA7D1EAC3295006EB95B /* Sources */ = { 682 | isa = PBXSourcesBuildPhase; 683 | buildActionMask = 2147483647; 684 | files = ( 685 | C41E08EF2237D2700039D213 /* Action+Extensions.swift in Sources */, 686 | 3D730DE51F673C65008534D3 /* Control+Action.swift in Sources */, 687 | 1FCDDA8A1EAC329E006EB95B /* Action.swift in Sources */, 688 | 3D730DEB1F674044008534D3 /* Button+Action.swift in Sources */, 689 | 1FCDDA8B1EAC329E006EB95B /* Action+Internal.swift in Sources */, 690 | ); 691 | runOnlyForDeploymentPostprocessing = 0; 692 | }; 693 | 1FCDDA911EAC33B0006EB95B /* Sources */ = { 694 | isa = PBXSourcesBuildPhase; 695 | buildActionMask = 2147483647; 696 | files = ( 697 | FA3F973C1EDAF46F00A84787 /* Action.swift in Sources */, 698 | FA3F973D1EDAF46F00A84787 /* Action+Internal.swift in Sources */, 699 | C41E08F02237D2740039D213 /* Action+Extensions.swift in Sources */, 700 | ); 701 | runOnlyForDeploymentPostprocessing = 0; 702 | }; 703 | 3DD965B71F5DC0E400C180FE /* Sources */ = { 704 | isa = PBXSourcesBuildPhase; 705 | buildActionMask = 2147483647; 706 | files = ( 707 | 3DD965DE1F5DF8C500C180FE /* BindToTests.swift in Sources */, 708 | C4BFF3412282B79F00D1AA27 /* TestEvents.swift in Sources */, 709 | C4BFF33E2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift in Sources */, 710 | 3DD965DF1F5DF8C900C180FE /* NSButtonTests.swift in Sources */, 711 | C4D3237F2285AEAD004B05A5 /* ActionTests.swift in Sources */, 712 | ); 713 | runOnlyForDeploymentPostprocessing = 0; 714 | }; 715 | 7F5E6A631D7F08D2000B6076 /* Sources */ = { 716 | isa = PBXSourcesBuildPhase; 717 | buildActionMask = 2147483647; 718 | files = ( 719 | C4BFF3402282B79F00D1AA27 /* TestEvents.swift in Sources */, 720 | 7F0569F71DE288EB007E1D0D /* BarButtonTests.swift in Sources */, 721 | 5ED520241E1EA199007621B9 /* BindToTests.swift in Sources */, 722 | 7F0569F61DE288EB007E1D0D /* AlertActionTests.swift in Sources */, 723 | C4D3237E2285ADF7004B05A5 /* ActionTests.swift in Sources */, 724 | 7B4BFE6320C290BF00D72FB0 /* RefreshControlTests.swift in Sources */, 725 | C4BFF33D2281D6E100D1AA27 /* Nimble+RxTest+Ext.swift in Sources */, 726 | 7F0569F81DE288EB007E1D0D /* ButtonTests.swift in Sources */, 727 | ); 728 | runOnlyForDeploymentPostprocessing = 0; 729 | }; 730 | 7F612AC81D7F13B800B93BC5 /* Sources */ = { 731 | isa = PBXSourcesBuildPhase; 732 | buildActionMask = 2147483647; 733 | files = ( 734 | 7F612AD11D7F13B800B93BC5 /* ViewController.swift in Sources */, 735 | 7F612ACF1D7F13B800B93BC5 /* AppDelegate.swift in Sources */, 736 | ); 737 | runOnlyForDeploymentPostprocessing = 0; 738 | }; 739 | BE73AD1B1CDCD101006F8B98 /* Sources */ = { 740 | isa = PBXSourcesBuildPhase; 741 | buildActionMask = 2147483647; 742 | files = ( 743 | 7F0569EC1DE28587007E1D0D /* UIAlertAction+Action.swift in Sources */, 744 | 7F0569E81DE28587007E1D0D /* Action+Internal.swift in Sources */, 745 | 7B4BFE5520C12B7800D72FB0 /* UIRefreshControl+Action.swift in Sources */, 746 | 7F0569ED1DE28587007E1D0D /* UIBarButtonItem+Action.swift in Sources */, 747 | C4DE00052229758700FB50AB /* Action+Extensions.swift in Sources */, 748 | 3D730DE91F673FD9008534D3 /* Button+Action.swift in Sources */, 749 | 3D730DE31F673BE4008534D3 /* Control+Action.swift in Sources */, 750 | 7F0569EA1DE28587007E1D0D /* Action.swift in Sources */, 751 | ); 752 | runOnlyForDeploymentPostprocessing = 0; 753 | }; 754 | /* End PBXSourcesBuildPhase section */ 755 | 756 | /* Begin PBXTargetDependency section */ 757 | 3DD965C21F5DC0E400C180FE /* PBXTargetDependency */ = { 758 | isa = PBXTargetDependency; 759 | target = 1FCDDA811EAC3295006EB95B /* Action-macOS */; 760 | targetProxy = 3DD965C11F5DC0E400C180FE /* PBXContainerItemProxy */; 761 | }; 762 | 7F5E6A6E1D7F08D2000B6076 /* PBXTargetDependency */ = { 763 | isa = PBXTargetDependency; 764 | target = BE73AD1F1CDCD101006F8B98 /* Action */; 765 | targetProxy = 7F5E6A6D1D7F08D2000B6076 /* PBXContainerItemProxy */; 766 | }; 767 | /* End PBXTargetDependency section */ 768 | 769 | /* Begin PBXVariantGroup section */ 770 | 7F612AD21D7F13B800B93BC5 /* Main.storyboard */ = { 771 | isa = PBXVariantGroup; 772 | children = ( 773 | 7F612AD31D7F13B800B93BC5 /* Base */, 774 | ); 775 | name = Main.storyboard; 776 | sourceTree = ""; 777 | }; 778 | 7F612AD71D7F13B800B93BC5 /* LaunchScreen.storyboard */ = { 779 | isa = PBXVariantGroup; 780 | children = ( 781 | 7F612AD81D7F13B800B93BC5 /* Base */, 782 | ); 783 | name = LaunchScreen.storyboard; 784 | sourceTree = ""; 785 | }; 786 | /* End PBXVariantGroup section */ 787 | 788 | /* Begin XCBuildConfiguration section */ 789 | 1FCDDA601EAC315A006EB95B /* Debug */ = { 790 | isa = XCBuildConfiguration; 791 | buildSettings = { 792 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 793 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 794 | CODE_SIGN_IDENTITY = ""; 795 | DEFINES_MODULE = YES; 796 | DYLIB_COMPATIBILITY_VERSION = 1; 797 | DYLIB_CURRENT_VERSION = 1; 798 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 799 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 800 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 801 | LD_RUNPATH_SEARCH_PATHS = ( 802 | "$(inherited)", 803 | "@executable_path/Frameworks", 804 | "@loader_path/Frameworks", 805 | ); 806 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 807 | PRODUCT_NAME = Action; 808 | SDKROOT = appletvos; 809 | SKIP_INSTALL = YES; 810 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 811 | SWIFT_VERSION = 5.0; 812 | TARGETED_DEVICE_FAMILY = 3; 813 | TVOS_DEPLOYMENT_TARGET = 9.0; 814 | }; 815 | name = Debug; 816 | }; 817 | 1FCDDA611EAC315A006EB95B /* Release */ = { 818 | isa = XCBuildConfiguration; 819 | buildSettings = { 820 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 821 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 822 | CODE_SIGN_IDENTITY = ""; 823 | DEFINES_MODULE = YES; 824 | DYLIB_COMPATIBILITY_VERSION = 1; 825 | DYLIB_CURRENT_VERSION = 1; 826 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 827 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 828 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 829 | LD_RUNPATH_SEARCH_PATHS = ( 830 | "$(inherited)", 831 | "@executable_path/Frameworks", 832 | "@loader_path/Frameworks", 833 | ); 834 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 835 | PRODUCT_NAME = Action; 836 | SDKROOT = appletvos; 837 | SKIP_INSTALL = YES; 838 | SWIFT_VERSION = 5.0; 839 | TARGETED_DEVICE_FAMILY = 3; 840 | TVOS_DEPLOYMENT_TARGET = 9.0; 841 | }; 842 | name = Release; 843 | }; 844 | 1FCDDA881EAC3295006EB95B /* Debug */ = { 845 | isa = XCBuildConfiguration; 846 | buildSettings = { 847 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 848 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 849 | CODE_SIGN_IDENTITY = ""; 850 | CODE_SIGN_STYLE = Manual; 851 | COMBINE_HIDPI_IMAGES = YES; 852 | DEFINES_MODULE = YES; 853 | DEVELOPMENT_TEAM = ""; 854 | DYLIB_COMPATIBILITY_VERSION = 1; 855 | DYLIB_CURRENT_VERSION = 1; 856 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 857 | FRAMEWORK_VERSION = A; 858 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 859 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 860 | LD_RUNPATH_SEARCH_PATHS = ( 861 | "$(inherited)", 862 | "@executable_path/../Frameworks", 863 | "@loader_path/Frameworks", 864 | ); 865 | MACOSX_DEPLOYMENT_TARGET = 10.15; 866 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 867 | PRODUCT_NAME = Action; 868 | PROVISIONING_PROFILE_SPECIFIER = ""; 869 | SDKROOT = macosx; 870 | SKIP_INSTALL = YES; 871 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 872 | SWIFT_VERSION = 5.0; 873 | }; 874 | name = Debug; 875 | }; 876 | 1FCDDA891EAC3295006EB95B /* Release */ = { 877 | isa = XCBuildConfiguration; 878 | buildSettings = { 879 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 880 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 881 | CODE_SIGN_IDENTITY = ""; 882 | CODE_SIGN_STYLE = Manual; 883 | COMBINE_HIDPI_IMAGES = YES; 884 | DEFINES_MODULE = YES; 885 | DEVELOPMENT_TEAM = ""; 886 | DYLIB_COMPATIBILITY_VERSION = 1; 887 | DYLIB_CURRENT_VERSION = 1; 888 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 889 | FRAMEWORK_VERSION = A; 890 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 891 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 892 | LD_RUNPATH_SEARCH_PATHS = ( 893 | "$(inherited)", 894 | "@executable_path/../Frameworks", 895 | "@loader_path/Frameworks", 896 | ); 897 | MACOSX_DEPLOYMENT_TARGET = 10.15; 898 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 899 | PRODUCT_NAME = Action; 900 | PROVISIONING_PROFILE_SPECIFIER = ""; 901 | SDKROOT = macosx; 902 | SKIP_INSTALL = YES; 903 | SWIFT_VERSION = 5.0; 904 | }; 905 | name = Release; 906 | }; 907 | 1FCDDA9C1EAC33B0006EB95B /* Debug */ = { 908 | isa = XCBuildConfiguration; 909 | buildSettings = { 910 | APPLICATION_EXTENSION_API_ONLY = YES; 911 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 912 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 913 | CODE_SIGN_IDENTITY = ""; 914 | DEFINES_MODULE = YES; 915 | DYLIB_COMPATIBILITY_VERSION = 1; 916 | DYLIB_CURRENT_VERSION = 1; 917 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 918 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 919 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 920 | LD_RUNPATH_SEARCH_PATHS = ( 921 | "$(inherited)", 922 | "@executable_path/Frameworks", 923 | "@loader_path/Frameworks", 924 | ); 925 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 926 | PRODUCT_NAME = Action; 927 | SDKROOT = watchos; 928 | SKIP_INSTALL = YES; 929 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 930 | SWIFT_VERSION = 5.0; 931 | TARGETED_DEVICE_FAMILY = 4; 932 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 933 | }; 934 | name = Debug; 935 | }; 936 | 1FCDDA9D1EAC33B0006EB95B /* Release */ = { 937 | isa = XCBuildConfiguration; 938 | buildSettings = { 939 | APPLICATION_EXTENSION_API_ONLY = YES; 940 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 941 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 942 | CODE_SIGN_IDENTITY = ""; 943 | DEFINES_MODULE = YES; 944 | DYLIB_COMPATIBILITY_VERSION = 1; 945 | DYLIB_CURRENT_VERSION = 1; 946 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 947 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 948 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 949 | LD_RUNPATH_SEARCH_PATHS = ( 950 | "$(inherited)", 951 | "@executable_path/Frameworks", 952 | "@loader_path/Frameworks", 953 | ); 954 | PRODUCT_BUNDLE_IDENTIFIER = jp.toshi0383.Action; 955 | PRODUCT_NAME = Action; 956 | SDKROOT = watchos; 957 | SKIP_INSTALL = YES; 958 | SWIFT_VERSION = 5.0; 959 | TARGETED_DEVICE_FAMILY = 4; 960 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 961 | }; 962 | name = Release; 963 | }; 964 | 3DD965C31F5DC0E400C180FE /* Debug */ = { 965 | isa = XCBuildConfiguration; 966 | buildSettings = { 967 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 968 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 969 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 970 | CLANG_WARN_COMMA = YES; 971 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 972 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 973 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 974 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 975 | CLANG_WARN_STRICT_PROTOTYPES = YES; 976 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 977 | COMBINE_HIDPI_IMAGES = YES; 978 | GCC_C_LANGUAGE_STANDARD = gnu11; 979 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 980 | INFOPLIST_FILE = Tests/ActionTests/Info.plist; 981 | LD_RUNPATH_SEARCH_PATHS = ( 982 | "$(inherited)", 983 | "@executable_path/../Frameworks", 984 | "@loader_path/../Frameworks", 985 | "$(PROJECT_DIR)/Carthage/Build/Mac", 986 | ); 987 | MACOSX_DEPLOYMENT_TARGET = 10.15; 988 | PRODUCT_BUNDLE_IDENTIFIER = "com.ashfurrow.Tests-macOS"; 989 | PRODUCT_NAME = "$(TARGET_NAME)"; 990 | SDKROOT = macosx; 991 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 992 | SWIFT_VERSION = 5.0; 993 | }; 994 | name = Debug; 995 | }; 996 | 3DD965C41F5DC0E400C180FE /* Release */ = { 997 | isa = XCBuildConfiguration; 998 | buildSettings = { 999 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 1000 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 1001 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1002 | CLANG_WARN_COMMA = YES; 1003 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1004 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1005 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1006 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1007 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1008 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 1009 | COMBINE_HIDPI_IMAGES = YES; 1010 | GCC_C_LANGUAGE_STANDARD = gnu11; 1011 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1012 | INFOPLIST_FILE = Tests/ActionTests/Info.plist; 1013 | LD_RUNPATH_SEARCH_PATHS = ( 1014 | "$(inherited)", 1015 | "@executable_path/../Frameworks", 1016 | "@loader_path/../Frameworks", 1017 | "$(PROJECT_DIR)/Carthage/Build/Mac", 1018 | ); 1019 | MACOSX_DEPLOYMENT_TARGET = 10.15; 1020 | PRODUCT_BUNDLE_IDENTIFIER = "com.ashfurrow.Tests-macOS"; 1021 | PRODUCT_NAME = "$(TARGET_NAME)"; 1022 | SDKROOT = macosx; 1023 | SWIFT_VERSION = 5.0; 1024 | }; 1025 | name = Release; 1026 | }; 1027 | 7F5E6A701D7F08D2000B6076 /* Debug */ = { 1028 | isa = XCBuildConfiguration; 1029 | buildSettings = { 1030 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1031 | CLANG_ENABLE_MODULES = YES; 1032 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1033 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 1034 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1035 | INFOPLIST_FILE = Tests/ActionTests/Info.plist; 1036 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 1037 | LD_RUNPATH_SEARCH_PATHS = ( 1038 | "$(inherited)", 1039 | "@executable_path/Frameworks", 1040 | "@loader_path/Frameworks", 1041 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1042 | ); 1043 | PRODUCT_BUNDLE_IDENTIFIER = "-.Tests"; 1044 | PRODUCT_NAME = "$(TARGET_NAME)"; 1045 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 1046 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1047 | SWIFT_VERSION = 5.0; 1048 | }; 1049 | name = Debug; 1050 | }; 1051 | 7F5E6A711D7F08D2000B6076 /* Release */ = { 1052 | isa = XCBuildConfiguration; 1053 | buildSettings = { 1054 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1055 | CLANG_ENABLE_MODULES = YES; 1056 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1057 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 1058 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1059 | INFOPLIST_FILE = Tests/ActionTests/Info.plist; 1060 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 1061 | LD_RUNPATH_SEARCH_PATHS = ( 1062 | "$(inherited)", 1063 | "@executable_path/Frameworks", 1064 | "@loader_path/Frameworks", 1065 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1066 | ); 1067 | PRODUCT_BUNDLE_IDENTIFIER = "-.Tests"; 1068 | PRODUCT_NAME = "$(TARGET_NAME)"; 1069 | SWIFT_VERSION = 5.0; 1070 | }; 1071 | name = Release; 1072 | }; 1073 | 7F612ADC1D7F13B800B93BC5 /* Debug */ = { 1074 | isa = XCBuildConfiguration; 1075 | buildSettings = { 1076 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1077 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1078 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 1079 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1080 | INFOPLIST_FILE = Demo/Info.plist; 1081 | LD_RUNPATH_SEARCH_PATHS = ( 1082 | "$(inherited)", 1083 | "@executable_path/Frameworks", 1084 | ); 1085 | PRODUCT_BUNDLE_IDENTIFIER = "-.Demo"; 1086 | PRODUCT_NAME = "$(TARGET_NAME)"; 1087 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 1088 | }; 1089 | name = Debug; 1090 | }; 1091 | 7F612ADD1D7F13B800B93BC5 /* Release */ = { 1092 | isa = XCBuildConfiguration; 1093 | buildSettings = { 1094 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1095 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1096 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 1097 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1098 | INFOPLIST_FILE = Demo/Info.plist; 1099 | LD_RUNPATH_SEARCH_PATHS = ( 1100 | "$(inherited)", 1101 | "@executable_path/Frameworks", 1102 | ); 1103 | PRODUCT_BUNDLE_IDENTIFIER = "-.Demo"; 1104 | PRODUCT_NAME = "$(TARGET_NAME)"; 1105 | }; 1106 | name = Release; 1107 | }; 1108 | BE73AD261CDCD102006F8B98 /* Debug */ = { 1109 | isa = XCBuildConfiguration; 1110 | buildSettings = { 1111 | ALWAYS_SEARCH_USER_PATHS = NO; 1112 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 1113 | CLANG_ANALYZER_NONNULL = YES; 1114 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1115 | CLANG_CXX_LIBRARY = "libc++"; 1116 | CLANG_ENABLE_MODULES = YES; 1117 | CLANG_ENABLE_OBJC_ARC = YES; 1118 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1119 | CLANG_WARN_BOOL_CONVERSION = YES; 1120 | CLANG_WARN_COMMA = YES; 1121 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1122 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 1123 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1124 | CLANG_WARN_EMPTY_BODY = YES; 1125 | CLANG_WARN_ENUM_CONVERSION = YES; 1126 | CLANG_WARN_INFINITE_RECURSION = YES; 1127 | CLANG_WARN_INT_CONVERSION = YES; 1128 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1129 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 1130 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1131 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1132 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1133 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1134 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1135 | CLANG_WARN_UNREACHABLE_CODE = YES; 1136 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1137 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1138 | COPY_PHASE_STRIP = NO; 1139 | CURRENT_PROJECT_VERSION = 1; 1140 | DEBUG_INFORMATION_FORMAT = dwarf; 1141 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1142 | ENABLE_TESTABILITY = YES; 1143 | GCC_C_LANGUAGE_STANDARD = gnu99; 1144 | GCC_DYNAMIC_NO_PIC = NO; 1145 | GCC_NO_COMMON_BLOCKS = YES; 1146 | GCC_OPTIMIZATION_LEVEL = 0; 1147 | GCC_PREPROCESSOR_DEFINITIONS = ( 1148 | "DEBUG=1", 1149 | "$(inherited)", 1150 | ); 1151 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1152 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1153 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1154 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1155 | GCC_WARN_UNUSED_FUNCTION = YES; 1156 | GCC_WARN_UNUSED_VARIABLE = YES; 1157 | INFOPLIST_FILE = Sources/Action/Info.plist; 1158 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1159 | MTL_ENABLE_DEBUG_INFO = YES; 1160 | ONLY_ACTIVE_ARCH = YES; 1161 | SDKROOT = iphoneos; 1162 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1163 | SWIFT_VERSION = 5.0; 1164 | TARGETED_DEVICE_FAMILY = "1,2"; 1165 | VERSIONING_SYSTEM = "apple-generic"; 1166 | VERSION_INFO_PREFIX = ""; 1167 | }; 1168 | name = Debug; 1169 | }; 1170 | BE73AD271CDCD102006F8B98 /* Release */ = { 1171 | isa = XCBuildConfiguration; 1172 | buildSettings = { 1173 | ALWAYS_SEARCH_USER_PATHS = NO; 1174 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 1175 | CLANG_ANALYZER_NONNULL = YES; 1176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1177 | CLANG_CXX_LIBRARY = "libc++"; 1178 | CLANG_ENABLE_MODULES = YES; 1179 | CLANG_ENABLE_OBJC_ARC = YES; 1180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1181 | CLANG_WARN_BOOL_CONVERSION = YES; 1182 | CLANG_WARN_COMMA = YES; 1183 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1184 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 1185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1186 | CLANG_WARN_EMPTY_BODY = YES; 1187 | CLANG_WARN_ENUM_CONVERSION = YES; 1188 | CLANG_WARN_INFINITE_RECURSION = YES; 1189 | CLANG_WARN_INT_CONVERSION = YES; 1190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1191 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 1192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1194 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1195 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1196 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1197 | CLANG_WARN_UNREACHABLE_CODE = YES; 1198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1199 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1200 | COPY_PHASE_STRIP = NO; 1201 | CURRENT_PROJECT_VERSION = 1; 1202 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1203 | ENABLE_NS_ASSERTIONS = NO; 1204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1205 | GCC_C_LANGUAGE_STANDARD = gnu99; 1206 | GCC_NO_COMMON_BLOCKS = YES; 1207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1211 | GCC_WARN_UNUSED_FUNCTION = YES; 1212 | GCC_WARN_UNUSED_VARIABLE = YES; 1213 | INFOPLIST_FILE = Sources/Action/Info.plist; 1214 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1215 | MTL_ENABLE_DEBUG_INFO = NO; 1216 | SDKROOT = iphoneos; 1217 | SWIFT_COMPILATION_MODE = wholemodule; 1218 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 1219 | SWIFT_VERSION = 5.0; 1220 | TARGETED_DEVICE_FAMILY = "1,2"; 1221 | VALIDATE_PRODUCT = YES; 1222 | VERSIONING_SYSTEM = "apple-generic"; 1223 | VERSION_INFO_PREFIX = ""; 1224 | }; 1225 | name = Release; 1226 | }; 1227 | BE73AD291CDCD102006F8B98 /* Debug */ = { 1228 | isa = XCBuildConfiguration; 1229 | buildSettings = { 1230 | CLANG_ENABLE_MODULES = YES; 1231 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 1232 | DEFINES_MODULE = YES; 1233 | DYLIB_COMPATIBILITY_VERSION = 1; 1234 | DYLIB_CURRENT_VERSION = 1; 1235 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1236 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1237 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1238 | LD_RUNPATH_SEARCH_PATHS = ( 1239 | "$(inherited)", 1240 | "@executable_path/Frameworks", 1241 | "@loader_path/Frameworks", 1242 | ); 1243 | PRODUCT_BUNDLE_IDENTIFIER = com.ashfurrow.Action; 1244 | PRODUCT_NAME = "$(TARGET_NAME)"; 1245 | SKIP_INSTALL = YES; 1246 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1247 | SWIFT_VERSION = 5.0; 1248 | }; 1249 | name = Debug; 1250 | }; 1251 | BE73AD2A1CDCD102006F8B98 /* Release */ = { 1252 | isa = XCBuildConfiguration; 1253 | buildSettings = { 1254 | CLANG_ENABLE_MODULES = YES; 1255 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 1256 | DEFINES_MODULE = YES; 1257 | DYLIB_COMPATIBILITY_VERSION = 1; 1258 | DYLIB_CURRENT_VERSION = 1; 1259 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1260 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; 1261 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1262 | LD_RUNPATH_SEARCH_PATHS = ( 1263 | "$(inherited)", 1264 | "@executable_path/Frameworks", 1265 | "@loader_path/Frameworks", 1266 | ); 1267 | PRODUCT_BUNDLE_IDENTIFIER = com.ashfurrow.Action; 1268 | PRODUCT_NAME = "$(TARGET_NAME)"; 1269 | SKIP_INSTALL = YES; 1270 | SWIFT_VERSION = 5.0; 1271 | }; 1272 | name = Release; 1273 | }; 1274 | /* End XCBuildConfiguration section */ 1275 | 1276 | /* Begin XCConfigurationList section */ 1277 | 1FCDDA621EAC315A006EB95B /* Build configuration list for PBXNativeTarget "Action-tvOS" */ = { 1278 | isa = XCConfigurationList; 1279 | buildConfigurations = ( 1280 | 1FCDDA601EAC315A006EB95B /* Debug */, 1281 | 1FCDDA611EAC315A006EB95B /* Release */, 1282 | ); 1283 | defaultConfigurationIsVisible = 0; 1284 | defaultConfigurationName = Release; 1285 | }; 1286 | 1FCDDA871EAC3295006EB95B /* Build configuration list for PBXNativeTarget "Action-macOS" */ = { 1287 | isa = XCConfigurationList; 1288 | buildConfigurations = ( 1289 | 1FCDDA881EAC3295006EB95B /* Debug */, 1290 | 1FCDDA891EAC3295006EB95B /* Release */, 1291 | ); 1292 | defaultConfigurationIsVisible = 0; 1293 | defaultConfigurationName = Release; 1294 | }; 1295 | 1FCDDA9B1EAC33B0006EB95B /* Build configuration list for PBXNativeTarget "Action-watchOS" */ = { 1296 | isa = XCConfigurationList; 1297 | buildConfigurations = ( 1298 | 1FCDDA9C1EAC33B0006EB95B /* Debug */, 1299 | 1FCDDA9D1EAC33B0006EB95B /* Release */, 1300 | ); 1301 | defaultConfigurationIsVisible = 0; 1302 | defaultConfigurationName = Release; 1303 | }; 1304 | 3DD965C51F5DC0E400C180FE /* Build configuration list for PBXNativeTarget "macOS-Tests" */ = { 1305 | isa = XCConfigurationList; 1306 | buildConfigurations = ( 1307 | 3DD965C31F5DC0E400C180FE /* Debug */, 1308 | 3DD965C41F5DC0E400C180FE /* Release */, 1309 | ); 1310 | defaultConfigurationIsVisible = 0; 1311 | defaultConfigurationName = Release; 1312 | }; 1313 | 7F5E6A6F1D7F08D2000B6076 /* Build configuration list for PBXNativeTarget "iOS-Tests" */ = { 1314 | isa = XCConfigurationList; 1315 | buildConfigurations = ( 1316 | 7F5E6A701D7F08D2000B6076 /* Debug */, 1317 | 7F5E6A711D7F08D2000B6076 /* Release */, 1318 | ); 1319 | defaultConfigurationIsVisible = 0; 1320 | defaultConfigurationName = Release; 1321 | }; 1322 | 7F612ADB1D7F13B800B93BC5 /* Build configuration list for PBXNativeTarget "Demo" */ = { 1323 | isa = XCConfigurationList; 1324 | buildConfigurations = ( 1325 | 7F612ADC1D7F13B800B93BC5 /* Debug */, 1326 | 7F612ADD1D7F13B800B93BC5 /* Release */, 1327 | ); 1328 | defaultConfigurationIsVisible = 0; 1329 | defaultConfigurationName = Release; 1330 | }; 1331 | BE73AD1A1CDCD101006F8B98 /* Build configuration list for PBXProject "Action" */ = { 1332 | isa = XCConfigurationList; 1333 | buildConfigurations = ( 1334 | BE73AD261CDCD102006F8B98 /* Debug */, 1335 | BE73AD271CDCD102006F8B98 /* Release */, 1336 | ); 1337 | defaultConfigurationIsVisible = 0; 1338 | defaultConfigurationName = Release; 1339 | }; 1340 | BE73AD281CDCD102006F8B98 /* Build configuration list for PBXNativeTarget "Action" */ = { 1341 | isa = XCConfigurationList; 1342 | buildConfigurations = ( 1343 | BE73AD291CDCD102006F8B98 /* Debug */, 1344 | BE73AD2A1CDCD102006F8B98 /* Release */, 1345 | ); 1346 | defaultConfigurationIsVisible = 0; 1347 | defaultConfigurationName = Release; 1348 | }; 1349 | /* End XCConfigurationList section */ 1350 | 1351 | /* Begin XCRemoteSwiftPackageReference section */ 1352 | CF23D7372ACBB67A0007A649 /* XCRemoteSwiftPackageReference "Nimble" */ = { 1353 | isa = XCRemoteSwiftPackageReference; 1354 | repositoryURL = "https://github.com/Quick/Nimble.git"; 1355 | requirement = { 1356 | kind = upToNextMajorVersion; 1357 | minimumVersion = 12.3.0; 1358 | }; 1359 | }; 1360 | CF8C4CC42ACBB637007FE582 /* XCRemoteSwiftPackageReference "Quick" */ = { 1361 | isa = XCRemoteSwiftPackageReference; 1362 | repositoryURL = "https://github.com/Quick/Quick.git"; 1363 | requirement = { 1364 | kind = upToNextMajorVersion; 1365 | minimumVersion = 7.3.0; 1366 | }; 1367 | }; 1368 | /* End XCRemoteSwiftPackageReference section */ 1369 | 1370 | /* Begin XCSwiftPackageProductDependency section */ 1371 | CF23D7352ACBB6530007A649 /* Quick */ = { 1372 | isa = XCSwiftPackageProductDependency; 1373 | package = CF8C4CC42ACBB637007FE582 /* XCRemoteSwiftPackageReference "Quick" */; 1374 | productName = Quick; 1375 | }; 1376 | CF23D7382ACBB67A0007A649 /* Nimble */ = { 1377 | isa = XCSwiftPackageProductDependency; 1378 | package = CF23D7372ACBB67A0007A649 /* XCRemoteSwiftPackageReference "Nimble" */; 1379 | productName = Nimble; 1380 | }; 1381 | CF23D73A2ACBB6850007A649 /* Nimble */ = { 1382 | isa = XCSwiftPackageProductDependency; 1383 | package = CF23D7372ACBB67A0007A649 /* XCRemoteSwiftPackageReference "Nimble" */; 1384 | productName = Nimble; 1385 | }; 1386 | CF8C4CC52ACBB637007FE582 /* Quick */ = { 1387 | isa = XCSwiftPackageProductDependency; 1388 | package = CF8C4CC42ACBB637007FE582 /* XCRemoteSwiftPackageReference "Quick" */; 1389 | productName = Quick; 1390 | }; 1391 | /* End XCSwiftPackageProductDependency section */ 1392 | }; 1393 | rootObject = BE73AD171CDCD101006F8B98 /* Project object */; 1394 | } 1395 | -------------------------------------------------------------------------------- /Action.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Action.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Action.xcodeproj/xcshareddata/xcschemes/Action-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Action.xcodeproj/xcshareddata/xcschemes/Action-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Action.xcodeproj/xcshareddata/xcschemes/Action-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Action.xcodeproj/xcshareddata/xcschemes/Action.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Action.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Action.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Action.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Action.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "cwlcatchexception", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/mattgallagher/CwlCatchException.git", 7 | "state" : { 8 | "revision" : "3b123999de19bf04905bc1dfdb76f817b0f2cc00", 9 | "version" : "2.1.2" 10 | } 11 | }, 12 | { 13 | "identity" : "cwlpreconditiontesting", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", 16 | "state" : { 17 | "revision" : "a23ded2c91df9156628a6996ab4f347526f17b6b", 18 | "version" : "2.1.2" 19 | } 20 | }, 21 | { 22 | "identity" : "nimble", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/Quick/Nimble.git", 25 | "state" : { 26 | "revision" : "edaedc1ec86f14ac6e2ca495b94f0ff7150d98d0", 27 | "version" : "12.3.0" 28 | } 29 | }, 30 | { 31 | "identity" : "quick", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/Quick/Quick.git", 34 | "state" : { 35 | "revision" : "ef9aaf3f634b3a1ab6f54f1173fe2400b36e7cb8", 36 | "version" : "7.3.0" 37 | } 38 | } 39 | ], 40 | "version" : 2 41 | } 42 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" >= 6.0 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" "6.6.0" 2 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Current master 5 | -------------- 6 | 7 | Nothing yet! 8 | 9 | 5.0.0 10 | ----- 11 | 12 | - **Breaking change** Avoid from getting errors in `CompletableAction.completions`. See [#25](https://github.com/RxSwiftCommunity/Action/pull/236) 13 | - Break retain cycle in `bind(to:input:)` 14 | - Use PublishRelay instead of PublishSubject 15 | 16 | 4.3.0 17 | ----- 18 | 19 | - Changed iOS Deployment target version to 9.0 20 | 21 | 4.2.0 22 | ----- 23 | 24 | - Add support for RxSwift 6.0. 25 | 26 | 4.1.0 27 | ----- 28 | 29 | - Added full support for Swift 5.0 30 | - Added full support for RxSwift 5.0 31 | - Added RxCocoa dependency for all non-test targets 32 | 33 | 4.0.1 34 | ----- 35 | 36 | - Remove RxAtomic references 37 | 38 | 4.0.0 39 | ------- 40 | - Add `completions` property to `CompletableAction` 41 | - Change `inputs` type to `AnyObserver` 42 | 43 | 3.11.0 44 | ------- 45 | - Introduction of `underlyingError` observable which returns a `Swift.Error` element type. 46 | - Updated specs that were breaking in `Circle CI` pipeline 47 | 48 | 3.10.2 49 | ------- 50 | - Update the project and xcworkspace and make it compatible with Version 10.1 (10B61) 51 | - Update the Unit test target and make it compatible with Version 10.1 (10B61) 52 | - Moved CI to circle CI 2.0 53 | - Fix issue [#155](https://github.com/RxSwiftCommunity/Action/issues/155) 54 | - Fix issue [#179](https://github.com/RxSwiftCommunity/Action/issues/179) 55 | 56 | 3.10.0 57 | ------- 58 | - Adding syntax sugar `execute()` method on `Action` when `Input` is `Void`. [#171](https://github.com/RxSwiftCommunity/Action/pull/171) 59 | - Raises minimum watchOS deployment target to 3.0, to match RxSwift. 60 | 61 | 3.9.1 62 | ----- 63 | 64 | - Less restrictive RxSwift/RxCocoa dependencies in podspec, now supporting RxSwift/RxCocoa 4.x starting with version 4.3 65 | 66 | 3.9.0 67 | ----- 68 | - Fix Action Demo build failure 69 | - Added missing support for Swift 4.2 after 3.7.0 70 | 71 | 72 | 3.8.0 73 | ----- 74 | 75 | - Fix build failure on New Build System (default on Xcode 10) [#151](https://github.com/RxSwiftCommunity/Action/pull/151) 76 | 77 | 3.7.0 78 | ----- 79 | - Added full support for Swift 4.2 80 | - Added full support for RxSwift 4.3 81 | 82 | 3.6.0 83 | ----- 84 | - Updated `Semantic Versioning` to reflect what is actaully released both on `Pod` and `Carthage` 85 | - Added full support for Swift 4.1 86 | - Added full support for RxSwift 4.2.0 87 | - UIRefreshControl support: binding to an action (or CocoaAction) starts the action itself and updates the control's refreshing status 88 | 89 | 3.5.0 90 | ----- 91 | 92 | - Add convenience initializer with work factories returning `PrimitiveSequence` or any other `ObservableConvertibleType` [#125](https://github.com/RxSwiftCommunity/Action/pull/125) 93 | - Introduce `CompletableAction`, a typealias for action that only completes without emitting any elements [#125](https://github.com/RxSwiftCommunity/Action/pull/125) 94 | 95 | 3.4.0 96 | ----- 97 | - Added full support for Swift 4.0 98 | - Added full support for RxSwift 4.0.0 99 | - Preserved old behavior for `shareReplay(1)` api changes from `RxSwift`. [#110](https://github.com/RxSwiftCommunity/Action/pull/110) 100 | 101 | 102 | Version table 103 | ------------- 104 | 105 | | Swift version | RxSwift version | Action version | 106 | | ------------- | --------------- | -------------- | 107 | | Swift 3.0 | v3.2.* | v2.2.0 | 108 | | Swift 3.2 | v3.6.* | v3.2.0 | 109 | | **Swift 4** | **v4.0.0** | **v3.4.0** | 110 | | Swift 4.1 | **v4.2.0** | **v3.6.0** | 111 | 112 | 3.2.0 113 | ----- 114 | - Add macOS bindings for NSControl and NSButton 115 | 116 | 3.1.1 117 | ----- 118 | 119 | - Loosens dependency on RxSwift. 120 | 121 | 3.1.0 122 | ----- 123 | 124 | - Replace `PublishSubject` with `InputSubject` [#92](https://github.com/RxSwiftCommunity/Action/pull/92) 125 | - Added missing sources for watchOS target [#95](https://github.com/RxSwiftCommunity/Action/pull/95) 126 | 127 | 3.0.0 128 | ----- 129 | 130 | - Change `bindTo([...])` methods to `bind(to: [...])` to better align with the revised Rx API 131 | - Update Rx invocations to resolve deprecation warnings related to same 132 | 133 | 2.2.2 134 | ----- 135 | 136 | - Remove `RxBlocking` from Linked Libraries in `Action` target 137 | 138 | 2.2.1 139 | ----- 140 | 141 | - Loosens dependency on RxSwift. 142 | 143 | 2.2.0 144 | ----- 145 | 146 | - Fixes [#63](https://github.com/RxSwiftCommunity/Action/issues/63), related to the default enabled state. 147 | - Adds `bindTo(action:)` for non-CocoaAction. 148 | 149 | 2.1.1 150 | ----- 151 | 152 | - Not replay `executionObservables` to fix `execute(_:)`. See [#64](https://github.com/RxSwiftCommunity/Action/pull/56). 153 | 154 | 2.1.0 155 | ----- 156 | 157 | - Refactors internal implementation. See [#56](https://github.com/RxSwiftCommunity/Action/pull/56) and [#59](https://github.com/RxSwiftCommunity/Action/pull/59). 158 | - Adds SwiftPM support. See [#58](https://github.com/RxSwiftCommunity/Action/pull/58). 159 | 160 | 2.0.0-beta.1 161 | ------------ 162 | 163 | - Adds Swift 3 support. See [#46](https://github.com/RxSwiftCommunity/Action/pull/46). 164 | 165 | 1.2.2 166 | ----- 167 | 168 | - Added inputs subject to trigger actins by observables. See [#37](https://github.com/RxSwiftCommunity/Action/pull/37). 169 | - Fixes a problem with observable deallocation related to `rx_action` button property. See [#33](https://github.com/RxSwiftCommunity/Action/pull/33). 170 | - Improved Carthage compatibility. See [#34](https://github.com/RxSwiftCommunity/Action/pull/34). 171 | - Swift 2.3 support. 172 | 173 | 1.2.1 174 | ----- 175 | 176 | - Relaxes dependency requirements. 177 | 178 | 1.2.0 179 | ----- 180 | 181 | - Updates to RxSwift 2.1.0. 182 | 183 | 1.1.0 184 | ----- 185 | 186 | - Transitioned podspec to new remote URL (see [#15](https://github.com/RxSwiftCommunity/Action/issues/15)). 187 | - Moved to RxSwift 2.0 🎉 188 | 189 | 1.2.2 190 | ----- 191 | 192 | - Fixes memory leak when used on buttons. See [#21](https://github.com/RxSwiftCommunity/Action/issues/21). 193 | 194 | 1.2.1 195 | ----- 196 | 197 | - Relaxes dependency requirements. 198 | 199 | 1.0.0 200 | ----- 201 | 202 | - Unbounded replaying of event values (see [#3](https://github.com/ashfurrow/Action/issues/3)). 203 | 204 | 0.3.0 205 | ----- 206 | 207 | Added `UIAlertAction` support. 208 | 209 | 0.2.1 210 | ----- 211 | 212 | - Fixes `enabledIf:` parameter to be `Observable`, `where B: BooleanType`. 213 | 214 | 0.2.0 215 | ----- 216 | 217 | - Added tvOS support to UIButton extension. 218 | - Changes `enabledIf` to accept `Observable` instead of `Bool`. 219 | 220 | 0.1.0 221 | ----- 222 | 223 | - Initial release. 224 | -------------------------------------------------------------------------------- /Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by Ash Furrow on 2015-11-14. 6 | // Copyright © 2015 Ash Furrow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 51 | 54 | 58 | 62 | 63 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 3.6.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by Ash Furrow on 2015-11-14. 6 | // Copyright © 2015 Ash Furrow. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import Action 13 | 14 | enum SharedInput { 15 | case button(String) 16 | case barButton 17 | } 18 | 19 | class ViewController: UIViewController { 20 | @IBOutlet weak var button: UIButton! 21 | @IBOutlet weak var workingLabel: UILabel! 22 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView! 23 | 24 | @IBOutlet weak var button1: UIButton! 25 | @IBOutlet weak var button2: UIButton! 26 | 27 | @IBOutlet weak var scrollView: UIScrollView! 28 | 29 | var disposableBag = DisposeBag() 30 | let sharedAction = Action { input in 31 | switch input { 32 | case .barButton: return Observable.just("UIBarButtonItem with 3 seconds delay").delaySubscription(.seconds(3), scheduler: MainScheduler.instance) 33 | case .button(let title): return .just("UIButton " + title) 34 | } 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | // Demo: add an action to a button in the view 41 | let action = CocoaAction { 42 | print("Button was pressed, showing an alert and keeping the activity indicator spinning while alert is displayed") 43 | return Observable.create { [weak self] observer -> Disposable in 44 | // Demo: show an alert and complete the view's button action once the alert's OK button is pressed 45 | let alertController = UIAlertController(title: "Hello world", message: "This alert was triggered by a button action", preferredStyle: .alert) 46 | var ok = UIAlertAction.Action("OK", style: .default) 47 | ok.rx.action = CocoaAction { 48 | print("Alert's OK button was pressed") 49 | observer.onCompleted() 50 | return .empty() 51 | } 52 | alertController.addAction(ok) 53 | self?.present(alertController, animated: true, completion: nil) 54 | 55 | return Disposables.create() 56 | } 57 | } 58 | 59 | button.rx.action = action 60 | 61 | // Demo: add an action to a UIBarButtonItem in the navigation item 62 | self.navigationItem.rightBarButtonItem?.rx.action = CocoaAction { 63 | print("Bar button item was pressed, simulating a 2 second action") 64 | return Observable.empty().delaySubscription(.seconds(2), scheduler: MainScheduler.instance) 65 | } 66 | 67 | // Demo: observe the output of both actions, spin an activity indicator 68 | // while performing the work 69 | Observable.combineLatest( 70 | button.rx.action!.executing, 71 | self.navigationItem.rightBarButtonItem!.rx.action!.executing) { a,b in 72 | // we combine two boolean observable and output one boolean 73 | return a || b 74 | } 75 | .distinctUntilChanged() 76 | .subscribe(onNext: { [weak self] executing in 77 | // every time the execution status changes, spin an activity indicator 78 | self?.workingLabel.isHidden = !executing 79 | if (executing) { 80 | self?.activityIndicator.startAnimating() 81 | } 82 | else { 83 | self?.activityIndicator.stopAnimating() 84 | } 85 | }) 86 | .disposed(by: self.disposableBag) 87 | 88 | button1.rx.bind(to: sharedAction, input: .button("Button 1")) 89 | 90 | button2.rx.bind(to: sharedAction) { _ in 91 | return .button("Button 2") 92 | } 93 | self.navigationItem.leftBarButtonItem?.rx.bind(to: sharedAction, input: .barButton) 94 | 95 | sharedAction.executing.debounce(.seconds(0), scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in 96 | if (executing) { 97 | self?.activityIndicator.startAnimating() 98 | } 99 | else { 100 | self?.activityIndicator.stopAnimating() 101 | } 102 | }) 103 | .disposed(by: self.disposableBag) 104 | 105 | sharedAction.elements.subscribe(onNext: { string in 106 | print(string + " pressed") 107 | }) 108 | .disposed(by: self.disposableBag) 109 | 110 | 111 | var refreshControl = UIRefreshControl() 112 | self.scrollView.addSubview(refreshControl) 113 | refreshControl.rx.action = CocoaAction { 114 | print("Reloading was triggered!") 115 | return Observable 116 | .empty() 117 | .delaySubscription(.seconds(2), scheduler: MainScheduler.instance) 118 | .do(onCompleted:{ 119 | print ("Reloading completed!") 120 | }) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Ash Furrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "RxSwift", 6 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9dcaa4b333db437b0fbfaf453fad29069044a8b4", 10 | "version": "6.6.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Action", 7 | products: [ 8 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 9 | .library( 10 | name: "Action", 11 | targets: ["Action"]), 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0")), 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 18 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 19 | .target( 20 | name: "Action", 21 | dependencies: ["RxSwift", "RxCocoa"], 22 | path: "Sources/Action") 23 | ] 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/RxSwiftCommunity/Action/actions/workflows/ci.yml/badge.svg)](https://github.com/RxSwiftCommunity/Action/actions/workflows/ci.yml) 2 | 3 | Action 4 | ====== 5 | 6 | This library is used with [RxSwift](https://github.com/ReactiveX/RxSwift) to provide an abstraction on top of observables: actions. 7 | 8 | An action is a way to say "hey, later I'll need you to subscribe to this thing." It's actually a lot more involved than that. 9 | 10 | Actions accept a `workFactory`: a closure that takes some input and produces an observable. When `execute()` is called on the action, the `workFactory` gets passed this parameter and the action subscribes to the observable that gets returned. 11 | 12 | An action: 13 | 14 | - Can only be executed while "enabled" (`true` if unspecified). 15 | - Only executes one thing at a time. 16 | - Aggregates next/error events across individual executions. 17 | 18 | Oh, and it has this really Swift thing with `UIButton` that's pretty cool. It'll manage the button's enabled state, make sure the button is disabled while your work is being done, all that stuff 👍 19 | 20 | Usage 21 | ----- 22 | 23 | You have to pass a `workFactory` that takes input and returns an `Observable`. This represents some work that needs to be accomplished. Whenever you call `execute()`, you pass in input that's fed to the work factory. The `Action` will subscribe to the observable and emit the `Next` events on its `elements` property. If the observable errors, the error is sent as a `Next` even on the `errors` property. Neat. 24 | 25 | Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The `executing` property sends `true` and `false` values as `Next` events. 26 | 27 | ```swift 28 | action: Action = Action(workFactory: { input in 29 | return networkLibrary.checkEmailExists(input) 30 | }) 31 | 32 | ... 33 | 34 | action.execute("ash@ashfurrow.com") 35 | ``` 36 | 37 | Notice that the first generic parameter is the type of the input, and the second is the type of observable that `workFactory` creates. You can think of it a bit like the action's "output." 38 | 39 | You can also specify an `enabledIf` parameter to the `Action` initializer. 40 | 41 | ```swift 42 | let validEmailAddress = emailTextField.rx.text.map(isValidEmail) 43 | 44 | action: Action = Action(enabledIf: validEmailAddress, workFactory: { input in 45 | return networkLibrary.checkEmailExists(input) 46 | }) 47 | ``` 48 | 49 | Now `execute()` only does the work if the email address is valid. Super cool! 50 | 51 | (Note that `enabledIf` isn't the same as the `enabled` property. You pass in `enabledIf` and the action uses that (combined with its current executing state) to determine if it's currently enabled.) 52 | 53 | What's _really_ cool is the `UIButton` extension. It accepts a `CocoaAction`, which is just `Action`. 54 | 55 | ```swift 56 | button.rx.action = action 57 | ``` 58 | 59 | Now when the button is pressed, the action is executed. The button's `enabled` state is bound to the action's `enabled` property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out [this code example of CocoaAction _in_ action](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift). 60 | 61 | If you'd like to use `Action` to do a complex operation such as file download with download progress report (to update progress bar in the UI for example) you'd use `Action` instead of `CocoaAction`. Out of the box `CocoaAction` can't emit progress values, your own `Action` will do that. For details refer to [this article](http://www.sm-cloud.com/rxswift-action/). 62 | 63 | If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bindTo` on each `UIButton` with a closure that returns correct input. 64 | 65 | ```swift 66 | let button1 = UIButton() 67 | let button2 = UIButton() 68 | 69 | let action = Action { input in 70 | print(input) 71 | return .just(input) 72 | } 73 | button1.rx.bindTo(action) { _ in return "Hello"} 74 | button2.rx.bindTo(action) { _ in return "Goodbye"} 75 | ``` 76 | 77 | `button1` and `button2` are sharing the same `Action`, but they are feeding it with different input (`Hello` and `Goodbye` that will be printed for corresponding tap). 78 | 79 | A more complex use case can be a single action related to a `UIViewController` that manages your navigation, error handling and loading state. With this approach, you can have as many `UIButton`s (or `UIBarButtonItem`s) as you want and subscribe to `executing`, `errors`, `elements` and `completions` once and in a single common place. 80 | 81 | There's also a really cool extension on `UIAlertAction`, used by [`UIAlertController`](http://ashfurrow.com/blog/uialertviewcontroller-example/). One catch: because of the limitations of that class, you can't instantiate it with the normal initializer. Instead, call this class method: 82 | 83 | ```swift 84 | let action = UIAlertAction.Action("Hi", style: .default) 85 | ``` 86 | 87 | Installing 88 | ---------- 89 | 90 | ### CocoaPods 91 | 92 | Just add the line below to your Podfile: 93 | 94 | ```ruby 95 | pod 'Action' 96 | ``` 97 | 98 | Then run `pod install` and that'll be 👌 99 | 100 | ### Carthage 101 | 102 | Add this to `Cartfile` 103 | 104 | ``` 105 | github "RxSwiftCommunity/Action" ~> 5.0.0 106 | ``` 107 | 108 | If you are using RxSwift 3.2.0 or below, Use Action `~2.2.0` instead! 109 | 110 | then run 111 | 112 | ```sh 113 | > carthage update 114 | ``` 115 | 116 | Thanks 117 | ------ 118 | 119 | This library is (pretty obviously) inspired by [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa)'s [`Action` class](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Action.swift). Those developers deserve a lot of thanks! 120 | 121 | License 122 | ------- 123 | 124 | MIT obvs. 125 | 126 | ![Permissive licenses are the only licenses permitted in the Q continuum.](https://38.media.tumblr.com/4ca19ffae09cb09520cbb5611f0a17e9/tumblr_n13vc9nm1Q1svlvsyo6_250.gif) 127 | -------------------------------------------------------------------------------- /Sources/Action/Action+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | public extension Action { 5 | /// Filters out `notEnabled` errors and returns 6 | /// only underlying error from `ActionError` 7 | var underlyingError: Observable { 8 | return errors.flatMap { actionError -> Observable in 9 | guard case .underlyingError(let error) = actionError else { 10 | return Observable.empty() 11 | } 12 | return Observable.just(error) 13 | } 14 | } 15 | } 16 | 17 | public extension Action where Input == Void { 18 | /// use to trigger an action. 19 | @discardableResult 20 | func execute() -> Observable { 21 | return execute(()) 22 | } 23 | } 24 | 25 | public extension CompletableAction { 26 | /// Emits everytime work factory completes. 27 | var completions: Observable { 28 | return executionObservables 29 | .flatMap { execution in 30 | execution.flatMap { _ in Observable.empty() } 31 | .concat(Observable.just(())) 32 | .catchAndReturn(nil) 33 | } 34 | .compactMap { $0 } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Action/Action+Internal.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | internal struct AssociatedKeys { 5 | static var Action: StaticString = "rx_action" 6 | static var DisposeBag: StaticString = "rx_disposeBag" 7 | } 8 | 9 | // Note: Actions performed in this extension are _not_ locked 10 | // So be careful! 11 | internal extension NSObject { 12 | 13 | // A dispose bag to be used exclusively for the instance's rx.action. 14 | var actionDisposeBag: DisposeBag { 15 | var disposeBag: DisposeBag 16 | 17 | if let lookup = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBag) as? DisposeBag { 18 | disposeBag = lookup 19 | } else { 20 | disposeBag = DisposeBag() 21 | objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, disposeBag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 22 | } 23 | 24 | return disposeBag 25 | } 26 | 27 | // Resets the actionDisposeBag to nil, disposeing of any subscriptions within it. 28 | func resetActionDisposeBag() { 29 | objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 30 | } 31 | 32 | // Uses objc_sync on self to perform a locked operation. 33 | func doLocked(_ closure: () -> Void) { 34 | objc_sync_enter(self); defer { objc_sync_exit(self) } 35 | closure() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Action/Action.h: -------------------------------------------------------------------------------- 1 | // 2 | // Action.h 3 | // Action 4 | // 5 | // Created by Cezary Kopacz on 06.05.2016. 6 | // Copyright © 2016 CezaryKopacz. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Action. 12 | FOUNDATION_EXPORT double ActionVersionNumber; 13 | 14 | //! Project version string for Action. 15 | FOUNDATION_EXPORT const unsigned char ActionVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Action/Action.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import RxCocoa 4 | 5 | /// Typealias for compatibility with UIButton's rx.action property. 6 | public typealias CocoaAction = Action 7 | /// Typealias for actions with work factory returns `Completable`. 8 | public typealias CompletableAction = Action 9 | 10 | /// Possible errors from invoking execute() 11 | public enum ActionError: Error { 12 | case notEnabled 13 | case underlyingError(Error) 14 | } 15 | 16 | /** 17 | Represents a value that accepts a workFactory which takes some Observable as its input 18 | and produces an Observable as its output. 19 | 20 | When this excuted via execute() or inputs subject, it passes its parameter to this closure and subscribes to the work. 21 | */ 22 | public final class Action { 23 | public typealias WorkFactory = (Input) -> Observable 24 | 25 | public let _enabledIf: Observable 26 | public let workFactory: WorkFactory 27 | 28 | /// Bindable sink for inputs that triggers execution of action. 29 | public let inputs: AnyObserver 30 | 31 | /// Errors aggrevated from invocations of execute(). 32 | /// Delivered on whatever scheduler they were sent from. 33 | public let errors: Observable 34 | 35 | /// Whether or not we're currently executing. 36 | /// Delivered on whatever scheduler they were sent from. 37 | public let elements: Observable 38 | 39 | /// Whether or not we're currently executing. 40 | public let executing: Observable 41 | 42 | /// Observables returned by the workFactory. 43 | /// Useful for sending results back from work being completed 44 | /// e.g. response from a network call. 45 | public let executionObservables: Observable> 46 | 47 | /// Whether or not we're enabled. Note that this is a *computed* sequence 48 | /// property based on enabledIf initializer and if we're currently executing. 49 | /// Always observed on MainScheduler. 50 | public let enabled: Observable 51 | 52 | private let disposeBag = DisposeBag() 53 | 54 | public convenience init( 55 | enabledIf: Observable = Observable.just(true), 56 | workFactory: @escaping (Input) -> O 57 | ) where O.Element == Element { 58 | self.init(enabledIf: enabledIf) { 59 | workFactory($0).asObservable() 60 | } 61 | } 62 | 63 | public init( 64 | enabledIf: Observable = Observable.just(true), 65 | workFactory: @escaping WorkFactory) { 66 | 67 | self._enabledIf = enabledIf 68 | self.workFactory = workFactory 69 | 70 | let enabledSubject = BehaviorSubject(value: false) 71 | enabled = enabledSubject.asObservable() 72 | 73 | let errorRelay = PublishRelay() 74 | errors = errorRelay.asObservable() 75 | 76 | let inputsRelay = PublishRelay() 77 | inputs = AnyObserver { event in 78 | guard case .next(let value) = event else { return } 79 | inputsRelay.accept(value) 80 | } 81 | 82 | executionObservables = inputsRelay 83 | .withLatestFrom(enabled) { input, enabled in (input, enabled) } 84 | .flatMap { input, enabled -> Observable> in 85 | if enabled { 86 | return Observable.of(workFactory(input) 87 | .do(onError: { errorRelay.accept(.underlyingError($0)) }) 88 | .share(replay: 1, scope: .forever)) 89 | } else { 90 | errorRelay.accept(.notEnabled) 91 | return Observable.empty() 92 | } 93 | } 94 | .share() 95 | 96 | elements = executionObservables 97 | .flatMap { $0.catch { _ in Observable.empty() } } 98 | 99 | executing = executionObservables.flatMap { 100 | execution -> Observable in 101 | let execution = execution 102 | .flatMap { _ in Observable.empty() } 103 | .catch { _ in Observable.empty() } 104 | 105 | return Observable.concat([Observable.just(true), 106 | execution, 107 | Observable.just(false)]) 108 | } 109 | .startWith(false) 110 | .share(replay: 1, scope: .forever) 111 | 112 | Observable 113 | .combineLatest(executing, enabledIf) { !$0 && $1 } 114 | .bind(to: enabledSubject) 115 | .disposed(by: disposeBag) 116 | } 117 | 118 | @discardableResult 119 | public func execute(_ value: Input) -> Observable { 120 | defer { 121 | inputs.onNext(value) 122 | } 123 | 124 | let subject = ReplaySubject.createUnbounded() 125 | 126 | let work = executionObservables 127 | .map { $0.catch { throw ActionError.underlyingError($0) } } 128 | 129 | let error = errors 130 | .map { Observable.error($0) } 131 | 132 | work.amb(error) 133 | .take(1) 134 | .flatMap { $0 } 135 | .subscribe(subject) 136 | .disposed(by: disposeBag) 137 | 138 | return subject.asObservable() 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Sources/Action/CommonUI/Button+Action.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) || os(macOS) 2 | import Foundation 3 | #if os(iOS) || os(tvOS) 4 | import UIKit 5 | public typealias Button = UIKit.UIButton 6 | #elseif os(macOS) 7 | import Cocoa 8 | public typealias Button = Cocoa.NSButton 9 | #endif 10 | import RxSwift 11 | import RxCocoa 12 | 13 | public extension Reactive where Base: Button { 14 | /// Binds enabled state of action to button, and subscribes to rx_tap to execute action. 15 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 16 | /// them, set the rx.action to nil or another action. 17 | var action: CocoaAction? { 18 | get { 19 | var action: CocoaAction? 20 | action = objc_getAssociatedObject(self.base, &AssociatedKeys.Action) as? Action 21 | return action 22 | } 23 | 24 | set { 25 | // Store new value. 26 | objc_setAssociatedObject(self.base, &AssociatedKeys.Action, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 27 | 28 | // This effectively disposes of any existing subscriptions. 29 | self.base.resetActionDisposeBag() 30 | 31 | // Set up new bindings, if applicable. 32 | if let action = newValue { 33 | action 34 | .enabled 35 | .bind(to: self.isEnabled) 36 | .disposed(by: self.base.actionDisposeBag) 37 | 38 | // Technically, this file is only included on tv/iOS platforms, 39 | // so this optional will never be nil. But let's be safe 😉 40 | let lookupControlEvent: ControlEvent? 41 | 42 | #if os(tvOS) 43 | lookupControlEvent = self.primaryAction 44 | #elseif os(iOS) || os(macOS) 45 | lookupControlEvent = self.tap 46 | #endif 47 | 48 | guard let controlEvent = lookupControlEvent else { 49 | return 50 | } 51 | 52 | controlEvent 53 | .bind(to: action.inputs) 54 | .disposed(by: self.base.actionDisposeBag) 55 | } 56 | } 57 | } 58 | 59 | /// Binds enabled state of action to button, and subscribes to rx_tap to execute action with given input transform. 60 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 61 | /// them, call bindToAction with another action or call unbindAction(). 62 | func bind(to action: Action, inputTransform: @escaping (Base) -> (Input)) { 63 | // This effectively disposes of any existing subscriptions. 64 | unbindAction() 65 | 66 | // Technically, this file is only included on tv/iOS platforms, 67 | // so this optional will never be nil. But let's be safe 😉 68 | let lookupControlEvent: ControlEvent? 69 | 70 | #if os(tvOS) 71 | lookupControlEvent = self.primaryAction 72 | #elseif os(iOS) || os(macOS) 73 | lookupControlEvent = self.tap 74 | #endif 75 | 76 | guard let controlEvent = lookupControlEvent else { 77 | return 78 | } 79 | self.bind(to: action, controlEvent: controlEvent, inputTransform: inputTransform) 80 | } 81 | 82 | /// Binds enabled state of action to button, and subscribes to rx_tap to execute action with given input value. 83 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 84 | /// them, call bindToAction with another action or call unbindAction(). 85 | func bind(to action: Action, input: Input) { 86 | self.bind(to: action) { _ in input } 87 | } 88 | } 89 | #endif 90 | -------------------------------------------------------------------------------- /Sources/Action/CommonUI/Control+Action.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) || os(macOS) 2 | import Foundation 3 | #if os(iOS) || os(tvOS) 4 | import UIKit 5 | public typealias Control = UIKit.UIControl 6 | #elseif os(macOS) 7 | import Cocoa 8 | public typealias Control = Cocoa.NSControl 9 | #endif 10 | import RxSwift 11 | import RxCocoa 12 | 13 | public extension Reactive where Base: Control { 14 | /// Binds enabled state of action to control, and subscribes action's execution to provided controlEvents. 15 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 16 | /// them, set the rx.action to nil or another action, or call unbindAction(). 17 | func bind(to action: Action, controlEvent: ControlEvent, inputTransform: @escaping (Base) -> (Input)) { 18 | // This effectively disposes of any existing subscriptions. 19 | unbindAction() 20 | 21 | // For each tap event, use the inputTransform closure to provide an Input value to the action 22 | controlEvent 23 | .withUnretained(self.base) 24 | .map(\.0) 25 | .map(inputTransform) 26 | .bind(to: action.inputs) 27 | .disposed(by: self.base.actionDisposeBag) 28 | 29 | // Bind the enabled state of the control to the enabled state of the action 30 | action 31 | .enabled 32 | .bind(to: self.isEnabled) 33 | .disposed(by: self.base.actionDisposeBag) 34 | } 35 | 36 | /// Unbinds any existing action, disposing of all subscriptions. 37 | func unbindAction() { 38 | self.base.resetActionDisposeBag() 39 | } 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/Action/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.6.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Action/UIKitExtensions/UIAlertAction+Action.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import UIKit 3 | import RxSwift 4 | import RxCocoa 5 | 6 | #if swift(>=4.2) 7 | public typealias ActionStyle = UIAlertAction.Style 8 | #else 9 | public typealias ActionStyle = UIAlertActionStyle 10 | #endif 11 | 12 | public extension UIAlertAction { 13 | 14 | static func Action(_ title: String?, style: ActionStyle) -> UIAlertAction { 15 | return UIAlertAction(title: title, style: style, handler: { action in 16 | action.rx.action?.execute() 17 | return 18 | }) 19 | } 20 | } 21 | 22 | public extension Reactive where Base: UIAlertAction { 23 | 24 | /// Binds enabled state of action to button, and subscribes to rx_tap to execute action. 25 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 26 | /// them, set the rx.action to nil or another action. 27 | var action: CocoaAction? { 28 | get { 29 | var action: CocoaAction? 30 | action = objc_getAssociatedObject(base, &AssociatedKeys.Action) as? Action 31 | return action 32 | } 33 | 34 | set { 35 | // Store new value. 36 | objc_setAssociatedObject(base, &AssociatedKeys.Action, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 37 | 38 | // This effectively disposes of any existing subscriptions. 39 | self.base.resetActionDisposeBag() 40 | 41 | // Set up new bindings, if applicable. 42 | if let action = newValue { 43 | action 44 | .enabled 45 | .bind(to: self.enabled) 46 | .disposed(by: self.base.actionDisposeBag) 47 | } 48 | } 49 | } 50 | 51 | var enabled: AnyObserver { 52 | return AnyObserver { [weak base] event in 53 | MainScheduler.ensureExecutingOnScheduler() 54 | 55 | switch event { 56 | case .next(let value): 57 | base?.isEnabled = value 58 | case .error(let error): 59 | let error = "Binding error to UI: \(error)" 60 | #if DEBUG 61 | fatalError(error) 62 | #else 63 | print(error) 64 | #endif 65 | break 66 | case .completed: 67 | break 68 | } 69 | } 70 | } 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) || os(tvOS) 2 | import UIKit 3 | import RxSwift 4 | import RxCocoa 5 | import ObjectiveC 6 | 7 | public extension Reactive where Base: UIBarButtonItem { 8 | 9 | /// Binds enabled state of action to bar button item, and subscribes to rx_tap to execute action. 10 | /// These subscriptions are managed in a private, inaccessible dispose bag. To cancel 11 | /// them, set the rx.action to nil or another action. 12 | var action: CocoaAction? { 13 | get { 14 | var action: CocoaAction? 15 | action = objc_getAssociatedObject(self.base, &AssociatedKeys.Action) as? Action 16 | return action 17 | } 18 | 19 | set { 20 | // Store new value. 21 | objc_setAssociatedObject(self.base, &AssociatedKeys.Action, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 22 | 23 | // This effectively disposes of any existing subscriptions. 24 | self.base.resetActionDisposeBag() 25 | 26 | // Set up new bindings, if applicable. 27 | if let action = newValue { 28 | action 29 | .enabled 30 | .bind(to: self.isEnabled) 31 | .disposed(by: self.base.actionDisposeBag) 32 | 33 | self.tap.subscribe(onNext: { 34 | action.execute() 35 | }) 36 | .disposed(by: self.base.actionDisposeBag) 37 | } 38 | } 39 | } 40 | 41 | func bind(to action: Action, inputTransform: @escaping (Base) -> (Input)) { 42 | unbindAction() 43 | 44 | self.tap 45 | .withUnretained(self.base) 46 | .map(\.0) 47 | .map(inputTransform) 48 | .bind(to: action.inputs) 49 | .disposed(by: self.base.actionDisposeBag) 50 | 51 | action 52 | .enabled 53 | .bind(to: self.isEnabled) 54 | .disposed(by: self.base.actionDisposeBag) 55 | } 56 | 57 | func bind(to action: Action, input: Input) { 58 | self.bind(to: action) { _ in input} 59 | } 60 | 61 | /// Unbinds any existing action, disposing of all subscriptions. 62 | func unbindAction() { 63 | self.base.resetActionDisposeBag() 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/Action/UIKitExtensions/UIRefreshControl+Action.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import RxSwift 4 | import RxCocoa 5 | 6 | public extension Reactive where Base: UIRefreshControl { 7 | 8 | // Binds enabled state of action to refresh control, and subscribes to value changed control event to execute action. 9 | // These subscriptions are managed in a private, inaccessible dispose bag. To cancel 10 | // them, set the rx.action to nil or another action. 11 | 12 | var action: CocoaAction? { 13 | get { 14 | var action: CocoaAction? 15 | action = objc_getAssociatedObject(base, &AssociatedKeys.Action) as? Action 16 | return action 17 | } 18 | 19 | set { 20 | // Store new value. 21 | objc_setAssociatedObject(base, &AssociatedKeys.Action, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 22 | 23 | // This effectively disposes of any existing subscriptions. 24 | self.base.resetActionDisposeBag() 25 | 26 | // Set up new bindings, if applicable. 27 | if let action = newValue { 28 | self.bind(to: action, input: ()) 29 | } 30 | } 31 | } 32 | 33 | func bind(to action: Action, inputTransform: @escaping (Base) -> (Input)) { 34 | unbindAction() 35 | 36 | self.controlEvent(.valueChanged) 37 | .withUnretained(self.base) 38 | .map(\.0) 39 | .map(inputTransform) 40 | .bind(to: action.inputs) 41 | .disposed(by: self.base.actionDisposeBag) 42 | 43 | action 44 | .executing 45 | .bind(to: self.isRefreshing) 46 | .disposed(by: self.base.actionDisposeBag) 47 | 48 | action 49 | .enabled 50 | .bind(to: self.isEnabled) 51 | .disposed(by: self.base.actionDisposeBag) 52 | } 53 | 54 | func bind(to action: Action, input: Input) { 55 | self.bind(to: action) { _ in input} 56 | } 57 | 58 | /// Unbinds any existing action, disposing of all subscriptions. 59 | func unbindAction() { 60 | self.base.resetActionDisposeBag() 61 | } 62 | } 63 | #endif 64 | -------------------------------------------------------------------------------- /Tests/ActionTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import RxTest 5 | @testable import Action 6 | 7 | class ActionTests: QuickSpec { 8 | override class func spec() { 9 | var scheduler: TestScheduler! 10 | var disposeBag: DisposeBag! 11 | 12 | beforeEach { 13 | scheduler = TestScheduler(initialClock: 0) 14 | disposeBag = DisposeBag() 15 | } 16 | 17 | describe("completable action") { 18 | var inputs: TestableObserver! 19 | var action: CompletableAction! 20 | 21 | beforeEach { 22 | inputs = scheduler.createObserver(String.self) 23 | action = CompletableAction { input -> Completable in 24 | inputs.onNext(input) 25 | return Observable.empty().asCompletable() 26 | } 27 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 28 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 29 | } 30 | 31 | afterEach { 32 | action = nil 33 | } 34 | 35 | it("receives generated inputs") { 36 | scheduler.start() 37 | expect(inputs.events).to(match(TestEvents.inputs)) 38 | } 39 | it("emits nothing on `elements`") { 40 | let elements = scheduler.createObserver(Never.self) 41 | action.elements.bind(to: elements).disposed(by: disposeBag) 42 | scheduler.start() 43 | expect(elements.events.count).to(match(0)) 44 | } 45 | it("emits on `completions` when completed") { 46 | let completions = scheduler.createObserver(Void.self) 47 | action.completions.bind(to: completions).disposed(by: disposeBag) 48 | scheduler.start() 49 | expect(completions.events.contains { $0.time == 10}).to(beTrue()) 50 | expect(completions.events.contains { $0.time == 20}).to(beTrue()) 51 | } 52 | } 53 | describe("completable action finished with error") { 54 | var action: CompletableAction! 55 | beforeEach { 56 | action = CompletableAction { errorMaybe -> Completable in 57 | if let error = errorMaybe { 58 | return Observable.error(error).asCompletable() 59 | } else { 60 | return Observable.empty().asCompletable() 61 | } 62 | 63 | } 64 | scheduler.scheduleAt(10) { action.inputs.onNext(TestError) } 65 | scheduler.scheduleAt(20) { action.inputs.onNext(nil) } 66 | } 67 | afterEach { 68 | action = nil 69 | } 70 | it("emits no errors on `completions`") { 71 | let completions = scheduler.createObserver(Void.self) 72 | action.completions.bind(to: completions).disposed(by: disposeBag) 73 | 74 | scheduler.start() 75 | 76 | expect(completions.events.contains { $0.time == 10 }).to(beFalse()) 77 | expect(completions.events.contains { $0.time == 20 }).to(beTrue()) 78 | } 79 | } 80 | 81 | describe("Input observer behavior") { 82 | var action: Action! 83 | var inputs: TestableObserver! 84 | var executions: TestableObserver>! 85 | beforeEach { 86 | inputs = scheduler.createObserver(String.self) 87 | action = Action { 88 | inputs.onNext($0) 89 | return Observable.just($0) 90 | } 91 | executions = scheduler.createObserver(Observable.self) 92 | action.executionObservables.bind(to: executions).disposed(by: disposeBag) 93 | } 94 | afterEach { 95 | action = nil 96 | inputs = nil 97 | executions = nil 98 | } 99 | 100 | it("execute on .next") { 101 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 102 | scheduler.start() 103 | expect(executions.events.filter { $0.value.isStopEvent }).to(beEmpty()) 104 | } 105 | it("ignore .error events") { 106 | scheduler.scheduleAt(10) { action.inputs.onError(TestError) } 107 | scheduler.start() 108 | expect(executions.events.filter { $0.value.isStopEvent }).to(beEmpty()) 109 | } 110 | it("ignore .completed events") { 111 | scheduler.scheduleAt(10) { action.inputs.onCompleted() } 112 | scheduler.start() 113 | expect(executions.events.filter { $0.value.isStopEvent }).to(beEmpty()) 114 | expect(inputs.events.isEmpty).to(beTrue()) 115 | } 116 | it("accept multiple .next events") { 117 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 118 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 119 | scheduler.start() 120 | expect(inputs.events).to(match(TestEvents.inputs)) 121 | let executionsCount = executions.events.filter { !$0.value.isStopEvent }.count 122 | expect(executionsCount).to(match(2)) 123 | } 124 | it("not terminate after .error event") { 125 | scheduler.scheduleAt(10) { action.inputs.onError(TestError) } 126 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 127 | scheduler.start() 128 | expect(inputs.events).to(match(Recorded.events([.next(20, "b")]))) 129 | let executionsCount = executions.events.filter { !$0.value.isStopEvent }.count 130 | expect(executionsCount).to(match(1)) 131 | } 132 | it("not terminate after .completed event") { 133 | scheduler.scheduleAt(10) { action.inputs.onCompleted() } 134 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 135 | scheduler.start() 136 | expect(inputs.events).to(match(Recorded.events([.next(20, "b")]))) 137 | let executionsCount = executions.events.filter { !$0.value.isStopEvent }.count 138 | expect(executionsCount).to(match(1)) 139 | } 140 | } 141 | 142 | describe("action properties") { 143 | var inputs: TestableObserver! 144 | var elements: TestableObserver! 145 | var errors: TestableObserver! 146 | var enabled: TestableObserver! 147 | var executing: TestableObserver! 148 | var executionObservables: TestableObserver>! 149 | var underlyingError: TestableObserver! 150 | 151 | beforeEach { 152 | inputs = scheduler.createObserver(String.self) 153 | elements = scheduler.createObserver(String.self) 154 | errors = scheduler.createObserver(ActionError.self) 155 | enabled = scheduler.createObserver(Bool.self) 156 | executing = scheduler.createObserver(Bool.self) 157 | executionObservables = scheduler.createObserver(Observable.self) 158 | underlyingError = scheduler.createObserver(Error.self) 159 | } 160 | 161 | func buildAction(enabledIf: Observable = Observable.just(true), 162 | factory: @escaping (String) -> Observable) -> Action { 163 | let action = Action(enabledIf: enabledIf) { 164 | inputs.onNext($0) 165 | return factory($0) 166 | } 167 | 168 | action.elements 169 | .bind(to: elements) 170 | .disposed(by: disposeBag) 171 | 172 | action.errors 173 | .bind(to: errors) 174 | .disposed(by: disposeBag) 175 | 176 | action.enabled 177 | .bind(to: enabled) 178 | .disposed(by: disposeBag) 179 | 180 | action.executing 181 | .bind(to: executing) 182 | .disposed(by: disposeBag) 183 | 184 | action.executionObservables 185 | .bind(to: executionObservables) 186 | .disposed(by: disposeBag) 187 | 188 | action.underlyingError 189 | .bind(to: underlyingError) 190 | .disposed(by: disposeBag) 191 | 192 | // Dummy subscription for multiple subcription tests 193 | action.elements.subscribe().disposed(by: disposeBag) 194 | action.errors.subscribe().disposed(by: disposeBag) 195 | action.enabled.subscribe().disposed(by: disposeBag) 196 | action.executing.subscribe().disposed(by: disposeBag) 197 | action.executionObservables.subscribe().disposed(by: disposeBag) 198 | action.underlyingError.subscribe().disposed(by: disposeBag) 199 | 200 | return action 201 | } 202 | 203 | describe("single element action") { 204 | sharedExamples("send elements to elements observable") { 205 | it("work factory receives inputs") { 206 | expect(inputs.events).to(match(TestEvents.inputs)) 207 | } 208 | it("elements observable receives generated elements") { 209 | expect(elements.events).to(match(TestEvents.elements)) 210 | } 211 | it("errors observable receives nothing") { 212 | expect(errors.events.isEmpty).to(beTrue()) 213 | } 214 | it("disabled until element returns") { 215 | expect(enabled.events).to(match(TestEvents.disabled)) 216 | } 217 | it("executing until element returns") { 218 | expect(executing.events).to(match(TestEvents.executing)) 219 | } 220 | it("executes twice") { 221 | expect(executionObservables.events.count) == 2 222 | } 223 | } 224 | 225 | var action: Action! 226 | 227 | beforeEach { 228 | action = buildAction { Observable.just($0) } 229 | } 230 | 231 | context("trigger via inputs subject") { 232 | beforeEach { 233 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 234 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 235 | scheduler.start() 236 | } 237 | 238 | itBehavesLike("send elements to elements observable") 239 | } 240 | 241 | context("trigger via execute() method") { 242 | beforeEach { 243 | scheduler.scheduleAt(10) { action.execute("a") } 244 | scheduler.scheduleAt(20) { action.execute("b") } 245 | scheduler.start() 246 | } 247 | 248 | itBehavesLike("send elements to elements observable") 249 | } 250 | } 251 | 252 | describe("multiple element action") { 253 | sharedExamples("send array elements to elements observable") { 254 | it("work factory receives inputs") { 255 | expect(inputs.events).to(match(TestEvents.inputs)) 256 | } 257 | it("elements observable receives generated elements") { 258 | expect(elements.events).to(match(TestEvents.multipleElements)) 259 | } 260 | it("errors observable receives nothing") { 261 | expect(errors.events.isEmpty).to(beTrue()) 262 | } 263 | it("disabled until element returns") { 264 | expect(enabled.events).to(match(TestEvents.disabled)) 265 | } 266 | it("executing until element returns") { 267 | expect(executing.events).to(match(TestEvents.executing)) 268 | } 269 | it("executes twice") { 270 | expect(executionObservables.events.count).to(match(2)) 271 | } 272 | } 273 | 274 | var action: Action! 275 | 276 | beforeEach { 277 | action = buildAction { input in 278 | // "a" -> ["a", "b", "c"] 279 | let baseValue = UnicodeScalar(input)!.value 280 | let strings = (baseValue..<(baseValue + 3)) 281 | .compactMap { UnicodeScalar($0) } 282 | .map { String($0) } 283 | 284 | return Observable.from(strings) 285 | } 286 | } 287 | 288 | context("trigger via inputs subject") { 289 | beforeEach { 290 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 291 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 292 | scheduler.start() 293 | } 294 | 295 | itBehavesLike("send array elements to elements observable") 296 | } 297 | 298 | context("trigger via execute() method") { 299 | beforeEach { 300 | scheduler.scheduleAt(10) { action.execute("a") } 301 | scheduler.scheduleAt(20) { action.execute("b") } 302 | scheduler.start() 303 | } 304 | 305 | itBehavesLike("send array elements to elements observable") 306 | } 307 | } 308 | 309 | describe("error action") { 310 | sharedExamples("send errors to errors observable") { 311 | it("work factory receives inputs") { 312 | expect(inputs.events).to(match(TestEvents.inputs)) 313 | } 314 | it("elements observable receives nothing") { 315 | expect(elements.events.isEmpty).to(beTrue()) 316 | } 317 | it("errors observable receives generated errors") { 318 | expect(errors.events).to(match(TestEvents.underlyingErrors)) 319 | } 320 | it("underlyingError observable receives 2 generated errors") { 321 | expect(underlyingError.events.count).to(match(2)) 322 | } 323 | it("underlyingError observable receives generated errors") { 324 | expect(underlyingError.events).to(match(with: TestEvents.elementUnderlyingErrors)) 325 | } 326 | it("disabled until error returns") { 327 | expect(enabled.events).to(match(TestEvents.disabled)) 328 | } 329 | it("executing until error returns") { 330 | expect(executing.events).to(match(TestEvents.executing)) 331 | } 332 | it("executes twice") { 333 | expect(executionObservables.events.count).to(match(2)) 334 | } 335 | } 336 | 337 | var action: Action! 338 | 339 | beforeEach { 340 | action = buildAction { _ in Observable.error(TestError) } 341 | } 342 | 343 | context("trigger via inputs subject") { 344 | beforeEach { 345 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 346 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 347 | scheduler.start() 348 | } 349 | 350 | itBehavesLike("send errors to errors observable") 351 | } 352 | 353 | context("trigger via execute() method") { 354 | beforeEach { 355 | scheduler.scheduleAt(10) { action.execute("a") } 356 | scheduler.scheduleAt(20) { action.execute("b") } 357 | scheduler.start() 358 | } 359 | 360 | itBehavesLike("send errors to errors observable") 361 | } 362 | } 363 | 364 | describe("disabled action") { 365 | sharedExamples("send notEnabled errors to errors observable") { 366 | it("work factory receives nothing") { 367 | expect(inputs.events.isEmpty).to(beTrue()) 368 | } 369 | it("elements observable receives nothing") { 370 | expect(elements.events.isEmpty).to(beTrue()) 371 | } 372 | it("errors observable receives generated errors") { 373 | expect(errors.events).to(match(TestEvents.notEnabledErrors)) 374 | } 375 | it("underlyingError observable receives zero generated errors") { 376 | expect(underlyingError.events.count).to(match(0)) 377 | } 378 | it("disabled") { 379 | expect(enabled.events).to(match(TestEvents.false)) 380 | } 381 | it("never be executing") { 382 | expect(executing.events).to(match(TestEvents.false)) 383 | } 384 | it("never executes") { 385 | expect(executionObservables.events).to(beEmpty()) 386 | } 387 | } 388 | 389 | var action: Action! 390 | 391 | beforeEach { 392 | action = buildAction(enabledIf: Observable.just(false)) { Observable.just($0) } 393 | } 394 | 395 | context("trigger via inputs subject") { 396 | beforeEach { 397 | scheduler.scheduleAt(10) { action.inputs.onNext("a") } 398 | scheduler.scheduleAt(20) { action.inputs.onNext("b") } 399 | scheduler.start() 400 | } 401 | 402 | itBehavesLike("send notEnabled errors to errors observable") 403 | } 404 | 405 | context("trigger via execute() method") { 406 | beforeEach { 407 | scheduler.scheduleAt(10) { action.execute("a") } 408 | scheduler.scheduleAt(20) { action.execute("b") } 409 | scheduler.start() 410 | } 411 | 412 | itBehavesLike("send notEnabled errors to errors observable") 413 | } 414 | } 415 | } 416 | 417 | describe("execute function return value") { 418 | var action: Action! 419 | var element: TestableObserver! 420 | var executionObservables: TestableObserver>! 421 | 422 | beforeEach { 423 | element = scheduler.createObserver(String.self) 424 | executionObservables = scheduler.createObserver(Observable.self) 425 | } 426 | 427 | func bindAndExecuteTwice(action: Action) { 428 | action.executionObservables 429 | .bind(to: executionObservables) 430 | .disposed(by: disposeBag) 431 | 432 | scheduler.scheduleAt(10) { 433 | action.execute("a") 434 | .bind(to: element) 435 | .disposed(by: disposeBag) 436 | } 437 | 438 | scheduler.scheduleAt(20) { 439 | action.execute("b") 440 | .bind(to: element) 441 | .disposed(by: disposeBag) 442 | } 443 | 444 | scheduler.start() 445 | } 446 | 447 | context("single element action") { 448 | beforeEach { 449 | action = Action { Observable.just($0) } 450 | bindAndExecuteTwice(action: action) 451 | } 452 | 453 | it("element receives single value for each execution") { 454 | expect(element.events).to(match(TestEvents.executionStreams)) 455 | } 456 | 457 | it("executes twice") { 458 | expect(executionObservables.events.count).to(match(2)) 459 | } 460 | } 461 | 462 | context("multiple element action") { 463 | beforeEach { 464 | action = Action { Observable.of($0, $0, $0) } 465 | bindAndExecuteTwice(action: action) 466 | } 467 | 468 | it("element receives 3 values for each execution") { 469 | expect(element.events).to(match(TestEvents.multipleExecutionStreams)) 470 | } 471 | it("executes twice") { 472 | expect(executionObservables.events.count).to(match(2)) 473 | } 474 | } 475 | 476 | context("error action") { 477 | beforeEach { 478 | action = Action { _ in Observable.error(TestError) } 479 | bindAndExecuteTwice(action: action) 480 | } 481 | 482 | it("element fails with underlyingError") { 483 | expect(element.events).to(match(with: TestEvents.elementUnderlyingErrors)) 484 | } 485 | it("executes twice") { 486 | expect(executionObservables.events.count) == 2 487 | } 488 | } 489 | 490 | context("disabled") { 491 | beforeEach { 492 | action = Action(enabledIf: Observable.just(false)) { Observable.just($0) } 493 | bindAndExecuteTwice(action: action) 494 | } 495 | 496 | it("element fails with notEnabled") { 497 | expect(element.events).to(match(with: TestEvents.elementNotEnabledErrors)) 498 | } 499 | it("never executes") { 500 | expect(executionObservables.events).to(beEmpty()) 501 | } 502 | } 503 | 504 | context("execute while executing") { 505 | var secondElement: TestableObserver! 506 | var trigger: PublishSubject! 507 | 508 | beforeEach { 509 | secondElement = scheduler.createObserver(String.self) 510 | trigger = PublishSubject() 511 | action = Action { Observable.just($0).sample(trigger) } 512 | 513 | action.executionObservables 514 | .bind(to: executionObservables) 515 | .disposed(by: disposeBag) 516 | 517 | scheduler.scheduleAt(10) { 518 | action.execute("a") 519 | .bind(to: element) 520 | .disposed(by: disposeBag) 521 | } 522 | 523 | scheduler.scheduleAt(20) { 524 | action.execute("b") 525 | .bind(to: secondElement) 526 | .disposed(by: disposeBag) 527 | } 528 | 529 | scheduler.scheduleAt(30) { 530 | #if swift(>=3.2) 531 | trigger.onNext(()) 532 | #else 533 | trigger.onNext() 534 | #endif 535 | } 536 | 537 | scheduler.start() 538 | } 539 | 540 | it("first element receives single value") { 541 | expect(element.events).to(match(Recorded.events([.next(30, "a"), .completed(30)]))) 542 | } 543 | it("second element fails with notEnabled error") { 544 | expect(secondElement.events).to(match(Recorded.events([.error(20, ActionError.notEnabled)]))) 545 | } 546 | it("executes once") { 547 | expect(executionObservables.events.count).to(match(1)) 548 | } 549 | } 550 | } 551 | } 552 | } 553 | 554 | extension ActionError: Equatable { 555 | // Not accurate but convenient for testing. 556 | public static func ==(lhs: ActionError, rhs: ActionError) -> Bool { 557 | switch (lhs, rhs) { 558 | case (.notEnabled, .notEnabled): 559 | return true 560 | case (.underlyingError, .underlyingError): 561 | return true 562 | default: 563 | return false 564 | } 565 | } 566 | } 567 | 568 | extension String: Error { } 569 | let TestError = "Test Error" 570 | -------------------------------------------------------------------------------- /Tests/ActionTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Nimble+RxTest+Ext.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | import RxSwift 5 | import RxTest 6 | 7 | /// `Foundation` 8 | 9 | extension Optional { 10 | func asString() -> String { 11 | guard let s = self else { return "nil" } 12 | return String(describing: s) 13 | } 14 | } 15 | 16 | /// `RxSwift` 17 | 18 | extension Event { 19 | var isError: Bool { 20 | switch self { 21 | case .error: return true 22 | default: return false 23 | } 24 | } 25 | } 26 | 27 | /// Nimble 28 | 29 | extension PredicateResult { 30 | static var evaluationFailed: PredicateResult { 31 | return PredicateResult(status: .doesNotMatch, 32 | message: .fail("failed to evaluate given expression")) 33 | } 34 | 35 | static func isEqual(actual: T?, expected: T?) -> PredicateResult { 36 | return PredicateResult(bool: actual == expected, 37 | message: .expectedCustomValueTo("get <\(expected.asString())>", actual: "<\(actual.asString())>")) 38 | } 39 | } 40 | 41 | public func match(_ expected: T) -> Nimble.Predicate where T: Equatable { 42 | return Predicate { events in 43 | 44 | guard let source = try events.evaluate() else { 45 | return PredicateResult.evaluationFailed 46 | } 47 | guard source == expected else { 48 | return PredicateResult(status: .doesNotMatch, 49 | message: .expectedCustomValueTo("get <\(expected)> events", actual: "<\(source)> events")) 50 | } 51 | 52 | 53 | return PredicateResult(bool: true, message: .fail("matched values and timeline as expected")) 54 | } 55 | } 56 | 57 | public func match(_ expected: [Recorded>]) -> Nimble.Predicate<[Recorded>]> where T: Equatable { 58 | return Predicate { events in 59 | 60 | guard let source = try events.evaluate() else { 61 | return PredicateResult.evaluationFailed 62 | } 63 | guard source.count == expected.count else { 64 | return PredicateResult(bool: false, 65 | message: .expectedCustomValueTo("get <\(expected.count)> events", actual: "<\(source.count)> events")) 66 | } 67 | 68 | for (lhs, rhs) in zip(source, expected) { 69 | guard lhs.time == rhs.time, 70 | lhs.value == rhs.value else { 71 | return PredicateResult(bool: rhs == lhs, 72 | message: .expectedCustomValueTo("match <\(rhs)>", actual: "<\(lhs)>")) 73 | } 74 | continue 75 | } 76 | 77 | return PredicateResult(bool: true, message: .fail("match timeline")) 78 | } 79 | } 80 | 81 | public func match(with expectedErrors: [Recorded>]) -> Nimble.Predicate<[Recorded>]> where E: Equatable { 82 | return Predicate { events in 83 | guard let source = try events.evaluate() else { 84 | return PredicateResult.evaluationFailed 85 | } 86 | let errorEvents = source.filter { $0.value.isError } 87 | for (lhs, rhs) in zip(errorEvents, expectedErrors) { 88 | guard lhs.time == rhs.time, 89 | lhs.value.error.asString() == rhs.value.error.asString() else { 90 | return PredicateResult(bool: false, 91 | message: .fail("did not error")) 92 | } 93 | continue 94 | } 95 | 96 | return PredicateResult(bool: true, message: .fail("matched values and timeline as expected")) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/TestEvents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import RxTest 4 | @testable import Action 5 | 6 | enum TestEvents { 7 | static let inputs = Recorded.events([ 8 | .next(10, "a"), 9 | .next(20, "b"), 10 | ]) 11 | 12 | static let elements = Recorded.events([ 13 | .next(10, "a"), 14 | .next(20, "b"), 15 | ]) 16 | 17 | static let multipleElements = Recorded.events([ 18 | .next(10, "a"), 19 | .next(10, "b"), 20 | .next(10, "c"), 21 | .next(20, "b"), 22 | .next(20, "c"), 23 | .next(20, "d"), 24 | ]) 25 | 26 | static let enabled = Recorded.events([ 27 | .next(0, true), 28 | .next(10, false), 29 | .next(10, true), 30 | .next(20, false), 31 | .next(20, true), 32 | ]) 33 | 34 | static let disabled = Recorded.events([ 35 | .next(0, true), 36 | .next(10, false), 37 | .next(10, true), 38 | .next(20, false), 39 | .next(20, true), 40 | ]) 41 | 42 | static let executing = Recorded.events([ 43 | .next(0, false), 44 | .next(10, true), 45 | .next(10, false), 46 | .next(20, true), 47 | .next(20, false), 48 | ]) 49 | 50 | static let `false` = Recorded.events([ 51 | .next(0, false), 52 | ]) 53 | 54 | static let underlyingErrors = Recorded.events([ 55 | .next(10, ActionError.underlyingError(TestError)), 56 | .next(20, ActionError.underlyingError(TestError)), 57 | ]) 58 | 59 | static let notEnabledErrors = Recorded.events([ 60 | .next(10, ActionError.notEnabled), 61 | .next(20, ActionError.notEnabled), 62 | ]) 63 | 64 | static let executionStreams = Recorded.events([ 65 | .next(10, "a"), 66 | .completed(10), 67 | .next(20, "b"), 68 | .completed(20), 69 | ]) 70 | 71 | static let elementUnderlyingErrors = Recorded.events([ 72 | .error(10, ActionError.underlyingError(TestError), String.self), 73 | .error(20, ActionError.underlyingError(TestError), String.self), 74 | ]) 75 | 76 | static let elementNotEnabledErrors = Recorded.events([ 77 | .error(10, ActionError.notEnabled, String.self), 78 | .error(20, ActionError.notEnabled, String.self), 79 | ]) 80 | 81 | static let multipleExecutionStreams = Recorded.events([ 82 | .next(10, "a"), 83 | .next(10, "a"), 84 | .next(10, "a"), 85 | .completed(10), 86 | .next(20, "b"), 87 | .next(20, "b"), 88 | .next(20, "b"), 89 | .completed(20), 90 | ]) 91 | } 92 | -------------------------------------------------------------------------------- /Tests/iOS-Tests/AlertActionTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import UIKit 5 | import Action 6 | 7 | class AlertActionTests: QuickSpec { 8 | override class func spec() { 9 | it("is nil by default") { 10 | let subject = UIAlertAction.Action("Hi", style: .default) 11 | expect(subject.rx.action).to(beNil()) 12 | } 13 | it("respects setter") { 14 | var subject = UIAlertAction.Action("Hi", style: .default) 15 | let action = emptyAction() 16 | subject.rx.action = action 17 | expect(subject.rx.action).to(beAKindOf(CocoaAction.self)) 18 | } 19 | it("disables the alert action while executing") { 20 | var subject = UIAlertAction.Action("Hi", style: .default) 21 | var observer: AnyObserver! 22 | let action = CocoaAction(workFactory: { _ in 23 | return Observable.create { (obsv) -> Disposable in 24 | observer = obsv 25 | return Disposables.create() 26 | } 27 | }) 28 | 29 | subject.rx.action = action 30 | action.execute() 31 | expect(subject.isEnabled).toEventually( beFalse() ) 32 | 33 | observer.onCompleted() 34 | expect(subject.isEnabled).toEventually( beTrue() ) 35 | } 36 | 37 | it("disables the alert action if the Action is disabled") { 38 | var subject = UIAlertAction.Action("Hi", style: .default) 39 | let disposeBag = DisposeBag() 40 | 41 | subject.rx.action = emptyAction(.just(false)) 42 | waitUntil { done in 43 | subject.rx.observe(Bool.self, "enabled") 44 | .take(1) 45 | .subscribe(onNext: { _ in 46 | done() 47 | }) 48 | .disposed(by: disposeBag) 49 | } 50 | 51 | expect(subject.isEnabled).to(beFalse()) 52 | } 53 | 54 | it("disposes of old action subscriptions when re-set") { 55 | var subject = UIAlertAction.Action("Hi", style: .default) 56 | 57 | var disposed = false 58 | autoreleasepool { 59 | let disposeBag = DisposeBag() 60 | 61 | let action = emptyAction() 62 | subject.rx.action = action 63 | 64 | action 65 | .elements 66 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 67 | disposed = true 68 | }) 69 | .disposed(by: disposeBag) 70 | } 71 | 72 | subject.rx.action = nil 73 | expect(disposed).to(beTrue()) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/iOS-Tests/BarButtonTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import UIKit 5 | import Action 6 | 7 | class BarButtonTests: QuickSpec { 8 | override class func spec() { 9 | 10 | it("is nil by default") { 11 | let subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 12 | expect(subject.rx.action).to( beNil() ) 13 | } 14 | 15 | it("respects setter") { 16 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 17 | 18 | let action = emptyAction() 19 | subject.rx.action = action 20 | expect(subject.rx.action).to(beAKindOf(CocoaAction.self)) 21 | } 22 | 23 | it("disables the button while executing") { 24 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 25 | 26 | var observer: AnyObserver! 27 | let action = CocoaAction(workFactory: { _ in 28 | return Observable.create { (obsv) -> Disposable in 29 | observer = obsv 30 | return Disposables.create() 31 | } 32 | }) 33 | 34 | subject.rx.action = action 35 | 36 | action.execute() 37 | expect(subject.isEnabled).toEventually( beFalse() ) 38 | 39 | observer.onCompleted() 40 | expect(subject.isEnabled).toEventually( beTrue() ) 41 | } 42 | 43 | it("disables the button if the Action is disabled") { 44 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 45 | 46 | subject.rx.action = emptyAction(.just(false)) 47 | expect(subject.target).toEventuallyNot(beNil()) 48 | expect(subject.isEnabled).to(beFalse()) 49 | } 50 | 51 | it("doesn't execute a disabled action when tapped") { 52 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 53 | 54 | var executed = false 55 | subject.rx.action = CocoaAction(enabledIf: .just(false), workFactory: { _ in 56 | executed = true 57 | return .empty() 58 | }) 59 | 60 | _ = subject.target?.perform(subject.action, with: subject) 61 | expect(executed) == false 62 | } 63 | 64 | it("executes the action when tapped") { 65 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 66 | 67 | var executed = false 68 | let action = CocoaAction(workFactory: { _ in 69 | executed = true 70 | return .empty() 71 | }) 72 | subject.rx.action = action 73 | // Setting the action has the asynchronous effect of adding a target. 74 | expect(subject.target).toEventuallyNot(beNil()) 75 | _ = subject.target?.perform(subject.action, with: subject) 76 | expect(executed).to(beTrue()) 77 | } 78 | 79 | it("disposes of old action subscriptions when re-set") { 80 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 81 | 82 | var disposed = false 83 | autoreleasepool { 84 | let disposeBag = DisposeBag() 85 | 86 | let action = emptyAction() 87 | subject.rx.action = action 88 | 89 | action 90 | .elements 91 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 92 | disposed = true 93 | }) 94 | .disposed(by: disposeBag) 95 | } 96 | 97 | subject.rx.action = nil 98 | expect(disposed).to(beTrue()) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/iOS-Tests/BindToTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import RxCocoa 5 | import RxTest 6 | import UIKit 7 | import Action 8 | 9 | extension UIButton { 10 | // Normally I'd use subject.sendActionsForControlEvents(.TouchUpInside) but it's not working 11 | func test_executeTap() { 12 | for case let target as NSObject in allTargets { 13 | for action in actions(forTarget: target, forControlEvent: .touchUpInside) ?? [] { 14 | target.perform(Selector(action), with: self) 15 | } 16 | } 17 | } 18 | } 19 | 20 | extension UIRefreshControl { 21 | // Normally I'd use subject.sendActionsForControlEvents(.valueChanged) but it's not working 22 | func test_executeRefresh() { 23 | for case let target as NSObject in allTargets { 24 | for action in actions(forTarget: target, forControlEvent: .valueChanged) ?? [] { 25 | target.perform(Selector(action), with: self) 26 | } 27 | } 28 | } 29 | } 30 | 31 | class BindToTests: QuickSpec { 32 | override class func spec() { 33 | it("actives a UIButton") { 34 | var called = false 35 | let button = UIButton() 36 | let action = Action(workFactory: { _ in 37 | called = true 38 | return .empty() 39 | }) 40 | button.rx.bind(to: action, input: "Hi there!") 41 | // Setting the action has an asynchronous effect of adding a target. 42 | expect(button.allTargets.count) == 1 43 | 44 | button.test_executeTap() 45 | 46 | expect(called).toEventually( beTrue() ) 47 | } 48 | 49 | it("does not retain UIButton") { 50 | var button: UIButton? = UIButton() 51 | let action = Action(workFactory: { _ in 52 | return .empty() 53 | }) 54 | button?.rx.bind(to: action, input: "Hi there!") 55 | 56 | weak var buttonWeakReference = button 57 | button = nil 58 | 59 | expect(buttonWeakReference).to(beNil()) 60 | } 61 | 62 | it("activates a generic control event") { 63 | var called = false 64 | let button = UIButton() 65 | let action = Action(workFactory: { _ in 66 | called = true 67 | return .empty() 68 | }) 69 | button.rx.bind(to: action, controlEvent: button.rx.tap, inputTransform: { input in "\(input)" }) 70 | // Setting the action has an asynchronous effect of adding a target. 71 | expect(button.allTargets.count) == 1 72 | 73 | button.test_executeTap() 74 | 75 | expect(called).toEventually( beTrue() ) 76 | } 77 | 78 | it("actives a UIBarButtonItem") { 79 | var called = false 80 | let item = UIBarButtonItem() 81 | let action = Action(workFactory: { _ in 82 | called = true 83 | return .empty() 84 | }) 85 | item.rx.bind(to: action, input: "Hi there!") 86 | 87 | _ = item.target!.perform(item.action!, with: item) 88 | 89 | expect(called).toEventually( beTrue() ) 90 | } 91 | 92 | it("does not retain UIBarButtonItem") { 93 | var barButtonItem: UIBarButtonItem? = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 94 | let action = Action(workFactory: { _ in 95 | return .empty() 96 | }) 97 | barButtonItem?.rx.bind(to: action, input: "Hi there!") 98 | 99 | weak var barButtonItemWeakReference = barButtonItem 100 | barButtonItem = nil 101 | 102 | expect(barButtonItemWeakReference).to(beNil()) 103 | } 104 | 105 | it("actives a UIRefreshControl") { 106 | var called = false 107 | let item = UIRefreshControl() 108 | let action = Action(workFactory: { _ in 109 | called = true 110 | return .empty() 111 | }) 112 | item.rx.bind(to: action, input: "Hi there!") 113 | 114 | item.test_executeRefresh() 115 | 116 | expect(called).toEventually( beTrue() ) 117 | } 118 | 119 | it("does not retain UIRefreshControl") { 120 | var refreshControl: UIRefreshControl? = UIRefreshControl() 121 | let action = Action(workFactory: { _ in 122 | return .empty() 123 | }) 124 | refreshControl?.rx.bind(to: action, input: "Hi there!") 125 | 126 | weak var refreshControlWeakReference = refreshControl 127 | refreshControl = nil 128 | 129 | expect(refreshControlWeakReference).to(beNil()) 130 | } 131 | 132 | describe("unbinding") { 133 | it("unbinds actions for UIButton") { 134 | let button = UIButton() 135 | let action = Action(workFactory: { _ in 136 | assertionFailure() 137 | return .empty() 138 | }) 139 | button.rx.bind(to: action, input: "Hi there!") 140 | // Setting the action has an asynchronous effect of adding a target. 141 | expect(button.allTargets.count) == 1 142 | 143 | button.rx.unbindAction() 144 | button.test_executeTap() 145 | 146 | expect(button.allTargets.count) == 0 147 | } 148 | 149 | it("unbinds actions for UIRefreshControl") { 150 | let refreshControl = UIRefreshControl() 151 | let action = Action(workFactory: { _ in 152 | assertionFailure() 153 | return .empty() 154 | }) 155 | refreshControl.rx.bind(to: action, input: "Hi there!") 156 | // Setting the action has an asynchronous effect of adding a target. 157 | expect(refreshControl.allTargets.count) == 1 158 | 159 | refreshControl.rx.unbindAction() 160 | refreshControl.test_executeRefresh() 161 | 162 | expect(refreshControl.allTargets.count) == 0 163 | } 164 | 165 | it("unbinds actions for UIBarButtonItem") { 166 | var called = false 167 | let item = UIBarButtonItem() 168 | let action = Action(workFactory: { _ in 169 | called = true 170 | return .empty() 171 | }) 172 | item.rx.bind(to: action, input: "Hi there!") 173 | 174 | item.rx.unbindAction() 175 | _ = item.target?.perform(item.action!, with: item) 176 | 177 | expect(called).to( beFalse() ) 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Tests/iOS-Tests/ButtonTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import UIKit 5 | import Action 6 | 7 | class ButtonTests: QuickSpec { 8 | override class func spec() { 9 | 10 | it("is nil by default") { 11 | let subject = UIButton(type: .system) 12 | expect(subject.rx.action).to( beNil() ) 13 | } 14 | 15 | it("respects setter") { 16 | var subject = UIButton(type: .system) 17 | 18 | let action = emptyAction() 19 | 20 | subject.rx.action = action 21 | 22 | expect(subject.rx.action) === action 23 | } 24 | 25 | it("disables the button while executing") { 26 | var subject = UIButton(type: .system) 27 | 28 | var observer: AnyObserver! 29 | let action = CocoaAction(workFactory: { _ in 30 | return Observable.create { (obsv) -> Disposable in 31 | observer = obsv 32 | return Disposables.create() 33 | } 34 | }) 35 | 36 | subject.rx.action = action 37 | 38 | action.execute() 39 | expect(subject.isEnabled).toEventually( beFalse() ) 40 | 41 | observer.onCompleted() 42 | expect(subject.isEnabled).toEventually( beTrue() ) 43 | } 44 | 45 | it("disables the button if the Action is disabled") { 46 | var subject = UIButton(type: .system) 47 | 48 | subject.rx.action = emptyAction(.just(false)) 49 | expect(subject.allTargets.count) == 1 50 | expect(subject.isEnabled) == false 51 | } 52 | 53 | it("doesn't execute a disabled action when tapped") { 54 | var subject = UIButton(type: .system) 55 | 56 | var executed = false 57 | subject.rx.action = CocoaAction(enabledIf: .just(false), workFactory: { _ in 58 | executed = true 59 | return .empty() 60 | }) 61 | 62 | subject.sendActions(for: .touchUpInside) 63 | 64 | expect(executed) == false 65 | } 66 | 67 | it("executes the action when tapped") { 68 | var subject = UIButton(type: .system) 69 | 70 | var executed = false 71 | let action = CocoaAction(workFactory: { _ in 72 | executed = true 73 | return .empty() 74 | }) 75 | subject.rx.action = action 76 | 77 | // Setting the action has an asynchronous effect of adding a target. 78 | expect(subject.allTargets.count) == 1 79 | 80 | subject.test_executeTap() 81 | 82 | expect(executed).toEventually( beTrue() ) 83 | } 84 | 85 | it("disposes of old action subscriptions when re-set") { 86 | var subject = UIButton(type: .system) 87 | 88 | var disposed = false 89 | autoreleasepool { 90 | let disposeBag = DisposeBag() 91 | 92 | let action = emptyAction() 93 | subject.rx.action = action 94 | 95 | action 96 | .elements 97 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 98 | disposed = true 99 | }) 100 | .disposed(by: disposeBag) 101 | } 102 | 103 | subject.rx.action = nil 104 | 105 | expect(disposed) == true 106 | } 107 | 108 | it("cancels the observable if the button is deallocated") { 109 | 110 | var disposed = false 111 | 112 | waitUntil { done in 113 | autoreleasepool { 114 | var subject = UIButton(type: .system) 115 | let action = CocoaAction { 116 | return Observable.create {_ in 117 | Disposables.create { 118 | disposed = true 119 | done() 120 | } 121 | } 122 | } 123 | 124 | subject.rx.action = action 125 | subject.rx.action?.execute() 126 | } 127 | } 128 | 129 | expect(disposed) == true 130 | } 131 | } 132 | } 133 | 134 | func emptyAction(_ enabledIf: Observable = .just(true)) -> CocoaAction { 135 | return CocoaAction(enabledIf: enabledIf, workFactory: { _ in 136 | return .empty() 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /Tests/iOS-Tests/RefreshControlTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import UIKit 5 | import Action 6 | 7 | class RefreshControlTests: QuickSpec { 8 | override class func spec() { 9 | 10 | it("is nil by default") { 11 | let subject = UIRefreshControl() 12 | expect(subject.rx.action).to( beNil() ) 13 | } 14 | 15 | it("respects setter") { 16 | var subject = UIRefreshControl() 17 | 18 | let action = emptyAction() 19 | 20 | subject.rx.action = action 21 | 22 | expect(subject.rx.action) === action 23 | } 24 | 25 | it("disables the refresh control while executing") { 26 | var subject = UIRefreshControl() 27 | 28 | var observer: AnyObserver! 29 | let action = CocoaAction(workFactory: { _ in 30 | return Observable.create { (obsv) -> Disposable in 31 | observer = obsv 32 | return Disposables.create() 33 | } 34 | }) 35 | 36 | subject.rx.action = action 37 | 38 | action.execute() 39 | expect(subject.isEnabled).toEventually( beFalse() ) 40 | 41 | observer.onCompleted() 42 | expect(subject.isEnabled).toEventually( beTrue() ) 43 | } 44 | 45 | it("disables the refresh control if the Action is disabled") { 46 | var subject = UIRefreshControl() 47 | 48 | subject.rx.action = emptyAction(.just(false)) 49 | expect(subject.allTargets.count) == 1 50 | 51 | expect(subject.isEnabled) == false 52 | } 53 | 54 | it("doesn't execute a disabled action when refreshed") { 55 | var subject = UIRefreshControl() 56 | 57 | var executed = false 58 | subject.rx.action = CocoaAction(enabledIf: .just(false), workFactory: { _ in 59 | executed = true 60 | return .empty() 61 | }) 62 | 63 | subject.sendActions(for: .valueChanged) 64 | 65 | expect(executed) == false 66 | } 67 | 68 | it("executes the action when refreshed") { 69 | var subject = UIRefreshControl() 70 | 71 | var executed = false 72 | let action = CocoaAction(workFactory: { _ in 73 | executed = true 74 | return .empty() 75 | }) 76 | subject.rx.action = action 77 | 78 | // Setting the action has an asynchronous effect of adding a target. 79 | expect(subject.allTargets.count) == 1 80 | 81 | subject.test_executeRefresh() 82 | 83 | expect(executed).toEventually( beTrue() ) 84 | } 85 | 86 | it("disposes of old action subscriptions when re-set") { 87 | var subject = UIRefreshControl() 88 | 89 | var disposed = false 90 | autoreleasepool { 91 | let disposeBag = DisposeBag() 92 | 93 | let action = emptyAction() 94 | subject.rx.action = action 95 | 96 | action 97 | .elements 98 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 99 | disposed = true 100 | }) 101 | .disposed(by: disposeBag) 102 | } 103 | 104 | subject.rx.action = nil 105 | 106 | expect(disposed) == true 107 | } 108 | 109 | it("disposes of old action subscriptions when re-set") { 110 | var subject = UIBarButtonItem(barButtonSystemItem: .save, target: nil, action: nil) 111 | 112 | var disposed = false 113 | autoreleasepool { 114 | let disposeBag = DisposeBag() 115 | 116 | let action = emptyAction() 117 | subject.rx.action = action 118 | 119 | action 120 | .elements 121 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 122 | disposed = true 123 | }) 124 | .disposed(by: disposeBag) 125 | } 126 | 127 | subject.rx.action = nil 128 | 129 | expect(disposed) == true 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Tests/macOS-Tests/BindToTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | import RxCocoa 5 | import RxBlocking 6 | import RxTest 7 | import AppKit 8 | import Action 9 | 10 | extension NSButton { 11 | func test_executeAction() { 12 | if let target = self.target as? NSObject, let action = self.action { 13 | target.perform(action, with: self) 14 | } 15 | } 16 | } 17 | 18 | class BindToTests: QuickSpec { 19 | override class func spec() { 20 | it("actives a NSButton") { 21 | var called = false 22 | let button = NSButton() 23 | let action = Action(workFactory: { _ in 24 | called = true 25 | return .empty() 26 | }) 27 | button.rx.bind(to: action, input: "Hi there!") 28 | // Setting the action has an asynchronous effect of adding a target. 29 | expect(button.target).toEventuallyNot( beNil() ) 30 | 31 | button.test_executeAction() 32 | 33 | expect(called).toEventually( beTrue() ) 34 | } 35 | 36 | it("activates a generic control event") { 37 | var called = false 38 | let button = NSButton() 39 | let action = Action(workFactory: { _ in 40 | called = true 41 | return .empty() 42 | }) 43 | button.rx.bind(to: action, controlEvent: button.rx.tap, inputTransform: { input in "\(input)" }) 44 | // Setting the action has an asynchronous effect of adding a target. 45 | expect(button.target).toEventuallyNot( beNil() ) 46 | 47 | button.test_executeAction() 48 | 49 | expect(called).toEventually( beTrue() ) 50 | } 51 | 52 | describe("unbinding") { 53 | it("unbinds actions for UIButton") { 54 | let button = NSButton() 55 | let action = Action(workFactory: { _ in 56 | assertionFailure() 57 | return .empty() 58 | }) 59 | button.rx.bind(to: action, input: "Hi there!") 60 | // Setting the action has an asynchronous effect of adding a target. 61 | expect(button.target).toEventuallyNot( beNil() ) 62 | 63 | button.rx.unbindAction() 64 | button.test_executeAction() 65 | 66 | expect(button.target).toEventually( beNil() ) 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/macOS-Tests/NSButtonTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Quick 3 | import Nimble 4 | import RxSwift 5 | import RxBlocking 6 | import Action 7 | 8 | class NSButtonTests: QuickSpec { 9 | override class func spec() { 10 | 11 | it("is nil by default") { 12 | let subject = NSButton() 13 | expect(subject.rx.action).to( beNil() ) 14 | } 15 | 16 | it("respects setter") { 17 | var subject = NSButton() 18 | 19 | let action = emptyAction() 20 | 21 | subject.rx.action = action 22 | 23 | expect(subject.rx.action) === action 24 | } 25 | 26 | it("disables the button while executing") { 27 | var subject = NSButton() 28 | 29 | var observer: AnyObserver! 30 | let action = CocoaAction(workFactory: { _ in 31 | return Observable.create { (obsv) -> Disposable in 32 | observer = obsv 33 | return Disposables.create() 34 | } 35 | }) 36 | 37 | subject.rx.action = action 38 | 39 | action.execute(()) 40 | expect(subject.isEnabled).toEventually( beFalse() ) 41 | 42 | observer.onCompleted() 43 | expect(subject.isEnabled).toEventually( beTrue() ) 44 | } 45 | 46 | it("disables the button if the Action is disabled") { 47 | var subject = NSButton() 48 | subject.rx.action = emptyAction(.just(false)) 49 | expect(subject.target).toEventuallyNot( beNil() ) 50 | 51 | expect(subject.isEnabled) == false 52 | } 53 | 54 | it("doesn't execute a disabled action when tapped") { 55 | var subject = NSButton() 56 | 57 | var executed = false 58 | subject.rx.action = CocoaAction(enabledIf: .just(false), workFactory: { _ in 59 | executed = true 60 | return .empty() 61 | }) 62 | 63 | subject.sendAction(on: .leftMouseDown) 64 | 65 | expect(executed) == false 66 | } 67 | 68 | it("executes the action when tapped") { 69 | var subject = NSButton() 70 | 71 | var executed = false 72 | let action = CocoaAction(workFactory: { _ in 73 | executed = true 74 | return .empty() 75 | }) 76 | subject.rx.action = action 77 | 78 | // Setting the action has an asynchronous effect of adding a target. 79 | expect(subject.target).toEventuallyNot( beNil() ) 80 | 81 | subject.test_executeAction() 82 | 83 | expect(executed).toEventually( beTrue() ) 84 | } 85 | 86 | it("disposes of old action subscriptions when re-set") { 87 | var subject = NSButton() 88 | 89 | var disposed = false 90 | autoreleasepool { 91 | let disposeBag = DisposeBag() 92 | 93 | let action = emptyAction() 94 | subject.rx.action = action 95 | 96 | action 97 | .elements 98 | .subscribe(onNext: nil, onError: nil, onCompleted: nil, onDisposed: { 99 | disposed = true 100 | }) 101 | .disposed(by: disposeBag) 102 | } 103 | 104 | subject.rx.action = nil 105 | 106 | expect(disposed) == true 107 | } 108 | 109 | it("cancels the observable if the button is deallocated") { 110 | 111 | var disposed = false 112 | 113 | waitUntil { done in 114 | autoreleasepool { 115 | var subject = NSButton() 116 | let action = CocoaAction { 117 | return Observable.create {_ in 118 | Disposables.create { 119 | disposed = true 120 | done() 121 | } 122 | } 123 | } 124 | 125 | subject.rx.action = action 126 | #if swift(>=3.2) 127 | subject.rx.action?.execute(()) 128 | #else 129 | subject.rx.action?.execute() 130 | #endif 131 | } 132 | } 133 | 134 | expect(disposed) == true 135 | } 136 | } 137 | } 138 | 139 | func emptyAction(_ enabledIf: Observable = .just(true)) -> CocoaAction { 140 | return CocoaAction(enabledIf: enabledIf, workFactory: { _ in 141 | return .empty() 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /bin/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | carthage bootstrap 4 | cp Cartfile.resolved Carthage 5 | -------------------------------------------------------------------------------- /bin/bootstrap-if-needed: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Borrowed from https://robots.thoughtbot.com/caching-carthage-con-circleci 4 | 5 | if ! cmp -s Cartfile.resolved Carthage/Cartfile.resolved; then 6 | bin/bootstrap 7 | fi 8 | 9 | --------------------------------------------------------------------------------