├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── tests.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CODE_OF_CONDUCT.md ├── CombineCocoa.podspec ├── CombineCocoa.xcodeproj ├── CombineCocoa_Info.plist ├── Runtime_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── CombineCocoa-Package.xcscheme ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Example.xcscheme ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── ItunesArtwork@2x.png │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── ControlsViewController.swift │ ├── Info.plist │ ├── Main.storyboard │ ├── SceneDelegate.swift │ └── he.lproj │ │ └── LaunchScreen.strings ├── ExampleTests │ ├── Info.plist │ ├── UICollectionViewTests.swift │ ├── UIPageControlTests.swift │ ├── UIScrollViewTests.swift │ ├── UISearchBarTests.swift │ ├── UITableViewTests.swift │ └── UITextViewTests.swift ├── Podfile └── Podfile.lock ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Resources ├── example.gif └── logo.png ├── Sources ├── CombineCocoa │ ├── AnimatedAssignSubscriber.swift │ ├── CombineCocoa.h │ ├── CombineControlEvent.swift │ ├── CombineControlProperty.swift │ ├── CombineControlTarget.swift │ ├── Controls │ │ ├── NSTextStorage+Combine.swift │ │ ├── UIBarButtonItem+Combine.swift │ │ ├── UIButton+Combine.swift │ │ ├── UICollectionView+Combine.swift │ │ ├── UIControl+Combine.swift │ │ ├── UIDatePicker+Combine.swift │ │ ├── UIGestureRecognizer+Combine.swift │ │ ├── UIPageControl+Combine.swift │ │ ├── UIRefreshControl+Combine.swift │ │ ├── UIScrollView+Combine.swift │ │ ├── UISearchBar+Combine.swift │ │ ├── UISegmentedControl+Combine.swift │ │ ├── UISlider+Combine.swift │ │ ├── UIStepper+Combine.swift │ │ ├── UISwitch+Combine.swift │ │ ├── UITableView+Combine.swift │ │ ├── UITextField+Combine.swift │ │ └── UITextView+Combine.swift │ └── DelegateProxy │ │ ├── DelegateProxy.swift │ │ ├── DelegateProxyPublisher.swift │ │ └── DelegateProxyType.swift ├── Info.plist └── Runtime │ ├── ObjcDelegateProxy.m │ └── include │ ├── ObjcDelegateProxy.h │ └── module.modulemap ├── codecov.yml └── scripts ├── carthage-archive.sh └── make_project.rb /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device:** 27 | - Device: [e.g. iPhone 11] 28 | - OS: [e.g. iOS 13.4] 29 | - CombineCocoa version 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: CombineCocoa 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | xcode-tests: 7 | name: "Xcode & SPM Test" 8 | runs-on: macOS-latest 9 | 10 | strategy: 11 | matrix: 12 | platform: [iOS] 13 | include: 14 | - platform: iOS 15 | sdk: iphonesimulator 16 | destination: "name=iPhone 11" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Make project 20 | run: make project 21 | - name: Install Pods 22 | run: pod install --project-directory=Example 23 | - name: Run tests 24 | run: set -o pipefail && xcodebuild -workspace Example/Example.xcworkspace -scheme Example -enableCodeCoverage YES -sdk ${{ matrix.sdk }} -destination "${{ matrix.destination }}" test | xcpretty -c -r html --output logs/${{ matrix.platform }}.html 25 | - uses: codecov/codecov-action@v1.0.13 26 | with: 27 | token: 4842bea2-22dc-479b-b0a4-ff0b22aa9818 28 | name: CombineCocoa 29 | - uses: actions/upload-artifact@v1 30 | with: 31 | name: build-logs-${{ github.run_id }} 32 | path: logs 33 | carthage: 34 | name: "Carthage Test" 35 | runs-on: macOS-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | - name: Make project 39 | run: make project 40 | - name: Carthage build 41 | run: carthage build --no-skip-current --platform iOS --configuration Release --project-directory ./ 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | CombineCocoa.framework.zip 5 | Example/Example.xcworkspace 6 | 7 | ## Various settings 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata/ 17 | 18 | ## Other 19 | *.moved-aside 20 | *.xccheckout 21 | *.xcscmblueprint 22 | 23 | ## Obj-C/Swift specific 24 | *.hmap 25 | *.ipa 26 | *.dSYM.zip 27 | *.dSYM 28 | 29 | # Swift Package Manager 30 | .build/ 31 | 32 | # CocoaPods 33 | We recommend against adding the Pods directory to your .gitignore. However 34 | you should judge for yourself, the pros and cons are mentioned at: 35 | https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | Example/Pods/ 37 | 38 | # Carthage 39 | Carthage 40 | 41 | ### Visual Studio Code 42 | .vscode 43 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | opt_in_rules: 4 | - overridden_super_call 5 | - prohibited_super_call 6 | - extension_access_modifier 7 | - first_where 8 | - closure_spacing 9 | - unneeded_parentheses_in_closure_argument 10 | - vertical_parameter_alignment_on_call 11 | - redundant_nil_coalescing 12 | - pattern_matching_keywords 13 | - explicit_init 14 | - fatal_error_message 15 | - contains_over_first_not_nil 16 | - vertical_whitespace_closing_braces 17 | - vertical_whitespace_opening_braces 18 | - file_header 19 | disabled_rules: 20 | - nesting 21 | - line_length 22 | - cyclomatic_complexity 23 | - function_parameter_count 24 | - large_tuple 25 | 26 | file_header: 27 | required_pattern: | 28 | \/\/ 29 | \/\/ SWIFTLINT_CURRENT_FILENAME 30 | \/\/ CombineCocoa 31 | \/\/ 32 | \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2,4}\. 33 | \/\/ Copyright © \d{4} Combine Community\. All rights reserved\. 34 | \/\/ 35 | 36 | identifier_name: 37 | max_length: 60 -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at shai@combine.community. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CombineCocoa.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CombineCocoa" 3 | s.version = "0.4.1" 4 | s.summary = "CombineCocoa provided basic publisher bridges for UIControls in UIKit" 5 | s.description = <<-DESC 6 | Combine publisher bridges for Cocoa Controls (UIControl) in UIKit 7 | DESC 8 | s.homepage = "https://github.com/freak4pc/CombineCocoa" 9 | s.license = 'MIT' 10 | s.author = { "Shai Mishali" => "freak4pc@gmail.com" } 11 | s.source = { :git => "https://github.com/freak4pc/CombineCocoa.git", :tag => s.version.to_s } 12 | 13 | s.requires_arc = true 14 | 15 | s.ios.deployment_target = '10.0' 16 | 17 | s.source_files = 'Sources/**/*.{swift,h,m}' 18 | s.frameworks = ['Combine', 'Foundation'] 19 | s.swift_version = '5.0' 20 | end -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/CombineCocoa_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 | 0.2.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 0.2.1 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/Runtime_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | OBJ_101 /* ObjcDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* ObjcDelegateProxy.m */; }; 11 | OBJ_62 /* AnimatedAssignSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* AnimatedAssignSubscriber.swift */; }; 12 | OBJ_63 /* CombineControlEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* CombineControlEvent.swift */; }; 13 | OBJ_64 /* CombineControlProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* CombineControlProperty.swift */; }; 14 | OBJ_65 /* CombineControlTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* CombineControlTarget.swift */; }; 15 | OBJ_66 /* NSTextStorage+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* NSTextStorage+Combine.swift */; }; 16 | OBJ_67 /* UIBarButtonItem+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* UIBarButtonItem+Combine.swift */; }; 17 | OBJ_68 /* UIButton+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* UIButton+Combine.swift */; }; 18 | OBJ_69 /* UICollectionView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* UICollectionView+Combine.swift */; }; 19 | OBJ_70 /* UIControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* UIControl+Combine.swift */; }; 20 | OBJ_71 /* UIDatePicker+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* UIDatePicker+Combine.swift */; }; 21 | OBJ_72 /* UIGestureRecognizer+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* UIGestureRecognizer+Combine.swift */; }; 22 | OBJ_73 /* UIPageControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* UIPageControl+Combine.swift */; }; 23 | OBJ_74 /* UIRefreshControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* UIRefreshControl+Combine.swift */; }; 24 | OBJ_75 /* UIScrollView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* UIScrollView+Combine.swift */; }; 25 | OBJ_76 /* UISearchBar+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* UISearchBar+Combine.swift */; }; 26 | OBJ_77 /* UISegmentedControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* UISegmentedControl+Combine.swift */; }; 27 | OBJ_78 /* UISlider+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* UISlider+Combine.swift */; }; 28 | OBJ_79 /* UIStepper+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* UIStepper+Combine.swift */; }; 29 | OBJ_80 /* UISwitch+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* UISwitch+Combine.swift */; }; 30 | OBJ_81 /* UITableView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* UITableView+Combine.swift */; }; 31 | OBJ_82 /* UITextField+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* UITextField+Combine.swift */; }; 32 | OBJ_83 /* UITextView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* UITextView+Combine.swift */; }; 33 | OBJ_84 /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* DelegateProxy.swift */; }; 34 | OBJ_85 /* DelegateProxyPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* DelegateProxyPublisher.swift */; }; 35 | OBJ_86 /* DelegateProxyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* DelegateProxyType.swift */; }; 36 | OBJ_88 /* Runtime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "CombineCocoa::Runtime::Product" /* Runtime.framework */; }; 37 | OBJ_96 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXContainerItemProxy section */ 41 | 61B996BE25EA62D200D6F6ED /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = OBJ_1 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = "CombineCocoa::Runtime"; 46 | remoteInfo = Runtime; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | "CombineCocoa::CombineCocoa::Product" /* CombineCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CombineCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | "CombineCocoa::Runtime::Product" /* Runtime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Runtime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | OBJ_10 /* AnimatedAssignSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedAssignSubscriber.swift; sourceTree = ""; }; 54 | OBJ_11 /* CombineControlEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineControlEvent.swift; sourceTree = ""; }; 55 | OBJ_12 /* CombineControlProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineControlProperty.swift; sourceTree = ""; }; 56 | OBJ_13 /* CombineControlTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineControlTarget.swift; sourceTree = ""; }; 57 | OBJ_15 /* NSTextStorage+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextStorage+Combine.swift"; sourceTree = ""; }; 58 | OBJ_16 /* UIBarButtonItem+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Combine.swift"; sourceTree = ""; }; 59 | OBJ_17 /* UIButton+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Combine.swift"; sourceTree = ""; }; 60 | OBJ_18 /* UICollectionView+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Combine.swift"; sourceTree = ""; }; 61 | OBJ_19 /* UIControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Combine.swift"; sourceTree = ""; }; 62 | OBJ_20 /* UIDatePicker+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Combine.swift"; sourceTree = ""; }; 63 | OBJ_21 /* UIGestureRecognizer+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Combine.swift"; sourceTree = ""; }; 64 | OBJ_22 /* UIPageControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageControl+Combine.swift"; sourceTree = ""; }; 65 | OBJ_23 /* UIRefreshControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIRefreshControl+Combine.swift"; sourceTree = ""; }; 66 | OBJ_24 /* UIScrollView+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Combine.swift"; sourceTree = ""; }; 67 | OBJ_25 /* UISearchBar+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Combine.swift"; sourceTree = ""; }; 68 | OBJ_26 /* UISegmentedControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISegmentedControl+Combine.swift"; sourceTree = ""; }; 69 | OBJ_27 /* UISlider+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISlider+Combine.swift"; sourceTree = ""; }; 70 | OBJ_28 /* UIStepper+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStepper+Combine.swift"; sourceTree = ""; }; 71 | OBJ_29 /* UISwitch+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISwitch+Combine.swift"; sourceTree = ""; }; 72 | OBJ_30 /* UITableView+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Combine.swift"; sourceTree = ""; }; 73 | OBJ_31 /* UITextField+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Combine.swift"; sourceTree = ""; }; 74 | OBJ_32 /* UITextView+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Combine.swift"; sourceTree = ""; }; 75 | OBJ_34 /* DelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateProxy.swift; sourceTree = ""; }; 76 | OBJ_35 /* DelegateProxyPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateProxyPublisher.swift; sourceTree = ""; }; 77 | OBJ_36 /* DelegateProxyType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateProxyType.swift; sourceTree = ""; }; 78 | OBJ_38 /* ObjcDelegateProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ObjcDelegateProxy.m; sourceTree = ""; }; 79 | OBJ_40 /* ObjcDelegateProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjcDelegateProxy.h; sourceTree = ""; }; 80 | OBJ_41 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = /Users/hsncr/Desktop/workspace/CombineCocoa/Sources/Runtime/include/module.modulemap; sourceTree = ""; }; 81 | OBJ_46 /* Example */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Example; sourceTree = SOURCE_ROOT; }; 82 | OBJ_47 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = SOURCE_ROOT; }; 83 | OBJ_48 /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = SOURCE_ROOT; }; 84 | OBJ_49 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 85 | OBJ_50 /* codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = codecov.yml; sourceTree = ""; }; 86 | OBJ_51 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 87 | OBJ_52 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 88 | OBJ_53 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 89 | OBJ_54 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; }; 90 | OBJ_55 /* Gemfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile.lock; sourceTree = ""; }; 91 | OBJ_56 /* CombineCocoa.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CombineCocoa.podspec; sourceTree = ""; }; 92 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 93 | OBJ_9 /* CombineCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CombineCocoa.h; sourceTree = ""; }; 94 | /* End PBXFileReference section */ 95 | 96 | /* Begin PBXFrameworksBuildPhase section */ 97 | OBJ_102 /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 0; 100 | files = ( 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | OBJ_87 /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 0; 107 | files = ( 108 | OBJ_88 /* Runtime.framework in Frameworks */, 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | /* End PBXFrameworksBuildPhase section */ 113 | 114 | /* Begin PBXGroup section */ 115 | OBJ_14 /* Controls */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | OBJ_15 /* NSTextStorage+Combine.swift */, 119 | OBJ_16 /* UIBarButtonItem+Combine.swift */, 120 | OBJ_17 /* UIButton+Combine.swift */, 121 | OBJ_18 /* UICollectionView+Combine.swift */, 122 | OBJ_19 /* UIControl+Combine.swift */, 123 | OBJ_20 /* UIDatePicker+Combine.swift */, 124 | OBJ_21 /* UIGestureRecognizer+Combine.swift */, 125 | OBJ_22 /* UIPageControl+Combine.swift */, 126 | OBJ_23 /* UIRefreshControl+Combine.swift */, 127 | OBJ_24 /* UIScrollView+Combine.swift */, 128 | OBJ_25 /* UISearchBar+Combine.swift */, 129 | OBJ_26 /* UISegmentedControl+Combine.swift */, 130 | OBJ_27 /* UISlider+Combine.swift */, 131 | OBJ_28 /* UIStepper+Combine.swift */, 132 | OBJ_29 /* UISwitch+Combine.swift */, 133 | OBJ_30 /* UITableView+Combine.swift */, 134 | OBJ_31 /* UITextField+Combine.swift */, 135 | OBJ_32 /* UITextView+Combine.swift */, 136 | ); 137 | path = Controls; 138 | sourceTree = ""; 139 | }; 140 | OBJ_33 /* DelegateProxy */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | OBJ_34 /* DelegateProxy.swift */, 144 | OBJ_35 /* DelegateProxyPublisher.swift */, 145 | OBJ_36 /* DelegateProxyType.swift */, 146 | ); 147 | path = DelegateProxy; 148 | sourceTree = ""; 149 | }; 150 | OBJ_37 /* Runtime */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | OBJ_38 /* ObjcDelegateProxy.m */, 154 | OBJ_39 /* include */, 155 | ); 156 | name = Runtime; 157 | path = Sources/Runtime; 158 | sourceTree = SOURCE_ROOT; 159 | }; 160 | OBJ_39 /* include */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | OBJ_40 /* ObjcDelegateProxy.h */, 164 | OBJ_41 /* module.modulemap */, 165 | ); 166 | path = include; 167 | sourceTree = ""; 168 | }; 169 | OBJ_42 /* Tests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | ); 173 | name = Tests; 174 | sourceTree = SOURCE_ROOT; 175 | }; 176 | OBJ_43 /* Products */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | "CombineCocoa::Runtime::Product" /* Runtime.framework */, 180 | "CombineCocoa::CombineCocoa::Product" /* CombineCocoa.framework */, 181 | ); 182 | name = Products; 183 | sourceTree = BUILT_PRODUCTS_DIR; 184 | }; 185 | OBJ_5 /* */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | OBJ_6 /* Package.swift */, 189 | OBJ_7 /* Sources */, 190 | OBJ_42 /* Tests */, 191 | OBJ_43 /* Products */, 192 | OBJ_46 /* Example */, 193 | OBJ_47 /* Resources */, 194 | OBJ_48 /* scripts */, 195 | OBJ_49 /* CODE_OF_CONDUCT.md */, 196 | OBJ_50 /* codecov.yml */, 197 | OBJ_51 /* LICENSE */, 198 | OBJ_52 /* Makefile */, 199 | OBJ_53 /* README.md */, 200 | OBJ_54 /* Gemfile */, 201 | OBJ_55 /* Gemfile.lock */, 202 | OBJ_56 /* CombineCocoa.podspec */, 203 | ); 204 | name = ""; 205 | sourceTree = ""; 206 | }; 207 | OBJ_7 /* Sources */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | OBJ_8 /* CombineCocoa */, 211 | OBJ_37 /* Runtime */, 212 | ); 213 | name = Sources; 214 | sourceTree = SOURCE_ROOT; 215 | }; 216 | OBJ_8 /* CombineCocoa */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | OBJ_9 /* CombineCocoa.h */, 220 | OBJ_10 /* AnimatedAssignSubscriber.swift */, 221 | OBJ_11 /* CombineControlEvent.swift */, 222 | OBJ_12 /* CombineControlProperty.swift */, 223 | OBJ_13 /* CombineControlTarget.swift */, 224 | OBJ_14 /* Controls */, 225 | OBJ_33 /* DelegateProxy */, 226 | ); 227 | name = CombineCocoa; 228 | path = Sources/CombineCocoa; 229 | sourceTree = SOURCE_ROOT; 230 | }; 231 | /* End PBXGroup section */ 232 | 233 | /* Begin PBXNativeTarget section */ 234 | "CombineCocoa::CombineCocoa" /* CombineCocoa */ = { 235 | isa = PBXNativeTarget; 236 | buildConfigurationList = OBJ_58 /* Build configuration list for PBXNativeTarget "CombineCocoa" */; 237 | buildPhases = ( 238 | OBJ_61 /* Sources */, 239 | OBJ_87 /* Frameworks */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | OBJ_89 /* PBXTargetDependency */, 245 | ); 246 | name = CombineCocoa; 247 | productName = CombineCocoa; 248 | productReference = "CombineCocoa::CombineCocoa::Product" /* CombineCocoa.framework */; 249 | productType = "com.apple.product-type.framework"; 250 | }; 251 | "CombineCocoa::Runtime" /* Runtime */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = OBJ_97 /* Build configuration list for PBXNativeTarget "Runtime" */; 254 | buildPhases = ( 255 | OBJ_100 /* Sources */, 256 | OBJ_102 /* Frameworks */, 257 | ); 258 | buildRules = ( 259 | ); 260 | dependencies = ( 261 | ); 262 | name = Runtime; 263 | productName = Runtime; 264 | productReference = "CombineCocoa::Runtime::Product" /* Runtime.framework */; 265 | productType = "com.apple.product-type.framework"; 266 | }; 267 | "CombineCocoa::SwiftPMPackageDescription" /* CombineCocoaPackageDescription */ = { 268 | isa = PBXNativeTarget; 269 | buildConfigurationList = OBJ_92 /* Build configuration list for PBXNativeTarget "CombineCocoaPackageDescription" */; 270 | buildPhases = ( 271 | OBJ_95 /* Sources */, 272 | ); 273 | buildRules = ( 274 | ); 275 | dependencies = ( 276 | ); 277 | name = CombineCocoaPackageDescription; 278 | productName = CombineCocoaPackageDescription; 279 | productType = "com.apple.product-type.framework"; 280 | }; 281 | /* End PBXNativeTarget section */ 282 | 283 | /* Begin PBXProject section */ 284 | OBJ_1 /* Project object */ = { 285 | isa = PBXProject; 286 | attributes = { 287 | LastSwiftMigration = 9999; 288 | LastUpgradeCheck = 9999; 289 | }; 290 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "CombineCocoa" */; 291 | compatibilityVersion = "Xcode 3.2"; 292 | developmentRegion = en; 293 | hasScannedForEncodings = 0; 294 | knownRegions = ( 295 | en, 296 | ); 297 | mainGroup = OBJ_5 /* */; 298 | productRefGroup = OBJ_43 /* Products */; 299 | projectDirPath = ""; 300 | projectRoot = ""; 301 | targets = ( 302 | "CombineCocoa::CombineCocoa" /* CombineCocoa */, 303 | "CombineCocoa::SwiftPMPackageDescription" /* CombineCocoaPackageDescription */, 304 | "CombineCocoa::Runtime" /* Runtime */, 305 | ); 306 | }; 307 | /* End PBXProject section */ 308 | 309 | /* Begin PBXSourcesBuildPhase section */ 310 | OBJ_100 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 0; 313 | files = ( 314 | OBJ_101 /* ObjcDelegateProxy.m in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | OBJ_61 /* Sources */ = { 319 | isa = PBXSourcesBuildPhase; 320 | buildActionMask = 0; 321 | files = ( 322 | OBJ_62 /* AnimatedAssignSubscriber.swift in Sources */, 323 | OBJ_63 /* CombineControlEvent.swift in Sources */, 324 | OBJ_64 /* CombineControlProperty.swift in Sources */, 325 | OBJ_65 /* CombineControlTarget.swift in Sources */, 326 | OBJ_66 /* NSTextStorage+Combine.swift in Sources */, 327 | OBJ_67 /* UIBarButtonItem+Combine.swift in Sources */, 328 | OBJ_68 /* UIButton+Combine.swift in Sources */, 329 | OBJ_69 /* UICollectionView+Combine.swift in Sources */, 330 | OBJ_70 /* UIControl+Combine.swift in Sources */, 331 | OBJ_71 /* UIDatePicker+Combine.swift in Sources */, 332 | OBJ_72 /* UIGestureRecognizer+Combine.swift in Sources */, 333 | OBJ_73 /* UIPageControl+Combine.swift in Sources */, 334 | OBJ_74 /* UIRefreshControl+Combine.swift in Sources */, 335 | OBJ_75 /* UIScrollView+Combine.swift in Sources */, 336 | OBJ_76 /* UISearchBar+Combine.swift in Sources */, 337 | OBJ_77 /* UISegmentedControl+Combine.swift in Sources */, 338 | OBJ_78 /* UISlider+Combine.swift in Sources */, 339 | OBJ_79 /* UIStepper+Combine.swift in Sources */, 340 | OBJ_80 /* UISwitch+Combine.swift in Sources */, 341 | OBJ_81 /* UITableView+Combine.swift in Sources */, 342 | OBJ_82 /* UITextField+Combine.swift in Sources */, 343 | OBJ_83 /* UITextView+Combine.swift in Sources */, 344 | OBJ_84 /* DelegateProxy.swift in Sources */, 345 | OBJ_85 /* DelegateProxyPublisher.swift in Sources */, 346 | OBJ_86 /* DelegateProxyType.swift in Sources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | OBJ_95 /* Sources */ = { 351 | isa = PBXSourcesBuildPhase; 352 | buildActionMask = 0; 353 | files = ( 354 | OBJ_96 /* Package.swift in Sources */, 355 | ); 356 | runOnlyForDeploymentPostprocessing = 0; 357 | }; 358 | /* End PBXSourcesBuildPhase section */ 359 | 360 | /* Begin PBXTargetDependency section */ 361 | OBJ_89 /* PBXTargetDependency */ = { 362 | isa = PBXTargetDependency; 363 | target = "CombineCocoa::Runtime" /* Runtime */; 364 | targetProxy = 61B996BE25EA62D200D6F6ED /* PBXContainerItemProxy */; 365 | }; 366 | /* End PBXTargetDependency section */ 367 | 368 | /* Begin XCBuildConfiguration section */ 369 | OBJ_3 /* Debug */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | COMBINE_HIDPI_IMAGES = YES; 374 | COPY_PHASE_STRIP = NO; 375 | DEBUG_INFORMATION_FORMAT = dwarf; 376 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 377 | ENABLE_NS_ASSERTIONS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PREPROCESSOR_DEFINITIONS = ( 380 | "$(inherited)", 381 | "SWIFT_PACKAGE=1", 382 | "DEBUG=1", 383 | ); 384 | MACOSX_DEPLOYMENT_TARGET = 10.10; 385 | ONLY_ACTIVE_ARCH = YES; 386 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SDKROOT = macosx; 389 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | USE_HEADERMAP = NO; 393 | }; 394 | name = Debug; 395 | }; 396 | OBJ_4 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | COMBINE_HIDPI_IMAGES = YES; 401 | COPY_PHASE_STRIP = YES; 402 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 403 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 404 | GCC_OPTIMIZATION_LEVEL = s; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "$(inherited)", 407 | "SWIFT_PACKAGE=1", 408 | ); 409 | MACOSX_DEPLOYMENT_TARGET = 10.10; 410 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SDKROOT = macosx; 413 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 414 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 416 | USE_HEADERMAP = NO; 417 | }; 418 | name = Release; 419 | }; 420 | OBJ_59 /* Debug */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ENABLE_TESTABILITY = YES; 424 | FRAMEWORK_SEARCH_PATHS = ( 425 | "$(inherited)", 426 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 427 | ); 428 | HEADER_SEARCH_PATHS = ( 429 | "$(inherited)", 430 | "$(SRCROOT)/Sources/Runtime/include", 431 | ); 432 | INFOPLIST_FILE = CombineCocoa.xcodeproj/CombineCocoa_Info.plist; 433 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 435 | MACOSX_DEPLOYMENT_TARGET = 10.10; 436 | OTHER_CFLAGS = "$(inherited)"; 437 | OTHER_LDFLAGS = "$(inherited)"; 438 | OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/Sources/Runtime/include/module.modulemap"; 439 | PRODUCT_BUNDLE_IDENTIFIER = CombineCocoa; 440 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 441 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 442 | SKIP_INSTALL = YES; 443 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 444 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Sources/Runtime/include"; 445 | SWIFT_VERSION = 5.0; 446 | TARGET_NAME = CombineCocoa; 447 | TVOS_DEPLOYMENT_TARGET = 9.0; 448 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 449 | }; 450 | name = Debug; 451 | }; 452 | OBJ_60 /* Release */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ENABLE_TESTABILITY = YES; 456 | FRAMEWORK_SEARCH_PATHS = ( 457 | "$(inherited)", 458 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 459 | ); 460 | HEADER_SEARCH_PATHS = ( 461 | "$(inherited)", 462 | "$(SRCROOT)/Sources/Runtime/include", 463 | ); 464 | INFOPLIST_FILE = CombineCocoa.xcodeproj/CombineCocoa_Info.plist; 465 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 466 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 467 | MACOSX_DEPLOYMENT_TARGET = 10.10; 468 | OTHER_CFLAGS = "$(inherited)"; 469 | OTHER_LDFLAGS = "$(inherited)"; 470 | OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/Sources/Runtime/include/module.modulemap"; 471 | PRODUCT_BUNDLE_IDENTIFIER = CombineCocoa; 472 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 473 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 474 | SKIP_INSTALL = YES; 475 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 476 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Sources/Runtime/include"; 477 | SWIFT_VERSION = 5.0; 478 | TARGET_NAME = CombineCocoa; 479 | TVOS_DEPLOYMENT_TARGET = 9.0; 480 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 481 | }; 482 | name = Release; 483 | }; 484 | OBJ_93 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | LD = /usr/bin/true; 488 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 5.1.0"; 489 | SWIFT_VERSION = 5.0; 490 | }; 491 | name = Debug; 492 | }; 493 | OBJ_94 /* Release */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | LD = /usr/bin/true; 497 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk -package-description-version 5.1.0"; 498 | SWIFT_VERSION = 5.0; 499 | }; 500 | name = Release; 501 | }; 502 | OBJ_98 /* Debug */ = { 503 | isa = XCBuildConfiguration; 504 | buildSettings = { 505 | DEFINES_MODULE = NO; 506 | ENABLE_TESTABILITY = YES; 507 | FRAMEWORK_SEARCH_PATHS = ( 508 | "$(inherited)", 509 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 510 | ); 511 | HEADER_SEARCH_PATHS = ( 512 | "$(inherited)", 513 | "$(SRCROOT)/Sources/Runtime/include", 514 | ); 515 | INFOPLIST_FILE = CombineCocoa.xcodeproj/Runtime_Info.plist; 516 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 517 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 518 | MACOSX_DEPLOYMENT_TARGET = 10.10; 519 | OTHER_CFLAGS = "$(inherited)"; 520 | OTHER_LDFLAGS = "$(inherited)"; 521 | OTHER_SWIFT_FLAGS = "$(inherited)"; 522 | PRODUCT_BUNDLE_IDENTIFIER = Runtime; 523 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 524 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 525 | SKIP_INSTALL = YES; 526 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 527 | TARGET_NAME = Runtime; 528 | TVOS_DEPLOYMENT_TARGET = 9.0; 529 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 530 | }; 531 | name = Debug; 532 | }; 533 | OBJ_99 /* Release */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | DEFINES_MODULE = NO; 537 | ENABLE_TESTABILITY = YES; 538 | FRAMEWORK_SEARCH_PATHS = ( 539 | "$(inherited)", 540 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 541 | ); 542 | HEADER_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "$(SRCROOT)/Sources/Runtime/include", 545 | ); 546 | INFOPLIST_FILE = CombineCocoa.xcodeproj/Runtime_Info.plist; 547 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 548 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 549 | MACOSX_DEPLOYMENT_TARGET = 10.10; 550 | OTHER_CFLAGS = "$(inherited)"; 551 | OTHER_LDFLAGS = "$(inherited)"; 552 | OTHER_SWIFT_FLAGS = "$(inherited)"; 553 | PRODUCT_BUNDLE_IDENTIFIER = Runtime; 554 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 555 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 556 | SKIP_INSTALL = YES; 557 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 558 | TARGET_NAME = Runtime; 559 | TVOS_DEPLOYMENT_TARGET = 9.0; 560 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 561 | }; 562 | name = Release; 563 | }; 564 | /* End XCBuildConfiguration section */ 565 | 566 | /* Begin XCConfigurationList section */ 567 | OBJ_2 /* Build configuration list for PBXProject "CombineCocoa" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | OBJ_3 /* Debug */, 571 | OBJ_4 /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | OBJ_58 /* Build configuration list for PBXNativeTarget "CombineCocoa" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | OBJ_59 /* Debug */, 580 | OBJ_60 /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | OBJ_92 /* Build configuration list for PBXNativeTarget "CombineCocoaPackageDescription" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | OBJ_93 /* Debug */, 589 | OBJ_94 /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | OBJ_97 /* Build configuration list for PBXNativeTarget "Runtime" */ = { 595 | isa = XCConfigurationList; 596 | buildConfigurations = ( 597 | OBJ_98 /* Debug */, 598 | OBJ_99 /* Release */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | /* End XCConfigurationList section */ 604 | }; 605 | rootObject = OBJ_1 /* Project object */; 606 | } 607 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /CombineCocoa.xcodeproj/xcshareddata/xcschemes/CombineCocoa-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0AEC3D4E25260DC7007EE97C /* UISearchBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC3D4D25260DC7007EE97C /* UISearchBarTests.swift */; }; 11 | 3D5D919445BB2B0DA0F0A455 /* Pods_Example_ExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE798B0656978A899BDE50BF /* Pods_Example_ExampleTests.framework */; }; 12 | 3F50DED325D6B4B700AC53A7 /* UIPageControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F50DED225D6B4B700AC53A7 /* UIPageControlTests.swift */; }; 13 | 499340808F6A4BFA9D08AB34 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7789E43DCE40382369F72B2 /* Pods_Example.framework */; }; 14 | 7822AD1E24E1504600187502 /* UITextViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7822AD1D24E1504600187502 /* UITextViewTests.swift */; }; 15 | A233795622F59DBD0083F92F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A233795522F59DBD0083F92F /* Main.storyboard */; }; 16 | A24C43FD22F592E700BC2E2B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A24C43FC22F592E700BC2E2B /* AppDelegate.swift */; }; 17 | A24C43FF22F592E700BC2E2B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A24C43FE22F592E700BC2E2B /* SceneDelegate.swift */; }; 18 | A24C440122F592E700BC2E2B /* ControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A24C440022F592E700BC2E2B /* ControlsViewController.swift */; }; 19 | A24C440622F592E800BC2E2B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A24C440522F592E800BC2E2B /* Assets.xcassets */; }; 20 | A24C440922F592E800BC2E2B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A24C440722F592E800BC2E2B /* LaunchScreen.storyboard */; }; 21 | B9BC1EAD26BAE72B00AE95E5 /* UIScrollViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BC1EAC26BAE72B00AE95E5 /* UIScrollViewTests.swift */; }; 22 | DC8A555624B5ED4B007BCEEC /* UITableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8A555524B5ED4B007BCEEC /* UITableViewTests.swift */; }; 23 | DC8A555E24B60A9A007BCEEC /* UICollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8A555D24B60A9A007BCEEC /* UICollectionViewTests.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | DC8A555824B5ED4B007BCEEC /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = A24C43F122F592E700BC2E2B /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = A24C43F822F592E700BC2E2B; 32 | remoteInfo = Example; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 0AEC3D4D25260DC7007EE97C /* UISearchBarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISearchBarTests.swift; sourceTree = ""; }; 38 | 3F50DED225D6B4B700AC53A7 /* UIPageControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPageControlTests.swift; sourceTree = ""; }; 39 | 7822AD1D24E1504600187502 /* UITextViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewTests.swift; sourceTree = ""; }; 40 | A233795522F59DBD0083F92F /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 41 | A24C43F922F592E700BC2E2B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | A24C43FC22F592E700BC2E2B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | A24C43FE22F592E700BC2E2B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 44 | A24C440022F592E700BC2E2B /* ControlsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsViewController.swift; sourceTree = ""; }; 45 | A24C440522F592E800BC2E2B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | A24C440822F592E800BC2E2B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | A24C440A22F592E800BC2E2B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | A29BBF7A230183A800E2D144 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/LaunchScreen.strings; sourceTree = ""; }; 49 | A3A5BF6E4631484B5F0C14C7 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; 50 | AD949152515161F740DAF80C /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 51 | B84F6B1029BF235509B7F677 /* Pods-Example-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-ExampleTests.release.xcconfig"; path = "Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests.release.xcconfig"; sourceTree = ""; }; 52 | B9BC1EAC26BAE72B00AE95E5 /* UIScrollViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewTests.swift; sourceTree = ""; wrapsLines = 1; }; 53 | BE798B0656978A899BDE50BF /* Pods_Example_ExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example_ExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | DC8A555324B5ED4B007BCEEC /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | DC8A555524B5ED4B007BCEEC /* UITableViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewTests.swift; sourceTree = ""; }; 56 | DC8A555724B5ED4B007BCEEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | DC8A555D24B60A9A007BCEEC /* UICollectionViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewTests.swift; sourceTree = ""; }; 58 | E7789E43DCE40382369F72B2 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | FB09C37F8131759582DE37F2 /* Pods-Example-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-ExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests.debug.xcconfig"; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | A24C43F622F592E700BC2E2B /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 499340808F6A4BFA9D08AB34 /* Pods_Example.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | DC8A555024B5ED4B007BCEEC /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 3D5D919445BB2B0DA0F0A455 /* Pods_Example_ExampleTests.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | 188A69E03FD2502C8B88E87A /* Frameworks */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | E7789E43DCE40382369F72B2 /* Pods_Example.framework */, 86 | BE798B0656978A899BDE50BF /* Pods_Example_ExampleTests.framework */, 87 | ); 88 | name = Frameworks; 89 | sourceTree = ""; 90 | }; 91 | A24C43F022F592E700BC2E2B = { 92 | isa = PBXGroup; 93 | children = ( 94 | A24C43FB22F592E700BC2E2B /* Example */, 95 | DC8A555424B5ED4B007BCEEC /* ExampleTests */, 96 | A24C43FA22F592E700BC2E2B /* Products */, 97 | D8BE4EB532AFD7B4CF9F910A /* Pods */, 98 | 188A69E03FD2502C8B88E87A /* Frameworks */, 99 | ); 100 | indentWidth = 4; 101 | sourceTree = ""; 102 | tabWidth = 4; 103 | }; 104 | A24C43FA22F592E700BC2E2B /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | A24C43F922F592E700BC2E2B /* Example.app */, 108 | DC8A555324B5ED4B007BCEEC /* ExampleTests.xctest */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | A24C43FB22F592E700BC2E2B /* Example */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | A24C43FC22F592E700BC2E2B /* AppDelegate.swift */, 117 | A24C43FE22F592E700BC2E2B /* SceneDelegate.swift */, 118 | A24C440022F592E700BC2E2B /* ControlsViewController.swift */, 119 | A24C440522F592E800BC2E2B /* Assets.xcassets */, 120 | A24C440722F592E800BC2E2B /* LaunchScreen.storyboard */, 121 | A24C440A22F592E800BC2E2B /* Info.plist */, 122 | A233795522F59DBD0083F92F /* Main.storyboard */, 123 | ); 124 | path = Example; 125 | sourceTree = ""; 126 | }; 127 | D8BE4EB532AFD7B4CF9F910A /* Pods */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | A3A5BF6E4631484B5F0C14C7 /* Pods-Example.debug.xcconfig */, 131 | AD949152515161F740DAF80C /* Pods-Example.release.xcconfig */, 132 | FB09C37F8131759582DE37F2 /* Pods-Example-ExampleTests.debug.xcconfig */, 133 | B84F6B1029BF235509B7F677 /* Pods-Example-ExampleTests.release.xcconfig */, 134 | ); 135 | path = Pods; 136 | sourceTree = ""; 137 | }; 138 | DC8A555424B5ED4B007BCEEC /* ExampleTests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | DC8A555524B5ED4B007BCEEC /* UITableViewTests.swift */, 142 | DC8A555D24B60A9A007BCEEC /* UICollectionViewTests.swift */, 143 | B9BC1EAC26BAE72B00AE95E5 /* UIScrollViewTests.swift */, 144 | 7822AD1D24E1504600187502 /* UITextViewTests.swift */, 145 | 0AEC3D4D25260DC7007EE97C /* UISearchBarTests.swift */, 146 | 3F50DED225D6B4B700AC53A7 /* UIPageControlTests.swift */, 147 | DC8A555724B5ED4B007BCEEC /* Info.plist */, 148 | ); 149 | path = ExampleTests; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | A24C43F822F592E700BC2E2B /* Example */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = A24C440D22F592E800BC2E2B /* Build configuration list for PBXNativeTarget "Example" */; 158 | buildPhases = ( 159 | 1A00880812CC0A46E5A1E4A6 /* [CP] Check Pods Manifest.lock */, 160 | A24C43F522F592E700BC2E2B /* Sources */, 161 | A24C43F622F592E700BC2E2B /* Frameworks */, 162 | A24C43F722F592E700BC2E2B /* Resources */, 163 | 8B81933BC72185EEB0CF378D /* [CP] Embed Pods Frameworks */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = Example; 170 | productName = Example; 171 | productReference = A24C43F922F592E700BC2E2B /* Example.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | DC8A555224B5ED4B007BCEEC /* ExampleTests */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = DC8A555C24B5ED4B007BCEEC /* Build configuration list for PBXNativeTarget "ExampleTests" */; 177 | buildPhases = ( 178 | 809417A297E5B9BC9F9C1977 /* [CP] Check Pods Manifest.lock */, 179 | DC8A554F24B5ED4B007BCEEC /* Sources */, 180 | DC8A555024B5ED4B007BCEEC /* Frameworks */, 181 | DC8A555124B5ED4B007BCEEC /* Resources */, 182 | D5923E773DBC45360D2D9DC0 /* [CP] Embed Pods Frameworks */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | DC8A555924B5ED4B007BCEEC /* PBXTargetDependency */, 188 | ); 189 | name = ExampleTests; 190 | productName = ExampleTests; 191 | productReference = DC8A555324B5ED4B007BCEEC /* ExampleTests.xctest */; 192 | productType = "com.apple.product-type.bundle.unit-test"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | A24C43F122F592E700BC2E2B /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | LastSwiftUpdateCheck = 1150; 201 | LastUpgradeCheck = 1100; 202 | ORGANIZATIONNAME = "Shai Mishali"; 203 | TargetAttributes = { 204 | A24C43F822F592E700BC2E2B = { 205 | CreatedOnToolsVersion = 11.0; 206 | }; 207 | DC8A555224B5ED4B007BCEEC = { 208 | CreatedOnToolsVersion = 11.5; 209 | TestTargetID = A24C43F822F592E700BC2E2B; 210 | }; 211 | }; 212 | }; 213 | buildConfigurationList = A24C43F422F592E700BC2E2B /* Build configuration list for PBXProject "Example" */; 214 | compatibilityVersion = "Xcode 9.3"; 215 | developmentRegion = en; 216 | hasScannedForEncodings = 0; 217 | knownRegions = ( 218 | en, 219 | Base, 220 | he, 221 | ); 222 | mainGroup = A24C43F022F592E700BC2E2B; 223 | productRefGroup = A24C43FA22F592E700BC2E2B /* Products */; 224 | projectDirPath = ""; 225 | projectRoot = ""; 226 | targets = ( 227 | A24C43F822F592E700BC2E2B /* Example */, 228 | DC8A555224B5ED4B007BCEEC /* ExampleTests */, 229 | ); 230 | }; 231 | /* End PBXProject section */ 232 | 233 | /* Begin PBXResourcesBuildPhase section */ 234 | A24C43F722F592E700BC2E2B /* Resources */ = { 235 | isa = PBXResourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | A233795622F59DBD0083F92F /* Main.storyboard in Resources */, 239 | A24C440922F592E800BC2E2B /* LaunchScreen.storyboard in Resources */, 240 | A24C440622F592E800BC2E2B /* Assets.xcassets in Resources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | DC8A555124B5ED4B007BCEEC /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXResourcesBuildPhase section */ 252 | 253 | /* Begin PBXShellScriptBuildPhase section */ 254 | 1A00880812CC0A46E5A1E4A6 /* [CP] Check Pods Manifest.lock */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputFileListPaths = ( 260 | ); 261 | inputPaths = ( 262 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 263 | "${PODS_ROOT}/Manifest.lock", 264 | ); 265 | name = "[CP] Check Pods Manifest.lock"; 266 | outputFileListPaths = ( 267 | ); 268 | outputPaths = ( 269 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | shellPath = /bin/sh; 273 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 274 | showEnvVarsInLog = 0; 275 | }; 276 | 809417A297E5B9BC9F9C1977 /* [CP] Check Pods Manifest.lock */ = { 277 | isa = PBXShellScriptBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | ); 281 | inputFileListPaths = ( 282 | ); 283 | inputPaths = ( 284 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 285 | "${PODS_ROOT}/Manifest.lock", 286 | ); 287 | name = "[CP] Check Pods Manifest.lock"; 288 | outputFileListPaths = ( 289 | ); 290 | outputPaths = ( 291 | "$(DERIVED_FILE_DIR)/Pods-Example-ExampleTests-checkManifestLockResult.txt", 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | shellPath = /bin/sh; 295 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 296 | showEnvVarsInLog = 0; 297 | }; 298 | 8B81933BC72185EEB0CF378D /* [CP] Embed Pods Frameworks */ = { 299 | isa = PBXShellScriptBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | inputFileListPaths = ( 304 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-input-files.xcfilelist", 305 | ); 306 | name = "[CP] Embed Pods Frameworks"; 307 | outputFileListPaths = ( 308 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-output-files.xcfilelist", 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | shellPath = /bin/sh; 312 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; 313 | showEnvVarsInLog = 0; 314 | }; 315 | D5923E773DBC45360D2D9DC0 /* [CP] Embed Pods Frameworks */ = { 316 | isa = PBXShellScriptBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | ); 320 | inputFileListPaths = ( 321 | "${PODS_ROOT}/Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", 322 | ); 323 | name = "[CP] Embed Pods Frameworks"; 324 | outputFileListPaths = ( 325 | "${PODS_ROOT}/Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | shellPath = /bin/sh; 329 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-ExampleTests/Pods-Example-ExampleTests-frameworks.sh\"\n"; 330 | showEnvVarsInLog = 0; 331 | }; 332 | /* End PBXShellScriptBuildPhase section */ 333 | 334 | /* Begin PBXSourcesBuildPhase section */ 335 | A24C43F522F592E700BC2E2B /* Sources */ = { 336 | isa = PBXSourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | A24C440122F592E700BC2E2B /* ControlsViewController.swift in Sources */, 340 | A24C43FD22F592E700BC2E2B /* AppDelegate.swift in Sources */, 341 | A24C43FF22F592E700BC2E2B /* SceneDelegate.swift in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | DC8A554F24B5ED4B007BCEEC /* Sources */ = { 346 | isa = PBXSourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | 7822AD1E24E1504600187502 /* UITextViewTests.swift in Sources */, 350 | 0AEC3D4E25260DC7007EE97C /* UISearchBarTests.swift in Sources */, 351 | DC8A555E24B60A9A007BCEEC /* UICollectionViewTests.swift in Sources */, 352 | B9BC1EAD26BAE72B00AE95E5 /* UIScrollViewTests.swift in Sources */, 353 | 3F50DED325D6B4B700AC53A7 /* UIPageControlTests.swift in Sources */, 354 | DC8A555624B5ED4B007BCEEC /* UITableViewTests.swift in Sources */, 355 | ); 356 | runOnlyForDeploymentPostprocessing = 0; 357 | }; 358 | /* End PBXSourcesBuildPhase section */ 359 | 360 | /* Begin PBXTargetDependency section */ 361 | DC8A555924B5ED4B007BCEEC /* PBXTargetDependency */ = { 362 | isa = PBXTargetDependency; 363 | target = A24C43F822F592E700BC2E2B /* Example */; 364 | targetProxy = DC8A555824B5ED4B007BCEEC /* PBXContainerItemProxy */; 365 | }; 366 | /* End PBXTargetDependency section */ 367 | 368 | /* Begin PBXVariantGroup section */ 369 | A24C440722F592E800BC2E2B /* LaunchScreen.storyboard */ = { 370 | isa = PBXVariantGroup; 371 | children = ( 372 | A24C440822F592E800BC2E2B /* Base */, 373 | A29BBF7A230183A800E2D144 /* he */, 374 | ); 375 | name = LaunchScreen.storyboard; 376 | sourceTree = ""; 377 | }; 378 | /* End PBXVariantGroup section */ 379 | 380 | /* Begin XCBuildConfiguration section */ 381 | A24C440B22F592E800BC2E2B /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 386 | CLANG_ANALYZER_NONNULL = YES; 387 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 389 | CLANG_CXX_LIBRARY = "libc++"; 390 | CLANG_ENABLE_MODULES = YES; 391 | CLANG_ENABLE_OBJC_ARC = YES; 392 | CLANG_ENABLE_OBJC_WEAK = YES; 393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_COMMA = YES; 396 | CLANG_WARN_CONSTANT_CONVERSION = YES; 397 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 399 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 400 | CLANG_WARN_EMPTY_BODY = YES; 401 | CLANG_WARN_ENUM_CONVERSION = YES; 402 | CLANG_WARN_INFINITE_RECURSION = YES; 403 | CLANG_WARN_INT_CONVERSION = YES; 404 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 406 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 409 | CLANG_WARN_STRICT_PROTOTYPES = YES; 410 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 411 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | COPY_PHASE_STRIP = NO; 415 | DEBUG_INFORMATION_FORMAT = dwarf; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | ENABLE_TESTABILITY = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu11; 419 | GCC_DYNAMIC_NO_PIC = NO; 420 | GCC_NO_COMMON_BLOCKS = YES; 421 | GCC_OPTIMIZATION_LEVEL = 0; 422 | GCC_PREPROCESSOR_DEFINITIONS = ( 423 | "DEBUG=1", 424 | "$(inherited)", 425 | ); 426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 428 | GCC_WARN_UNDECLARED_SELECTOR = YES; 429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 430 | GCC_WARN_UNUSED_FUNCTION = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 433 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 434 | MTL_FAST_MATH = YES; 435 | ONLY_ACTIVE_ARCH = YES; 436 | SDKROOT = iphoneos; 437 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 438 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 439 | }; 440 | name = Debug; 441 | }; 442 | A24C440C22F592E800BC2E2B /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ALWAYS_SEARCH_USER_PATHS = NO; 446 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 447 | CLANG_ANALYZER_NONNULL = YES; 448 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 449 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 450 | CLANG_CXX_LIBRARY = "libc++"; 451 | CLANG_ENABLE_MODULES = YES; 452 | CLANG_ENABLE_OBJC_ARC = YES; 453 | CLANG_ENABLE_OBJC_WEAK = YES; 454 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 455 | CLANG_WARN_BOOL_CONVERSION = YES; 456 | CLANG_WARN_COMMA = YES; 457 | CLANG_WARN_CONSTANT_CONVERSION = YES; 458 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 459 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 460 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 461 | CLANG_WARN_EMPTY_BODY = YES; 462 | CLANG_WARN_ENUM_CONVERSION = YES; 463 | CLANG_WARN_INFINITE_RECURSION = YES; 464 | CLANG_WARN_INT_CONVERSION = YES; 465 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 466 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 467 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 468 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 469 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 470 | CLANG_WARN_STRICT_PROTOTYPES = YES; 471 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 472 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 473 | CLANG_WARN_UNREACHABLE_CODE = YES; 474 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 475 | COPY_PHASE_STRIP = NO; 476 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 477 | ENABLE_NS_ASSERTIONS = NO; 478 | ENABLE_STRICT_OBJC_MSGSEND = YES; 479 | GCC_C_LANGUAGE_STANDARD = gnu11; 480 | GCC_NO_COMMON_BLOCKS = YES; 481 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 482 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 483 | GCC_WARN_UNDECLARED_SELECTOR = YES; 484 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 485 | GCC_WARN_UNUSED_FUNCTION = YES; 486 | GCC_WARN_UNUSED_VARIABLE = YES; 487 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 488 | MTL_ENABLE_DEBUG_INFO = NO; 489 | MTL_FAST_MATH = YES; 490 | SDKROOT = iphoneos; 491 | SWIFT_COMPILATION_MODE = wholemodule; 492 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 493 | VALIDATE_PRODUCT = YES; 494 | }; 495 | name = Release; 496 | }; 497 | A24C440E22F592E800BC2E2B /* Debug */ = { 498 | isa = XCBuildConfiguration; 499 | baseConfigurationReference = A3A5BF6E4631484B5F0C14C7 /* Pods-Example.debug.xcconfig */; 500 | buildSettings = { 501 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 502 | CODE_SIGN_STYLE = Automatic; 503 | INFOPLIST_FILE = Example/Info.plist; 504 | LD_RUNPATH_SEARCH_PATHS = ( 505 | "$(inherited)", 506 | "@executable_path/Frameworks", 507 | ); 508 | PRODUCT_BUNDLE_IDENTIFIER = com.freak4pc.CombineCocoaExample; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SWIFT_VERSION = 5.0; 511 | TARGETED_DEVICE_FAMILY = 1; 512 | }; 513 | name = Debug; 514 | }; 515 | A24C440F22F592E800BC2E2B /* Release */ = { 516 | isa = XCBuildConfiguration; 517 | baseConfigurationReference = AD949152515161F740DAF80C /* Pods-Example.release.xcconfig */; 518 | buildSettings = { 519 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 520 | CODE_SIGN_STYLE = Automatic; 521 | INFOPLIST_FILE = Example/Info.plist; 522 | LD_RUNPATH_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "@executable_path/Frameworks", 525 | ); 526 | PRODUCT_BUNDLE_IDENTIFIER = com.freak4pc.CombineCocoaExample; 527 | PRODUCT_NAME = "$(TARGET_NAME)"; 528 | SWIFT_VERSION = 5.0; 529 | TARGETED_DEVICE_FAMILY = 1; 530 | }; 531 | name = Release; 532 | }; 533 | DC8A555A24B5ED4B007BCEEC /* Debug */ = { 534 | isa = XCBuildConfiguration; 535 | baseConfigurationReference = FB09C37F8131759582DE37F2 /* Pods-Example-ExampleTests.debug.xcconfig */; 536 | buildSettings = { 537 | BUNDLE_LOADER = "$(TEST_HOST)"; 538 | CODE_SIGN_STYLE = Automatic; 539 | INFOPLIST_FILE = ExampleTests/Info.plist; 540 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 541 | LD_RUNPATH_SEARCH_PATHS = ( 542 | "$(inherited)", 543 | "@executable_path/Frameworks", 544 | "@loader_path/Frameworks", 545 | ); 546 | PRODUCT_BUNDLE_IDENTIFIER = CombineCocoa.ExampleTests; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | SWIFT_VERSION = 5.0; 549 | TARGETED_DEVICE_FAMILY = "1,2"; 550 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 551 | }; 552 | name = Debug; 553 | }; 554 | DC8A555B24B5ED4B007BCEEC /* Release */ = { 555 | isa = XCBuildConfiguration; 556 | baseConfigurationReference = B84F6B1029BF235509B7F677 /* Pods-Example-ExampleTests.release.xcconfig */; 557 | buildSettings = { 558 | BUNDLE_LOADER = "$(TEST_HOST)"; 559 | CODE_SIGN_STYLE = Automatic; 560 | INFOPLIST_FILE = ExampleTests/Info.plist; 561 | IPHONEOS_DEPLOYMENT_TARGET = 13.5; 562 | LD_RUNPATH_SEARCH_PATHS = ( 563 | "$(inherited)", 564 | "@executable_path/Frameworks", 565 | "@loader_path/Frameworks", 566 | ); 567 | PRODUCT_BUNDLE_IDENTIFIER = CombineCocoa.ExampleTests; 568 | PRODUCT_NAME = "$(TARGET_NAME)"; 569 | SWIFT_VERSION = 5.0; 570 | TARGETED_DEVICE_FAMILY = "1,2"; 571 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 572 | }; 573 | name = Release; 574 | }; 575 | /* End XCBuildConfiguration section */ 576 | 577 | /* Begin XCConfigurationList section */ 578 | A24C43F422F592E700BC2E2B /* Build configuration list for PBXProject "Example" */ = { 579 | isa = XCConfigurationList; 580 | buildConfigurations = ( 581 | A24C440B22F592E800BC2E2B /* Debug */, 582 | A24C440C22F592E800BC2E2B /* Release */, 583 | ); 584 | defaultConfigurationIsVisible = 0; 585 | defaultConfigurationName = Release; 586 | }; 587 | A24C440D22F592E800BC2E2B /* Build configuration list for PBXNativeTarget "Example" */ = { 588 | isa = XCConfigurationList; 589 | buildConfigurations = ( 590 | A24C440E22F592E800BC2E2B /* Debug */, 591 | A24C440F22F592E800BC2E2B /* Release */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = Release; 595 | }; 596 | DC8A555C24B5ED4B007BCEEC /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | DC8A555A24B5ED4B007BCEEC /* Debug */, 600 | DC8A555B24B5ED4B007BCEEC /* Release */, 601 | ); 602 | defaultConfigurationIsVisible = 0; 603 | defaultConfigurationName = Release; 604 | }; 605 | /* End XCConfigurationList section */ 606 | }; 607 | rootObject = A24C43F122F592E700BC2E2B /* Project object */; 608 | } 609 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Shai Mishali on 03/08/2019. 6 | // Copyright © 2019 Shai Mishali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 14 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "ItunesArtwork@2x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Example/Example/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/ControlsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Shai Mishali on 03/08/2019. 6 | // Copyright © 2019 Shai Mishali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Combine 11 | import CombineCocoa 12 | 13 | class ControlsViewController: UIViewController { 14 | @IBOutlet private var segmented: UISegmentedControl! 15 | @IBOutlet private var slider: UISlider! 16 | @IBOutlet private var textField: UITextField! 17 | @IBOutlet private var button: UIButton! 18 | @IBOutlet private var `switch`: UISwitch! 19 | @IBOutlet private var datePicker: UIDatePicker! 20 | @IBOutlet private var console: UITextView! 21 | @IBOutlet private var rightBarButtonItem: UIBarButtonItem! 22 | 23 | private var subscriptions = Set() 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | // Set up some gesture recognizers 29 | let leftSwipe = UISwipeGestureRecognizer() 30 | leftSwipe.direction = .left 31 | view.addGestureRecognizer(leftSwipe) 32 | 33 | let longPress = UILongPressGestureRecognizer() 34 | longPress.minimumPressDuration = 2 35 | view.addGestureRecognizer(longPress) 36 | 37 | let doubleTap = UITapGestureRecognizer() 38 | doubleTap.numberOfTapsRequired = 2 39 | view.addGestureRecognizer(doubleTap) 40 | 41 | // Each merge can go up to 8 elements, so we have to chain a few of them ;-) 42 | Just("Debug Output:") 43 | .merge(with: segmented.selectedSegmentIndexPublisher.map { "Segmented at index \($0)" }, 44 | slider.valuePublisher.map { "Slider value is \($0)" }, 45 | textField.textPublisher.map { "Text Field text is \($0 ?? "")" }, 46 | button.tapPublisher.map { "Tapped Button" }, 47 | `switch`.isOnPublisher.map { "Switch is now \($0 ? "On" : "Off")" }, 48 | datePicker.datePublisher.map { "Date picker date is \($0)" }, 49 | rightBarButtonItem.tapPublisher.map { "Tapped Right Bar Button Item" }) 50 | .merge(with: leftSwipe.swipePublisher.map { "Swiped Left with Gesture \($0.memoryAddress)" }, 51 | longPress.longPressPublisher.map { "Long Pressed with Gesture \($0.memoryAddress)" }, 52 | doubleTap.tapPublisher.map { "Double-tapped view with two fingers with Gesture \($0.memoryAddress)" }, 53 | console.reachedBottomPublisher().map { _ in "Reached the bottom of the UITextView" }) 54 | .scan("") { $0 + "\n" + $1 } 55 | .handleEvents(receiveOutput: { [console] text in 56 | guard let console = console else { return } 57 | console.scrollRangeToVisible(console.selectedRange) 58 | }) 59 | .assign(to: \.text, on: console) 60 | .store(in: &subscriptions) 61 | } 62 | } 63 | 64 | private extension NSObject { 65 | var memoryAddress: String { 66 | Unmanaged.passUnretained(self).toOpaque().debugDescription 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSMainNibFile 24 | Main 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | UISceneStoryboardFile 39 | Main 40 | 41 | 42 | 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/Example/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 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 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 | -------------------------------------------------------------------------------- /Example/Example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Example 4 | // 5 | // Created by Shai Mishali on 03/08/2019. 6 | // Copyright © 2019 Shai Mishali. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | var window: UIWindow? 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Example/Example/he.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Example/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/ExampleTests/UICollectionViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Joan Disho on 08.07.20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Combine 11 | @testable import CombineCocoa 12 | 13 | class UICollectionViewTests: XCTestCase { 14 | var subscriptions = Set() 15 | 16 | override func tearDown() { 17 | subscriptions = .init() 18 | } 19 | 20 | func test_didSelectItemAt() { 21 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 22 | 23 | var resultIndexPath: IndexPath? = nil 24 | 25 | collectionView.didSelectItemPublisher 26 | .sink(receiveValue: { resultIndexPath = $0 }) 27 | .store(in: &subscriptions) 28 | 29 | let givenIndexPath = IndexPath(row: 1, section: 0) 30 | collectionView.delegate!.collectionView!(collectionView, didSelectItemAt: givenIndexPath) 31 | 32 | XCTAssertEqual(resultIndexPath, givenIndexPath) 33 | } 34 | 35 | func test_didDeselectItemAt() { 36 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 37 | 38 | var resultIndexPath: IndexPath? = nil 39 | 40 | collectionView.didDeselectItemPublisher 41 | .sink(receiveValue: { resultIndexPath = $0 }) 42 | .store(in: &subscriptions) 43 | 44 | let givenIndexPath = IndexPath(row: 1, section: 0) 45 | collectionView.delegate!.collectionView!(collectionView, didDeselectItemAt: givenIndexPath) 46 | 47 | XCTAssertEqual(resultIndexPath, givenIndexPath) 48 | } 49 | 50 | func test_willDisplayCell() { 51 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 52 | 53 | var resultIndexPath: IndexPath? = nil 54 | var resultCollectioViewCell: UICollectionViewCell? = nil 55 | 56 | collectionView.willDisplayCellPublisher 57 | .sink(receiveValue: { cell, indexPath in 58 | resultCollectioViewCell = cell 59 | resultIndexPath = indexPath 60 | }) 61 | .store(in: &subscriptions) 62 | 63 | let givenIndexPath = IndexPath(row: 1, section: 0) 64 | let givenCollectionViewCell = UICollectionViewCell() 65 | collectionView.delegate!.collectionView?(collectionView, willDisplay: givenCollectionViewCell, forItemAt: givenIndexPath) 66 | 67 | XCTAssertEqual(resultIndexPath, givenIndexPath) 68 | XCTAssertEqual(resultCollectioViewCell, givenCollectionViewCell) 69 | } 70 | 71 | func test_didEndDisplayingCell() { 72 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 73 | 74 | var resultIndexPath: IndexPath? = nil 75 | var resultCollectioViewCell: UICollectionViewCell? = nil 76 | 77 | collectionView.didEndDisplayingCellPublisher 78 | .sink(receiveValue: { cell, indexPath in 79 | resultCollectioViewCell = cell 80 | resultIndexPath = indexPath 81 | }) 82 | .store(in: &subscriptions) 83 | 84 | let givenIndexPath = IndexPath(row: 1, section: 0) 85 | let givenCollectionViewCell = UICollectionViewCell() 86 | collectionView.delegate!.collectionView?(collectionView, didEndDisplaying: givenCollectionViewCell, forItemAt: givenIndexPath) 87 | 88 | XCTAssertEqual(resultIndexPath, givenIndexPath) 89 | XCTAssertEqual(resultCollectioViewCell, givenCollectionViewCell) 90 | } 91 | 92 | func test_didSelectItemAt_for_multiple_subscribers() { 93 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 94 | 95 | var firstResultIndexPaths = [IndexPath]() 96 | var secondResultIndexPaths = [IndexPath]() 97 | 98 | collectionView.didSelectItemPublisher 99 | .sink(receiveValue: { firstResultIndexPaths.append($0) }) 100 | .store(in: &subscriptions) 101 | 102 | collectionView.didSelectItemPublisher 103 | .sink(receiveValue: { secondResultIndexPaths.append($0) }) 104 | .store(in: &subscriptions) 105 | 106 | let givenIndexPath = IndexPath(row: 1, section: 0) 107 | collectionView.delegate!.collectionView!(collectionView, didSelectItemAt: givenIndexPath) 108 | 109 | XCTAssertEqual(firstResultIndexPaths, [givenIndexPath]) 110 | XCTAssertEqual(firstResultIndexPaths, secondResultIndexPaths) 111 | } 112 | 113 | func test_didScrollAndDidSelectItemAt() { 114 | let collectionView = UICollectionView(frame: .zero, 115 | collectionViewLayout: UICollectionViewFlowLayout()) 116 | 117 | var didScroll = false 118 | collectionView.didScrollPublisher 119 | .sink(receiveValue: { didScroll = true }) 120 | .store(in: &subscriptions) 121 | 122 | var didSelect = false 123 | collectionView.didSelectItemPublisher 124 | .sink(receiveValue: { _ in didSelect = true }) 125 | .store(in: &subscriptions) 126 | 127 | collectionView.delegate!.scrollViewDidScroll!(collectionView) 128 | collectionView.delegate!.collectionView?(collectionView, didSelectItemAt: .init(row: 0, section: 1)) 129 | 130 | XCTAssertEqual(didScroll, true) 131 | XCTAssertEqual(didSelect, true) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Example/ExampleTests/UIPageControlTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIPageControlTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Lu Hao on 2021/2/12. 6 | // Copyright © 2021 Shai Mishali. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Combine 11 | @testable import CombineCocoa 12 | 13 | class UIPageControlTests: XCTestCase { 14 | 15 | func test_pageControl() { 16 | 17 | let maxPageCount = 10 18 | 19 | let pc = UIPageControl() 20 | pc.numberOfPages = maxPageCount 21 | 22 | var values = [Int]() 23 | 24 | let sub = pc.currentPagePublisher.sink { values.append($0) } 25 | 26 | for page in 1..() 16 | 17 | override func tearDown() { 18 | subscriptions = .init() 19 | } 20 | 21 | func test_didScroll() { 22 | let scrollView = UIScrollView() 23 | 24 | var didScroll = false 25 | 26 | scrollView.didScrollPublisher 27 | .sink(receiveValue: { didScroll = true }) 28 | .store(in: &subscriptions) 29 | 30 | scrollView.delegate!.scrollViewDidScroll!(scrollView) 31 | 32 | XCTAssertEqual(didScroll, true) 33 | } 34 | 35 | func test_willBeginDecelerating() { 36 | let scrollView = UIScrollView() 37 | 38 | var willBeginDecelerating = false 39 | 40 | scrollView.willBeginDeceleratingPublisher 41 | .sink(receiveValue: { willBeginDecelerating = true }) 42 | .store(in: &subscriptions) 43 | 44 | scrollView.delegate!.scrollViewWillBeginDecelerating!(scrollView) 45 | 46 | XCTAssertEqual(willBeginDecelerating, true) 47 | } 48 | 49 | func test_didEndDecelerating() { 50 | let scrollView = UIScrollView() 51 | 52 | var didEndDecelerating = false 53 | 54 | scrollView.didEndDeceleratingPublisher 55 | .sink(receiveValue: { didEndDecelerating = true }) 56 | .store(in: &subscriptions) 57 | 58 | scrollView.delegate!.scrollViewDidEndDecelerating!(scrollView) 59 | 60 | XCTAssertEqual(didEndDecelerating, true) 61 | } 62 | 63 | func test_willBeginDragging() { 64 | let scrollView = UIScrollView() 65 | 66 | var willBeginDragging = false 67 | 68 | scrollView.willBeginDraggingPublisher 69 | .sink(receiveValue: { willBeginDragging = true }) 70 | .store(in: &subscriptions) 71 | 72 | scrollView.delegate!.scrollViewWillBeginDragging!(scrollView) 73 | 74 | XCTAssertEqual(willBeginDragging, true) 75 | } 76 | 77 | func test_willEndDragging() { 78 | let scrollView = UIScrollView() 79 | 80 | var resultVelocity: CGPoint? = nil 81 | var resultTargetContentOffset: UnsafeMutablePointer? = nil 82 | 83 | scrollView.willEndDraggingPublisher 84 | .sink(receiveValue: { (velocity, targetContentOffset) in 85 | resultVelocity = velocity 86 | resultTargetContentOffset = targetContentOffset 87 | }) 88 | .store(in: &subscriptions) 89 | 90 | let givenVelocity: CGPoint = .init(x: 42, y: 42) 91 | let givenTargetContentOffset: UnsafeMutablePointer = UnsafeMutablePointer .allocate(capacity: 1) 92 | 93 | defer { givenTargetContentOffset.deallocate(capacity: 1) } 94 | 95 | scrollView.delegate!.scrollViewWillEndDragging!(scrollView, withVelocity: givenVelocity, targetContentOffset: givenTargetContentOffset) 96 | 97 | XCTAssertEqual(resultVelocity, givenVelocity) 98 | XCTAssertEqual(resultTargetContentOffset, givenTargetContentOffset) 99 | } 100 | 101 | func test_didEndDragging() { 102 | let scrollView = UIScrollView() 103 | 104 | var resultWillDecelerate: Bool? = nil 105 | 106 | scrollView.didEndDraggingPublisher 107 | .sink(receiveValue: { resultWillDecelerate = $0 }) 108 | .store(in: &subscriptions) 109 | 110 | let givenWillDecelerate = true 111 | 112 | scrollView.delegate!.scrollViewDidEndDragging?(scrollView, willDecelerate: givenWillDecelerate) 113 | 114 | XCTAssertEqual(resultWillDecelerate, givenWillDecelerate) 115 | } 116 | 117 | func test_didZoom() { 118 | let scrollView = UIScrollView() 119 | 120 | var didZoom = false 121 | 122 | scrollView.didZoomPublisher 123 | .sink(receiveValue: { didZoom = true }) 124 | .store(in: &subscriptions) 125 | 126 | scrollView.delegate!.scrollViewDidZoom!(scrollView) 127 | 128 | XCTAssertEqual(didZoom, true) 129 | } 130 | 131 | func test_didScrollToTop() { 132 | let scrollView = UIScrollView() 133 | 134 | var didScrollToTop = false 135 | 136 | scrollView.didScrollToTopPublisher 137 | .sink(receiveValue: { didScrollToTop = true }) 138 | .store(in: &subscriptions) 139 | 140 | scrollView.delegate!.scrollViewDidScrollToTop!(scrollView) 141 | 142 | XCTAssertEqual(didScrollToTop, true) 143 | } 144 | 145 | func test_didEndScrollingAnimation() { 146 | let scrollView = UIScrollView() 147 | 148 | var didEndScrollingAnimation = false 149 | 150 | scrollView.didEndScrollingAnimationPublisher 151 | .sink(receiveValue: { didEndScrollingAnimation = true }) 152 | .store(in: &subscriptions) 153 | 154 | scrollView.delegate!.scrollViewDidEndScrollingAnimation!(scrollView) 155 | 156 | XCTAssertEqual(didEndScrollingAnimation, true) 157 | } 158 | 159 | func test_willBeginZooming() { 160 | let scrollView = UIScrollView() 161 | 162 | var resultView: UIView? = nil 163 | 164 | scrollView.willBeginZoomingPublisher 165 | .sink(receiveValue: { resultView = $0 }) 166 | .store(in: &subscriptions) 167 | 168 | let givenView: UIView = UIView() 169 | 170 | scrollView.delegate!.scrollViewWillBeginZooming?(scrollView, with: givenView) 171 | 172 | XCTAssertEqual(resultView, givenView) 173 | } 174 | 175 | func test_didEndZooming() { 176 | let scrollView = UIScrollView() 177 | 178 | var resultView: UIView? = nil 179 | var resultScale: CGFloat? = nil 180 | 181 | scrollView.didEndZooming 182 | .sink(receiveValue: { (view, scale) in 183 | resultView = view 184 | resultScale = scale 185 | }) 186 | .store(in: &subscriptions) 187 | 188 | let givenView: UIView = UIView() 189 | let givenScale: CGFloat = .zero 190 | 191 | scrollView.delegate!.scrollViewDidEndZooming!(scrollView, with: givenView, atScale: givenScale) 192 | 193 | XCTAssertEqual(resultView, givenView) 194 | XCTAssertEqual(resultScale, givenScale) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Example/ExampleTests/UISearchBarTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISearchBarTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Kevin Renskers on 01/10/2020. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Combine 11 | @testable import CombineCocoa 12 | 13 | class UISearchBarTests: XCTestCase { 14 | var subscription: AnyCancellable! 15 | 16 | func test_textDidChange() { 17 | let searchbar = UISearchBar() 18 | 19 | var resultSearchText = "" 20 | 21 | subscription = searchbar.textDidChangePublisher 22 | .sink(receiveValue: { resultSearchText = $0 }) 23 | 24 | let givenSearchText = "Hello world" 25 | searchbar.delegate!.searchBar!(searchbar, textDidChange: givenSearchText) 26 | 27 | XCTAssertEqual(resultSearchText, givenSearchText) 28 | subscription.cancel() 29 | } 30 | 31 | func test_searchButtonClicked() { 32 | let searchbar = UISearchBar() 33 | 34 | var clicked = false 35 | 36 | subscription = searchbar.searchButtonClickedPublisher 37 | .sink(receiveValue: { clicked = true }) 38 | 39 | searchbar.delegate!.searchBarSearchButtonClicked!(searchbar) 40 | 41 | XCTAssertEqual(clicked, true) 42 | subscription.cancel() 43 | } 44 | 45 | func test_cancelButtonClicked() { 46 | let searchbar = UISearchBar() 47 | 48 | var clicked = false 49 | 50 | subscription = searchbar.cancelButtonClickedPublisher 51 | .sink(receiveValue: { clicked = true }) 52 | 53 | searchbar.delegate!.searchBarCancelButtonClicked!(searchbar) 54 | 55 | XCTAssertEqual(clicked, true) 56 | subscription.cancel() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/ExampleTests/UITableViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Joan Disho on 08.07.20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Combine 11 | @testable import CombineCocoa 12 | 13 | class UITableViewTests: XCTestCase { 14 | var subscriptions = Set() 15 | 16 | override func tearDown() { 17 | subscriptions = .init() 18 | } 19 | 20 | func test_didSelectRowAt() { 21 | let tableView = UITableView() 22 | 23 | var resultIndexPath: IndexPath? = nil 24 | 25 | tableView.didSelectRowPublisher 26 | .sink(receiveValue: { resultIndexPath = $0 }) 27 | .store(in: &subscriptions) 28 | 29 | let givenIndexPath = IndexPath(row: 1, section: 0) 30 | tableView.delegate!.tableView!(tableView, didSelectRowAt: givenIndexPath) 31 | 32 | XCTAssertEqual(resultIndexPath, givenIndexPath) 33 | } 34 | 35 | func test_didDeselectRowAt() { 36 | let tableView = UITableView() 37 | 38 | var resultIndexPath: IndexPath? = nil 39 | 40 | tableView.didDeselectRowPublisher 41 | .sink(receiveValue: { resultIndexPath = $0 }) 42 | .store(in: &subscriptions) 43 | 44 | let givenIndexPath = IndexPath(row: 1, section: 0) 45 | tableView.delegate!.tableView!(tableView, didDeselectRowAt: givenIndexPath) 46 | 47 | XCTAssertEqual(resultIndexPath, givenIndexPath) 48 | } 49 | 50 | func test_willDisplayCell() { 51 | let tableView = UITableView() 52 | 53 | var resultIndexPath: IndexPath? = nil 54 | var resultTableViewCell: UITableViewCell? = nil 55 | 56 | tableView.willDisplayCellPublisher 57 | .sink(receiveValue: { cell, indexPath in 58 | resultTableViewCell = cell 59 | resultIndexPath = indexPath 60 | }) 61 | .store(in: &subscriptions) 62 | 63 | let givenIndexPath = IndexPath(row: 1, section: 0) 64 | let givenTableViewCell = UITableViewCell() 65 | tableView.delegate!.tableView!(tableView, willDisplay: givenTableViewCell, forRowAt: givenIndexPath) 66 | 67 | XCTAssertEqual(resultIndexPath, givenIndexPath) 68 | XCTAssertEqual(resultTableViewCell, givenTableViewCell) 69 | } 70 | 71 | func test_didEndDisplayingCell() { 72 | let tableView = UITableView() 73 | 74 | var resultIndexPath: IndexPath? = nil 75 | var resultTableViewCell: UITableViewCell? = nil 76 | 77 | tableView.didEndDisplayingCellPublisher 78 | .sink(receiveValue: { cell, indexPath in 79 | resultTableViewCell = cell 80 | resultIndexPath = indexPath 81 | }) 82 | .store(in: &subscriptions) 83 | 84 | let givenIndexPath = IndexPath(row: 1, section: 0) 85 | let givenTableViewCell = UITableViewCell() 86 | tableView.delegate!.tableView!(tableView, didEndDisplaying: givenTableViewCell, forRowAt: givenIndexPath) 87 | 88 | XCTAssertEqual(resultIndexPath, givenIndexPath) 89 | XCTAssertEqual(resultTableViewCell, givenTableViewCell) 90 | } 91 | 92 | func test_itemAccessoryButtonTapped() { 93 | let tableView = UITableView() 94 | 95 | var resultIndexPath: IndexPath? = nil 96 | 97 | tableView.itemAccessoryButtonTappedPublisher 98 | .sink(receiveValue: { resultIndexPath = $0 }) 99 | .store(in: &subscriptions) 100 | 101 | let givenIndexPath = IndexPath(row: 1, section: 0) 102 | tableView.delegate!.tableView!(tableView, accessoryButtonTappedForRowWith: givenIndexPath) 103 | 104 | XCTAssertEqual(resultIndexPath, givenIndexPath) 105 | } 106 | 107 | func test_didSelectRowAt_for_multiple_subscribers() { 108 | let tableView = UITableView() 109 | 110 | var firstResultIndexPaths = [IndexPath]() 111 | var secondResultIndexPaths = [IndexPath]() 112 | 113 | tableView.didSelectRowPublisher 114 | .sink(receiveValue: { firstResultIndexPaths.append($0) }) 115 | .store(in: &subscriptions) 116 | 117 | tableView.didSelectRowPublisher 118 | .sink(receiveValue: { secondResultIndexPaths.append($0) }) 119 | .store(in: &subscriptions) 120 | 121 | let givenIndexPath = IndexPath(row: 1, section: 0) 122 | tableView.delegate!.tableView!(tableView, didSelectRowAt: givenIndexPath) 123 | 124 | XCTAssertEqual(firstResultIndexPaths, [givenIndexPath]) 125 | XCTAssertEqual(firstResultIndexPaths, secondResultIndexPaths) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Example/ExampleTests/UITextViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextViewTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Shai Mishali on 10/08/2020. 6 | // Copyright © 2020 Shai Mishali. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Combine 11 | @testable import CombineCocoa 12 | 13 | class UITextViewTests: XCTestCase { 14 | var subscriptions = Set() 15 | 16 | override func tearDown() { 17 | subscriptions = .init() 18 | } 19 | 20 | func test_valuePublisher() { 21 | let tv = UITextView(frame: .zero) 22 | var values = [String?]() 23 | 24 | tv.valuePublisher 25 | .sink(receiveValue: { values.append($0) }) 26 | .store(in: &subscriptions) 27 | 28 | tv.text = "hey" 29 | tv.text = "hey ho" 30 | tv.text = "test" 31 | tv.text = "shai" 32 | tv.text = "" 33 | 34 | XCTAssertEqual(values, ["", "hey", "hey ho", "test", "shai", ""]) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '13.0' 2 | use_frameworks! 3 | 4 | target 'Example' do 5 | pod 'CombineCocoa', :path => '../' 6 | 7 | target 'ExampleTests' do 8 | 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CombineCocoa (0.4.1) 3 | 4 | DEPENDENCIES: 5 | - CombineCocoa (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | CombineCocoa: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | CombineCocoa: 68a050228ce7c53c1c5ac2c57cccf92a7f5c5085 13 | 14 | PODFILE CHECKSUM: 19898029b2f3640d6c998827b076660d4767a5fd 15 | 16 | COCOAPODS: 1.10.2 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '1.9.1' 4 | gem 'xcodeproj' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.3) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.4) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.9.1) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.9.1) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.14.0, < 2.0) 34 | cocoapods-core (1.9.1) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | netrc (~> 0.11) 41 | typhoeus (~> 1.0) 42 | cocoapods-deintegrate (1.0.4) 43 | cocoapods-downloader (1.4.0) 44 | cocoapods-plugins (1.0.0) 45 | nap 46 | cocoapods-search (1.0.0) 47 | cocoapods-stats (1.1.0) 48 | cocoapods-trunk (1.5.0) 49 | nap (>= 0.8, < 2.0) 50 | netrc (~> 0.11) 51 | cocoapods-try (1.2.0) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.1.7) 54 | escape (0.0.4) 55 | ethon (0.12.0) 56 | ffi (>= 1.3.0) 57 | ffi (1.13.1) 58 | fourflusher (2.3.1) 59 | fuzzy_match (2.0.4) 60 | gh_inspector (1.1.3) 61 | httpclient (2.8.3) 62 | i18n (0.9.5) 63 | concurrent-ruby (~> 1.0) 64 | json (2.3.1) 65 | minitest (5.14.2) 66 | molinillo (0.6.6) 67 | nanaimo (0.3.0) 68 | nap (1.1.0) 69 | netrc (0.11.0) 70 | ruby-macho (1.4.0) 71 | thread_safe (0.3.6) 72 | typhoeus (1.4.0) 73 | ethon (>= 0.9.0) 74 | tzinfo (1.2.7) 75 | thread_safe (~> 0.1) 76 | xcodeproj (1.18.0) 77 | CFPropertyList (>= 2.3.3, < 4.0) 78 | atomos (~> 0.1.3) 79 | claide (>= 1.0.2, < 2.0) 80 | colored2 (~> 3.1) 81 | nanaimo (~> 0.3.0) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | cocoapods (= 1.9.1) 88 | xcodeproj 89 | 90 | BUNDLED WITH 91 | 2.1.4 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Shai Mishali 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | archive: 2 | scripts/carthage-archive.sh 3 | project: 4 | scripts/make_project.rb -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "CombineCocoa", 6 | platforms: [.iOS(.v10)], 7 | products: [ 8 | .library(name: "CombineCocoa", targets: ["CombineCocoa"]), 9 | ], 10 | dependencies: [], 11 | targets: [ 12 | .target(name: "CombineCocoa", dependencies: ["Runtime"]), 13 | .target(name: "Runtime", dependencies: []) 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CombineCocoa 2 | 3 |

4 | 5 |

6 | Build Status
7 | CombineCocoa supports CocoaPods 8 | CombineCocoa supports Swift Package Manager (SPM) 9 | CombineCocoa supports Carthage 10 |
11 | 12 |

13 | 14 | CombineCocoa attempts to provide publishers for common UIKit controls so you can consume user interaction as Combine emissions and compose them into meaningful, logical publisher chains. 15 | 16 | **Note**: This is still a primal version of this, with much more to be desired. I gladly accept PRs, ideas, opinions, or improvements. Thank you ! :) 17 | 18 | ## Basic Examples 19 | 20 | Check out the [Example in the **Example** folder](https://github.com/freak4pc/CombineCocoa/blob/main/Example/Example/ControlsViewController.swift#L27). Open the project in Xcode 11 and Swift Package Manager should automatically resolve the required dependencies. 21 | 22 |

23 | 24 | ## Usage 25 | 26 | tl;dr: 27 | 28 | ```swift 29 | import Combine 30 | import CombineCocoa 31 | 32 | textField.textPublisher // AnyPublisher 33 | segmented.selectedSegmentIndexPublisher // AnyPublisher 34 | slider.valuePublisher // AnyPublisher 35 | button.tapPublisher // AnyPublisher 36 | barButtonItem.tapPublisher // AnyPublisher 37 | switch.isOnPublisher // AnyPublisher 38 | stepper.valuePublisher // AnyPublisher 39 | datePicker.datePublisher // AnyPublisher 40 | refreshControl.isRefreshingPublisher // AnyPublisher 41 | pageControl.currentPagePublisher // AnyPublisher 42 | tapGesture.tapPublisher // AnyPublisher 43 | pinchGesture.pinchPublisher // AnyPublisher 44 | rotationGesture.rotationPublisher // AnyPublisher 45 | swipeGesture.swipePublisher // AnyPublisher 46 | panGesture.panPublisher // AnyPublisher 47 | screenEdgePanGesture.screenEdgePanPublisher // AnyPublisher 48 | longPressGesture.longPressPublisher // AnyPublisher 49 | scrollView.contentOffsetPublisher // AnyPublisher 50 | scrollView.reachedBottomPublisher(offset:) // AnyPublisher 51 | ``` 52 | 53 | ## Installation 54 | 55 | ### CocoaPods 56 | 57 | Add the following line to your **Podfile**: 58 | 59 | ```rb 60 | pod 'CombineCocoa' 61 | ``` 62 | 63 | ### Swift Package Manager 64 | 65 | Add the following dependency to your **Package.swift** file: 66 | 67 | ```swift 68 | .package(url: "https://github.com/CombineCommunity/CombineCocoa.git", from: "0.2.1") 69 | ``` 70 | 71 | ### Carthage 72 | 73 | Add the following to your **Cartfile**: 74 | 75 | ``` 76 | github "CombineCommunity/CombineCocoa" 77 | ``` 78 | 79 | ## Future ideas 80 | 81 | * Support non `UIControl.Event`-based publishers (e.g. delegates). 82 | * ... your ideas? :) 83 | 84 | ## Acknowledgments 85 | 86 | * CombineCocoa is highly inspired by RxSwift's [RxCocoa](https://github.com/ReactiveX/RxSwift) in its essence, kudos to [Krunoslav Zaher](https://twitter.com/KrunoslavZaher) for all of his amazing work on this. 87 | * Thanks to [Antoine van der Lee](https://twitter.com/twannl) for his tutorial on [Creating Custom Publishers](https://www.avanderlee.com/swift/custom-combine-publisher/). The idea to set up a control target inside the publisher was inspired by it. 88 | 89 | ## License 90 | 91 | MIT, of course ;-) See the [LICENSE](LICENSE) file. 92 | 93 | The Apple logo and the Combine framework are property of Apple Inc. 94 | -------------------------------------------------------------------------------- /Resources/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Resources/example.gif -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CombineCommunity/CombineCocoa/7300c75ff9e072aa7fd0fccefcc88f74aae9bf56/Resources/logo.png -------------------------------------------------------------------------------- /Sources/CombineCocoa/AnimatedAssignSubscriber.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedAssignSubscriber.swift 3 | // CombineCocoa 4 | // 5 | // Created by Marin Todorov on 05/03/20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if canImport(UIKit) && !(os(iOS) && (arch(i386) || arch(arm))) 12 | import Combine 13 | import UIKit 14 | 15 | /// A list of animations that can be used with `Publisher.assign(to:on:animation:)` 16 | @available(iOS 13.0, *) 17 | public enum AssignTransition { 18 | public enum Direction { 19 | case top, bottom, left, right 20 | } 21 | 22 | /// Flip from either bottom, top, left, or right. 23 | case flip(direction: Direction, duration: TimeInterval) 24 | 25 | /// Cross fade with previous value. 26 | case fade(duration: TimeInterval) 27 | 28 | /// A custom animation. Do not include your own code to update the target of the assign subscriber. 29 | case animation(duration: TimeInterval, options: UIView.AnimationOptions, animations: () -> Void, completion: ((Bool) -> Void)?) 30 | } 31 | 32 | @available(iOS 13.0, *) 33 | public extension Publisher where Self.Failure == Never { 34 | /// Behaves identically to `Publisher.assign(to:on:)` except that it allows the user to 35 | /// "wrap" emitting output in an animation transition. 36 | /// 37 | /// For example if you assign values to a `UILabel` on screen you 38 | /// can make it flip over when each new value is set: 39 | /// 40 | /// ``` 41 | /// myPublisher 42 | /// .assign(to: \.text, 43 | /// on: myLabel, 44 | /// animation: .flip(direction: .bottom, duration: 0.33)) 45 | /// ``` 46 | /// 47 | /// You may also provide a custom animation block, as follows: 48 | /// 49 | /// ``` 50 | /// myPublisher 51 | /// .assign(to: \.text, on: myLabel, animation: .animation(duration: 0.33, options: .curveEaseIn, animations: { _ in 52 | /// myLabel.center.x += 10.0 53 | /// }, completion: nil)) 54 | /// ``` 55 | func assign(to keyPath: ReferenceWritableKeyPath, on object: Root, animation: AssignTransition) -> AnyCancellable { 56 | var transition: UIView.AnimationOptions 57 | var duration: TimeInterval 58 | 59 | switch animation { 60 | case .fade(let interval): 61 | duration = interval 62 | transition = .transitionCrossDissolve 63 | case let .flip(dir, interval): 64 | duration = interval 65 | switch dir { 66 | case .bottom: transition = .transitionFlipFromBottom 67 | case .top: transition = .transitionFlipFromTop 68 | case .left: transition = .transitionFlipFromLeft 69 | case .right: transition = .transitionFlipFromRight 70 | } 71 | case let .animation(interval, options, animations, completion): 72 | // Use a custom animation. 73 | return handleEvents( 74 | receiveOutput: { value in 75 | UIView.animate(withDuration: interval, 76 | delay: 0, 77 | options: options, 78 | animations: { 79 | object[keyPath: keyPath] = value 80 | animations() 81 | }, 82 | completion: completion) 83 | } 84 | ) 85 | .sink { _ in } 86 | } 87 | 88 | // Use one of the built-in transitions like flip or crossfade. 89 | return self 90 | .handleEvents(receiveOutput: { value in 91 | UIView.transition(with: object, 92 | duration: duration, 93 | options: transition, 94 | animations: { 95 | object[keyPath: keyPath] = value 96 | }, 97 | completion: nil) 98 | }) 99 | .sink { _ in } 100 | } 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/CombineCocoa.h: -------------------------------------------------------------------------------- 1 | // 2 | // CombineCocoa.h 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | FOUNDATION_EXPORT double CombineCocoaVersionNumber; 13 | FOUNDATION_EXPORT const unsigned char CombineCocoaVersionString[]; 14 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/CombineControlEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineControlEvent.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 01/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import Foundation 12 | import UIKit.UIControl 13 | 14 | // MARK: - Publisher 15 | @available(iOS 13.0, *) 16 | public extension Combine.Publishers { 17 | /// A Control Event is a publisher that emits whenever the provided 18 | /// Control Events fire. 19 | struct ControlEvent: Publisher { 20 | public typealias Output = Void 21 | public typealias Failure = Never 22 | 23 | private let control: Control 24 | private let controlEvents: Control.Event 25 | 26 | /// Initialize a publisher that emits a Void 27 | /// whenever any of the provided Control Events trigger. 28 | /// 29 | /// - parameter control: UI Control. 30 | /// - parameter events: Control Events. 31 | public init(control: Control, 32 | events: Control.Event) { 33 | self.control = control 34 | self.controlEvents = events 35 | } 36 | 37 | public func receive(subscriber: S) where S.Failure == Failure, S.Input == Output { 38 | let subscription = Subscription(subscriber: subscriber, 39 | control: control, 40 | event: controlEvents) 41 | 42 | subscriber.receive(subscription: subscription) 43 | } 44 | } 45 | } 46 | 47 | // MARK: - Subscription 48 | @available(iOS 13.0, *) 49 | extension Combine.Publishers.ControlEvent { 50 | private final class Subscription: Combine.Subscription where S.Input == Void { 51 | private var subscriber: S? 52 | weak private var control: Control? 53 | 54 | init(subscriber: S, control: Control, event: Control.Event) { 55 | self.subscriber = subscriber 56 | self.control = control 57 | control.addTarget(self, action: #selector(processControlEvent), for: event) 58 | } 59 | 60 | func request(_ demand: Subscribers.Demand) { 61 | // We don't care about the demand at this point. 62 | // As far as we're concerned - UIControl events are endless until the control is deallocated. 63 | } 64 | 65 | func cancel() { 66 | subscriber = nil 67 | } 68 | 69 | @objc private func processControlEvent() { 70 | _ = subscriber?.receive() 71 | } 72 | } 73 | } 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/CombineControlProperty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineControlProperty.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 01/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import Foundation 12 | import UIKit.UIControl 13 | 14 | // MARK: - Publisher 15 | @available(iOS 13.0, *) 16 | public extension Combine.Publishers { 17 | /// A Control Property is a publisher that emits the value at the provided keypath 18 | /// whenever the specific control events are triggered. It also emits the keypath's 19 | /// initial value upon subscription. 20 | struct ControlProperty: Publisher { 21 | public typealias Output = Value 22 | public typealias Failure = Never 23 | 24 | private let control: Control 25 | private let controlEvents: Control.Event 26 | private let keyPath: KeyPath 27 | 28 | /// Initialize a publisher that emits the value at the specified keypath 29 | /// whenever any of the provided Control Events trigger. 30 | /// 31 | /// - parameter control: UI Control. 32 | /// - parameter events: Control Events. 33 | /// - parameter keyPath: A Key Path from the UI Control to the requested value. 34 | public init(control: Control, 35 | events: Control.Event, 36 | keyPath: KeyPath) { 37 | self.control = control 38 | self.controlEvents = events 39 | self.keyPath = keyPath 40 | } 41 | 42 | public func receive(subscriber: S) where S.Failure == Failure, S.Input == Output { 43 | let subscription = Subscription(subscriber: subscriber, 44 | control: control, 45 | event: controlEvents, 46 | keyPath: keyPath) 47 | 48 | subscriber.receive(subscription: subscription) 49 | } 50 | } 51 | } 52 | 53 | // MARK: - Subscription 54 | @available(iOS 13.0, *) 55 | extension Combine.Publishers.ControlProperty { 56 | private final class Subscription: Combine.Subscription where S.Input == Value { 57 | private var subscriber: S? 58 | weak private var control: Control? 59 | let keyPath: KeyPath 60 | private var didEmitInitial = false 61 | private let event: Control.Event 62 | 63 | init(subscriber: S, control: Control, event: Control.Event, keyPath: KeyPath) { 64 | self.subscriber = subscriber 65 | self.control = control 66 | self.keyPath = keyPath 67 | self.event = event 68 | control.addTarget(self, action: #selector(processControlEvent), for: event) 69 | } 70 | 71 | func request(_ demand: Subscribers.Demand) { 72 | // Emit initial value upon first demand request 73 | if !didEmitInitial, 74 | demand > .none, 75 | let control = control, 76 | let subscriber = subscriber { 77 | _ = subscriber.receive(control[keyPath: keyPath]) 78 | didEmitInitial = true 79 | } 80 | 81 | // We don't care about the demand at this point. 82 | // As far as we're concerned - UIControl events are endless until the control is deallocated. 83 | } 84 | 85 | func cancel() { 86 | control?.removeTarget(self, action: #selector(processControlEvent), for: event) 87 | subscriber = nil 88 | } 89 | 90 | @objc private func processControlEvent() { 91 | guard let control = control else { return } 92 | _ = subscriber?.receive(control[keyPath: keyPath]) 93 | } 94 | } 95 | } 96 | 97 | extension UIControl.Event { 98 | static var defaultValueEvents: UIControl.Event { 99 | return [.allEditingEvents, .valueChanged] 100 | } 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/CombineControlTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineControlTarget.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 12/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import Foundation 12 | 13 | // MARK: - Publisher 14 | @available(iOS 13.0, *) 15 | public extension Combine.Publishers { 16 | /// A publisher which wraps objects that use the Target & Action mechanism, 17 | /// for example - a UIBarButtonItem which isn't KVO-compliant and doesn't use UIControlEvent(s). 18 | /// 19 | /// Instead, you pass in a generic Control, and two functions: 20 | /// One to add a target action to the provided control, and a second one to 21 | /// remove a target action from a provided control. 22 | struct ControlTarget: Publisher { 23 | public typealias Output = Void 24 | public typealias Failure = Never 25 | 26 | private let control: Control 27 | private let addTargetAction: (Control, AnyObject, Selector) -> Void 28 | private let removeTargetAction: (Control?, AnyObject, Selector) -> Void 29 | 30 | /// Initialize a publisher that emits a Void whenever the 31 | /// provided control fires an action. 32 | /// 33 | /// - parameter control: UI Control. 34 | /// - parameter addTargetAction: A function which accepts the Control, a Target and a Selector and 35 | /// responsible to add the target action to the provided control. 36 | /// - parameter removeTargetAction: A function which accepts the Control, a Target and a Selector and it 37 | /// responsible to remove the target action from the provided control. 38 | public init(control: Control, 39 | addTargetAction: @escaping (Control, AnyObject, Selector) -> Void, 40 | removeTargetAction: @escaping (Control?, AnyObject, Selector) -> Void) { 41 | self.control = control 42 | self.addTargetAction = addTargetAction 43 | self.removeTargetAction = removeTargetAction 44 | } 45 | 46 | public func receive(subscriber: S) where S.Failure == Failure, S.Input == Output { 47 | let subscription = Subscription(subscriber: subscriber, 48 | control: control, 49 | addTargetAction: addTargetAction, 50 | removeTargetAction: removeTargetAction) 51 | 52 | subscriber.receive(subscription: subscription) 53 | } 54 | } 55 | } 56 | 57 | // MARK: - Subscription 58 | @available(iOS 13.0, *) 59 | private extension Combine.Publishers.ControlTarget { 60 | private final class Subscription: Combine.Subscription where S.Input == Void { 61 | private var subscriber: S? 62 | weak private var control: Control? 63 | 64 | private let removeTargetAction: (Control?, AnyObject, Selector) -> Void 65 | private let action = #selector(handleAction) 66 | 67 | init(subscriber: S, 68 | control: Control, 69 | addTargetAction: @escaping (Control, AnyObject, Selector) -> Void, 70 | removeTargetAction: @escaping (Control?, AnyObject, Selector) -> Void) { 71 | self.subscriber = subscriber 72 | self.control = control 73 | self.removeTargetAction = removeTargetAction 74 | 75 | addTargetAction(control, self, action) 76 | } 77 | 78 | func request(_ demand: Subscribers.Demand) { 79 | // We don't care about the demand at this point. 80 | // As far as we're concerned - The control's target events are endless until it is deallocated. 81 | } 82 | 83 | func cancel() { 84 | subscriber = nil 85 | removeTargetAction(control, self, action) 86 | } 87 | 88 | @objc private func handleAction() { 89 | _ = subscriber?.receive() 90 | } 91 | } 92 | } 93 | #endif 94 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/NSTextStorage+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextStorage+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 10/08/2020. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import UIKit 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | public extension NSTextStorage { 15 | /// Combine publisher for `NSTextStorageDelegate.textStorage(_:didProcessEditing:range:changeInLength:)` 16 | var didProcessEditingRangeChangeInLengthPublisher: AnyPublisher<(editedMask: NSTextStorage.EditActions, editedRange: NSRange, delta: Int), Never> { 17 | let selector = #selector(NSTextStorageDelegate.textStorage(_:didProcessEditing:range:changeInLength:)) 18 | 19 | return delegateProxy 20 | .interceptSelectorPublisher(selector) 21 | .map { args -> (editedMask: NSTextStorage.EditActions, editedRange: NSRange, delta: Int) in 22 | // swiftlint:disable force_cast 23 | let editedMask = NSTextStorage.EditActions(rawValue: args[1] as! UInt) 24 | let editedRange = (args[2] as! NSValue).rangeValue 25 | let delta = args[3] as! Int 26 | return (editedMask, editedRange, delta) 27 | // swiftlint:enable force_cast 28 | } 29 | .eraseToAnyPublisher() 30 | } 31 | 32 | private var delegateProxy: NSTextStorageDelegateProxy { 33 | .createDelegateProxy(for: self) 34 | } 35 | } 36 | 37 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 38 | private class NSTextStorageDelegateProxy: DelegateProxy, NSTextStorageDelegate, DelegateProxyType { 39 | func setDelegate(to object: NSTextStorage) { 40 | object.delegate = self 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIBarButtonItem+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBarButtonItem+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 12/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIBarButtonItem { 15 | /// A publisher which emits whenever this UIBarButtonItem is tapped. 16 | var tapPublisher: AnyPublisher { 17 | Publishers.ControlTarget(control: self, 18 | addTargetAction: { control, target, action in 19 | control.target = target 20 | control.action = action 21 | }, 22 | removeTargetAction: { control, _, _ in 23 | control?.target = nil 24 | control?.action = nil 25 | }) 26 | .eraseToAnyPublisher() 27 | } 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIButton+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIButton { 15 | /// A publisher emitting tap events from this button. 16 | var tapPublisher: AnyPublisher { 17 | controlEventPublisher(for: .touchUpInside) 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UICollectionView+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 05/04/20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) && !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Foundation 11 | import UIKit 12 | import Combine 13 | 14 | // swiftlint:disable force_cast 15 | @available(iOS 13.0, *) 16 | public extension UICollectionView { 17 | /// Combine wrapper for `collectionView(_:didSelectItemAt:)` 18 | var didSelectItemPublisher: AnyPublisher { 19 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:)) 20 | return delegateProxy.interceptSelectorPublisher(selector) 21 | .map { $0[1] as! IndexPath } 22 | .eraseToAnyPublisher() 23 | } 24 | 25 | /// Combine wrapper for `collectionView(_:didDeselectItemAt:)` 26 | var didDeselectItemPublisher: AnyPublisher { 27 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didDeselectItemAt:)) 28 | return delegateProxy.interceptSelectorPublisher(selector) 29 | .map { $0[1] as! IndexPath } 30 | .eraseToAnyPublisher() 31 | } 32 | 33 | /// Combine wrapper for `collectionView(_:didHighlightItemAt:)` 34 | var didHighlightItemPublisher: AnyPublisher { 35 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didHighlightItemAt:)) 36 | return delegateProxy.interceptSelectorPublisher(selector) 37 | .map { $0[1] as! IndexPath } 38 | .eraseToAnyPublisher() 39 | } 40 | 41 | /// Combine wrapper for `collectionView(_:didUnhighlightItemAt:)` 42 | var didUnhighlightRowPublisher: AnyPublisher { 43 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didUnhighlightItemAt:)) 44 | return delegateProxy.interceptSelectorPublisher(selector) 45 | .map { $0[1] as! IndexPath } 46 | .eraseToAnyPublisher() 47 | } 48 | 49 | /// Combine wrapper for `collectionView(_:willDisplay:forItemAt:)` 50 | var willDisplayCellPublisher: AnyPublisher<(cell: UICollectionViewCell, indexPath: IndexPath), Never> { 51 | let selector = #selector(UICollectionViewDelegate.collectionView(_:willDisplay:forItemAt:)) 52 | return delegateProxy.interceptSelectorPublisher(selector) 53 | .map { ($0[1] as! UICollectionViewCell, $0[2] as! IndexPath) } 54 | .eraseToAnyPublisher() 55 | } 56 | 57 | /// Combine wrapper for `collectionView(_:willDisplaySupplementaryView:forElementKind:at:)` 58 | var willDisplaySupplementaryViewPublisher: AnyPublisher<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never> { 59 | let selector = #selector(UICollectionViewDelegate.collectionView(_:willDisplaySupplementaryView:forElementKind:at:)) 60 | return delegateProxy.interceptSelectorPublisher(selector) 61 | .map { ($0[1] as! UICollectionReusableView, $0[2] as! String, $0[3] as! IndexPath) } 62 | .eraseToAnyPublisher() 63 | } 64 | 65 | /// Combine wrapper for `collectionView(_:didEndDisplaying:forItemAt:)` 66 | var didEndDisplayingCellPublisher: AnyPublisher<(cell: UICollectionViewCell, indexPath: IndexPath), Never> { 67 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didEndDisplaying:forItemAt:)) 68 | return delegateProxy.interceptSelectorPublisher(selector) 69 | .map { ($0[1] as! UICollectionViewCell, $0[2] as! IndexPath) } 70 | .eraseToAnyPublisher() 71 | } 72 | 73 | /// Combine wrapper for `collectionView(_:didEndDisplayingSupplementaryView:forElementKind:at:)` 74 | var didEndDisplaySupplementaryViewPublisher: AnyPublisher<(supplementaryView: UICollectionReusableView, elementKind: String, indexPath: IndexPath), Never> { 75 | let selector = #selector(UICollectionViewDelegate.collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:)) 76 | return delegateProxy.interceptSelectorPublisher(selector) 77 | .map { ($0[1] as! UICollectionReusableView, $0[2] as! String, $0[3] as! IndexPath) } 78 | .eraseToAnyPublisher() 79 | } 80 | 81 | override var delegateProxy: DelegateProxy { 82 | CollectionViewDelegateProxy.createDelegateProxy(for: self) 83 | } 84 | } 85 | 86 | @available(iOS 13.0, *) 87 | private class CollectionViewDelegateProxy: DelegateProxy, UICollectionViewDelegate, DelegateProxyType { 88 | func setDelegate(to object: UICollectionView) { 89 | object.delegate = self 90 | } 91 | } 92 | #endif 93 | // swiftlint:enable force_cast 94 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIControl+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Wes Wickwire on 9/23/20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIControl { 15 | /// A publisher emitting events from this control. 16 | func controlEventPublisher(for events: UIControl.Event) -> AnyPublisher { 17 | Publishers.ControlEvent(control: self, events: events) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIDatePicker+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDatePicker+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIDatePicker { 15 | /// A publisher emitting date changes from this date picker. 16 | var datePublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.date) 18 | .eraseToAnyPublisher() 19 | } 20 | 21 | /// A publisher emitting countdown duration changes from this date picker. 22 | var countDownDurationPublisher: AnyPublisher { 23 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.countDownDuration) 24 | .eraseToAnyPublisher() 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIGestureRecognizer+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 12/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | // MARK: - Gesture Publishers 14 | @available(iOS 13.0, *) 15 | public extension UITapGestureRecognizer { 16 | /// A publisher which emits when this Tap Gesture Recognizer is triggered 17 | var tapPublisher: AnyPublisher { 18 | gesturePublisher(for: self) 19 | } 20 | } 21 | 22 | @available(iOS 13.0, *) 23 | public extension UIPinchGestureRecognizer { 24 | /// A publisher which emits when this Pinch Gesture Recognizer is triggered 25 | var pinchPublisher: AnyPublisher { 26 | gesturePublisher(for: self) 27 | } 28 | } 29 | 30 | @available(iOS 13.0, *) 31 | public extension UIRotationGestureRecognizer { 32 | /// A publisher which emits when this Rotation Gesture Recognizer is triggered 33 | var rotationPublisher: AnyPublisher { 34 | gesturePublisher(for: self) 35 | } 36 | } 37 | 38 | @available(iOS 13.0, *) 39 | public extension UISwipeGestureRecognizer { 40 | /// A publisher which emits when this Swipe Gesture Recognizer is triggered 41 | var swipePublisher: AnyPublisher { 42 | gesturePublisher(for: self) 43 | } 44 | } 45 | 46 | @available(iOS 13.0, *) 47 | public extension UIPanGestureRecognizer { 48 | /// A publisher which emits when this Pan Gesture Recognizer is triggered 49 | var panPublisher: AnyPublisher { 50 | gesturePublisher(for: self) 51 | } 52 | } 53 | 54 | @available(iOS 13.0, *) 55 | public extension UIScreenEdgePanGestureRecognizer { 56 | /// A publisher which emits when this Screen Edge Gesture Recognizer is triggered 57 | var screenEdgePanPublisher: AnyPublisher { 58 | gesturePublisher(for: self) 59 | } 60 | } 61 | 62 | @available(iOS 13.0, *) 63 | public extension UILongPressGestureRecognizer { 64 | /// A publisher which emits when this Long Press Recognizer is triggered 65 | var longPressPublisher: AnyPublisher { 66 | gesturePublisher(for: self) 67 | } 68 | } 69 | 70 | // MARK: - Private Helpers 71 | 72 | // A private generic helper function which returns the provided 73 | // generic publisher whenever its specific event occurs. 74 | @available(iOS 13.0, *) 75 | private func gesturePublisher(for gesture: Gesture) -> AnyPublisher { 76 | Publishers.ControlTarget(control: gesture, 77 | addTargetAction: { gesture, target, action in 78 | gesture.addTarget(target, action: action) 79 | }, 80 | removeTargetAction: { gesture, target, action in 81 | gesture?.removeTarget(target, action: action) 82 | }) 83 | .subscribe(on: DispatchQueue.main) 84 | .map { gesture } 85 | .eraseToAnyPublisher() 86 | } 87 | #endif 88 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIPageControl+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIPageControl+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIPageControl { 15 | /// A publisher emitting current page changes for this page control. 16 | var currentPagePublisher: AnyPublisher { 17 | publisher(for: \.currentPage).eraseToAnyPublisher() 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIRefreshControl+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIRefreshControl+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIRefreshControl { 15 | /// A publisher emitting refresh status changes from this refresh control. 16 | var isRefreshingPublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.isRefreshing) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIScrollView+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 09/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import UIKit 11 | import Combine 12 | 13 | // swiftlint:disable force_cast 14 | @available(iOS 13.0, *) 15 | public extension UIScrollView { 16 | /// A publisher emitting content offset changes from this UIScrollView. 17 | var contentOffsetPublisher: AnyPublisher { 18 | publisher(for: \.contentOffset) 19 | .eraseToAnyPublisher() 20 | } 21 | 22 | /// A publisher emitting if the bottom of the UIScrollView is reached. 23 | /// 24 | /// - parameter offset: A threshold indicating how close to the bottom of the UIScrollView this publisher should emit. 25 | /// Defaults to 0 26 | /// - returns: A publisher that emits when the bottom of the UIScrollView is reached within the provided threshold. 27 | func reachedBottomPublisher(offset: CGFloat = 0) -> AnyPublisher { 28 | contentOffsetPublisher 29 | .map { [weak self] contentOffset -> Bool in 30 | guard let self = self else { return false } 31 | let visibleHeight = self.frame.height - self.contentInset.top - self.contentInset.bottom 32 | let yDelta = contentOffset.y + self.contentInset.top 33 | let threshold = max(offset, self.contentSize.height - visibleHeight) 34 | return yDelta > threshold 35 | } 36 | .removeDuplicates() 37 | .filter { $0 } 38 | .map { _ in () } 39 | .eraseToAnyPublisher() 40 | } 41 | 42 | /// Combine wrapper for `scrollViewDidScroll(_:)` 43 | var didScrollPublisher: AnyPublisher { 44 | let selector = #selector(UIScrollViewDelegate.scrollViewDidScroll(_:)) 45 | return delegateProxy.interceptSelectorPublisher(selector) 46 | .map { _ in () } 47 | .eraseToAnyPublisher() 48 | } 49 | 50 | /// Combine wrapper for `scrollViewWillBeginDecelerating(_:)` 51 | var willBeginDeceleratingPublisher: AnyPublisher { 52 | let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginDecelerating(_:)) 53 | return delegateProxy.interceptSelectorPublisher(selector) 54 | .map { _ in () } 55 | .eraseToAnyPublisher() 56 | } 57 | 58 | /// Combine wrapper for `scrollViewDidEndDecelerating(_:)` 59 | var didEndDeceleratingPublisher: AnyPublisher { 60 | let selector = #selector(UIScrollViewDelegate.scrollViewDidEndDecelerating(_:)) 61 | return delegateProxy.interceptSelectorPublisher(selector) 62 | .map { _ in () } 63 | .eraseToAnyPublisher() 64 | } 65 | 66 | /// Combine wrapper for `scrollViewWillBeginDragging(_:)` 67 | var willBeginDraggingPublisher: AnyPublisher { 68 | let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginDragging(_:)) 69 | return delegateProxy.interceptSelectorPublisher(selector) 70 | .map { _ in () } 71 | .eraseToAnyPublisher() 72 | } 73 | 74 | /// Combine wrapper for `scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)` 75 | var willEndDraggingPublisher: AnyPublisher<(velocity: CGPoint, targetContentOffset: UnsafeMutablePointer), Never> { 76 | let selector = #selector(UIScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)) 77 | return delegateProxy.interceptSelectorPublisher(selector) 78 | .map { values in 79 | let targetContentOffsetValue = values[2] as! NSValue 80 | let rawPointer = targetContentOffsetValue.pointerValue! 81 | 82 | return (values[1] as! CGPoint, rawPointer.bindMemory(to: CGPoint.self, capacity: MemoryLayout.size)) 83 | } 84 | .eraseToAnyPublisher() 85 | } 86 | 87 | /// Combine wrapper for `scrollViewDidEndDragging(_:willDecelerate:)` 88 | var didEndDraggingPublisher: AnyPublisher { 89 | let selector = #selector(UIScrollViewDelegate.scrollViewDidEndDragging(_:willDecelerate:)) 90 | return delegateProxy.interceptSelectorPublisher(selector) 91 | .map { $0[1] as! Bool } 92 | .eraseToAnyPublisher() 93 | } 94 | 95 | /// Combine wrapper for `scrollViewDidZoom(_:)` 96 | var didZoomPublisher: AnyPublisher { 97 | let selector = #selector(UIScrollViewDelegate.scrollViewDidZoom(_:)) 98 | return delegateProxy.interceptSelectorPublisher(selector) 99 | .map { _ in () } 100 | .eraseToAnyPublisher() 101 | } 102 | 103 | /// Combine wrapper for `scrollViewDidScrollToTop(_:)` 104 | var didScrollToTopPublisher: AnyPublisher { 105 | let selector = #selector(UIScrollViewDelegate.scrollViewDidScrollToTop(_:)) 106 | return delegateProxy.interceptSelectorPublisher(selector) 107 | .map { _ in () } 108 | .eraseToAnyPublisher() 109 | } 110 | 111 | /// Combine wrapper for `scrollViewDidEndScrollingAnimation(_:)` 112 | var didEndScrollingAnimationPublisher: AnyPublisher { 113 | let selector = #selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:)) 114 | return delegateProxy.interceptSelectorPublisher(selector) 115 | .map { _ in () } 116 | .eraseToAnyPublisher() 117 | } 118 | 119 | /// Combine wrapper for `scrollViewWillBeginZooming(_:with:)` 120 | var willBeginZoomingPublisher: AnyPublisher { 121 | let selector = #selector(UIScrollViewDelegate.scrollViewWillBeginZooming(_:with:)) 122 | return delegateProxy.interceptSelectorPublisher(selector) 123 | .map { $0[1] as! UIView? } 124 | .eraseToAnyPublisher() 125 | } 126 | 127 | /// Combine wrapper for `scrollViewDidEndZooming(_:with:atScale:)` 128 | var didEndZooming: AnyPublisher<(view: UIView?, scale: CGFloat), Never> { 129 | let selector = #selector(UIScrollViewDelegate.scrollViewDidEndZooming(_:with:atScale:)) 130 | return delegateProxy.interceptSelectorPublisher(selector) 131 | .map { ($0[1] as! UIView?, $0[2] as! CGFloat) } 132 | .eraseToAnyPublisher() 133 | } 134 | 135 | @objc var delegateProxy: DelegateProxy { 136 | ScrollViewDelegateProxy.createDelegateProxy(for: self) 137 | } 138 | } 139 | 140 | @available(iOS 13.0, *) 141 | private class ScrollViewDelegateProxy: DelegateProxy, UIScrollViewDelegate, DelegateProxyType { 142 | func setDelegate(to object: UIScrollView) { 143 | object.delegate = self 144 | } 145 | } 146 | #endif 147 | // swiftlint:enable force_cast 148 | 149 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UISearchBar+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISearchBar+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Kevin Renskers on 01/10/2020. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import UIKit 11 | import Combine 12 | 13 | // swiftlint:disable force_cast 14 | @available(iOS 13.0, *) 15 | public extension UISearchBar { 16 | /// Combine wrapper for `UISearchBarDelegate.searchBar(_:textDidChange:)` 17 | var textDidChangePublisher: AnyPublisher { 18 | let selector = #selector(UISearchBarDelegate.searchBar(_:textDidChange:)) 19 | return delegateProxy 20 | .interceptSelectorPublisher(selector) 21 | .map { $0[1] as! String } 22 | .eraseToAnyPublisher() 23 | } 24 | 25 | /// Combine wrapper for `UISearchBarDelegate.searchBarSearchButtonClicked(_:)` 26 | var searchButtonClickedPublisher: AnyPublisher { 27 | let selector = #selector(UISearchBarDelegate.searchBarSearchButtonClicked(_:)) 28 | return delegateProxy 29 | .interceptSelectorPublisher(selector) 30 | .map { _ in () } 31 | .eraseToAnyPublisher() 32 | } 33 | 34 | /// Combine wrapper for `UISearchBarDelegate.searchBarCancelButtonClicked(_:)` 35 | var cancelButtonClickedPublisher: AnyPublisher { 36 | let selector = #selector(UISearchBarDelegate.searchBarCancelButtonClicked(_:)) 37 | return delegateProxy 38 | .interceptSelectorPublisher(selector) 39 | .map { _ in () } 40 | .eraseToAnyPublisher() 41 | } 42 | 43 | private var delegateProxy: UISearchBarDelegateProxy { 44 | .createDelegateProxy(for: self) 45 | } 46 | } 47 | 48 | @available(iOS 13.0, *) 49 | private class UISearchBarDelegateProxy: DelegateProxy, UISearchBarDelegate, DelegateProxyType { 50 | func setDelegate(to object: UISearchBar) { 51 | object.delegate = self 52 | } 53 | } 54 | #endif 55 | // swiftlint:enable force_cast 56 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UISegmentedControl+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISegmentedControl+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UISegmentedControl { 15 | /// A publisher emitting selected segment index changes for this segmented control. 16 | var selectedSegmentIndexPublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.selectedSegmentIndex) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UISlider+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISlider+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UISlider { 15 | /// A publisher emitting value changes for this slider. 16 | var valuePublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.value) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UIStepper+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStepper+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UIStepper { 15 | /// A publisher emitting value changes for this stepper. 16 | var valuePublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.value) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UISwitch+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISwitch+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UISwitch { 15 | /// A publisher emitting on status changes for this switch. 16 | var isOnPublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.isOn) 18 | .eraseToAnyPublisher() 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UITableView+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 19/01/20. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) && !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Foundation 11 | import UIKit 12 | import Combine 13 | 14 | // swiftlint:disable force_cast 15 | @available(iOS 13.0, *) 16 | public extension UITableView { 17 | /// Combine wrapper for `tableView(_:willDisplay:forRowAt:)` 18 | var willDisplayCellPublisher: AnyPublisher<(cell: UITableViewCell, indexPath: IndexPath), Never> { 19 | let selector = #selector(UITableViewDelegate.tableView(_:willDisplay:forRowAt:)) 20 | return delegateProxy.interceptSelectorPublisher(selector) 21 | .map { ($0[1] as! UITableViewCell, $0[2] as! IndexPath) } 22 | .eraseToAnyPublisher() 23 | } 24 | 25 | /// Combine wrapper for `tableView(_:willDisplayHeaderView:forSection:)` 26 | var willDisplayHeaderViewPublisher: AnyPublisher<(headerView: UIView, section: Int), Never> { 27 | let selector = #selector(UITableViewDelegate.tableView(_:willDisplayHeaderView:forSection:)) 28 | return delegateProxy.interceptSelectorPublisher(selector) 29 | .map { ($0[1] as! UIView, $0[2] as! Int) } 30 | .eraseToAnyPublisher() 31 | } 32 | 33 | /// Combine wrapper for `tableView(_:willDisplayFooterView:forSection:)` 34 | var willDisplayFooterViewPublisher: AnyPublisher<(footerView: UIView, section: Int), Never> { 35 | let selector = #selector(UITableViewDelegate.tableView(_:willDisplayFooterView:forSection:)) 36 | return delegateProxy.interceptSelectorPublisher(selector) 37 | .map { ($0[1] as! UIView, $0[2] as! Int) } 38 | .eraseToAnyPublisher() 39 | } 40 | 41 | /// Combine wrapper for `tableView(_:didEndDisplaying:forRowAt:)` 42 | var didEndDisplayingCellPublisher: AnyPublisher<(cell: UITableViewCell, indexPath: IndexPath), Never> { 43 | let selector = #selector(UITableViewDelegate.tableView(_:didEndDisplaying:forRowAt:)) 44 | return delegateProxy.interceptSelectorPublisher(selector) 45 | .map { ($0[1] as! UITableViewCell, $0[2] as! IndexPath) } 46 | .eraseToAnyPublisher() 47 | } 48 | 49 | /// Combine wrapper for `tableView(_:didEndDisplayingHeaderView:forSection:)` 50 | var didEndDisplayingHeaderViewPublisher: AnyPublisher<(headerView: UIView, section: Int), Never> { 51 | let selector = #selector(UITableViewDelegate.tableView(_:didEndDisplayingHeaderView:forSection:)) 52 | return delegateProxy.interceptSelectorPublisher(selector) 53 | .map { ($0[1] as! UIView, $0[2] as! Int) } 54 | .eraseToAnyPublisher() 55 | } 56 | 57 | /// Combine wrapper for `tableView(_:didEndDisplayingFooterView:forSection:)` 58 | var didEndDisplayingFooterView: AnyPublisher<(headerView: UIView, section: Int), Never> { 59 | let selector = #selector(UITableViewDelegate.tableView(_:didEndDisplayingFooterView:forSection:)) 60 | return delegateProxy.interceptSelectorPublisher(selector) 61 | .map { ($0[1] as! UIView, $0[2] as! Int) } 62 | .eraseToAnyPublisher() 63 | } 64 | 65 | /// Combine wrapper for `tableView(_:accessoryButtonTappedForRowWith:)` 66 | var itemAccessoryButtonTappedPublisher: AnyPublisher { 67 | let selector = #selector(UITableViewDelegate.tableView(_:accessoryButtonTappedForRowWith:)) 68 | return delegateProxy.interceptSelectorPublisher(selector) 69 | .map { $0[1] as! IndexPath } 70 | .eraseToAnyPublisher() 71 | } 72 | 73 | /// Combine wrapper for `tableView(_:didHighlightRowAt:)` 74 | var didHighlightRowPublisher: AnyPublisher { 75 | let selector = #selector(UITableViewDelegate.tableView(_:didHighlightRowAt:)) 76 | return delegateProxy.interceptSelectorPublisher(selector) 77 | .map { $0[1] as! IndexPath } 78 | .eraseToAnyPublisher() 79 | } 80 | 81 | /// Combine wrapper for `tableView(_:didUnHighlightRowAt:)` 82 | var didUnhighlightRowPublisher: AnyPublisher { 83 | let selector = #selector(UITableViewDelegate.tableView(_:didUnhighlightRowAt:)) 84 | return delegateProxy.interceptSelectorPublisher(selector) 85 | .map { $0[1] as! IndexPath } 86 | .eraseToAnyPublisher() 87 | } 88 | 89 | /// Combine wrapper for `tableView(_:didSelectRowAt:)` 90 | var didSelectRowPublisher: AnyPublisher { 91 | let selector = #selector(UITableViewDelegate.tableView(_:didSelectRowAt:)) 92 | return delegateProxy.interceptSelectorPublisher(selector) 93 | .map { $0[1] as! IndexPath } 94 | .eraseToAnyPublisher() 95 | } 96 | 97 | /// Combine wrapper for `tableView(_:didDeselectRowAt:)` 98 | var didDeselectRowPublisher: AnyPublisher { 99 | let selector = #selector(UITableViewDelegate.tableView(_:didDeselectRowAt:)) 100 | return delegateProxy.interceptSelectorPublisher(selector) 101 | .map { $0[1] as! IndexPath } 102 | .eraseToAnyPublisher() 103 | } 104 | 105 | /// Combine wrapper for `tableView(_:willBeginEditingRowAt:)` 106 | var willBeginEditingRowPublisher: AnyPublisher { 107 | let selector = #selector(UITableViewDelegate.tableView(_:willBeginEditingRowAt:)) 108 | return delegateProxy.interceptSelectorPublisher(selector) 109 | .map { $0[1] as! IndexPath } 110 | .eraseToAnyPublisher() 111 | } 112 | 113 | /// Combine wrapper for `tableView(_:didEndEditingRowAt:)` 114 | var didEndEditingRowPublisher: AnyPublisher { 115 | let selector = #selector(UITableViewDelegate.tableView(_:didEndEditingRowAt:)) 116 | return delegateProxy.interceptSelectorPublisher(selector) 117 | .map { $0[1] as! IndexPath } 118 | .eraseToAnyPublisher() 119 | } 120 | 121 | override var delegateProxy: DelegateProxy { 122 | TableViewDelegateProxy.createDelegateProxy(for: self) 123 | } 124 | } 125 | 126 | @available(iOS 13.0, *) 127 | private class TableViewDelegateProxy: DelegateProxy, UITableViewDelegate, DelegateProxyType { 128 | func setDelegate(to object: UITableView) { 129 | object.delegate = self 130 | } 131 | } 132 | #endif 133 | // swiftlint:enable force_cast 134 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UITextField+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 02/08/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Combine 11 | import UIKit 12 | 13 | @available(iOS 13.0, *) 14 | public extension UITextField { 15 | /// A publisher emitting any text changes to a this text field. 16 | var textPublisher: AnyPublisher { 17 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.text) 18 | .eraseToAnyPublisher() 19 | } 20 | 21 | /// A publisher emitting any attributed text changes to this text field. 22 | var attributedTextPublisher: AnyPublisher { 23 | Publishers.ControlProperty(control: self, events: .defaultValueEvents, keyPath: \.attributedText) 24 | .eraseToAnyPublisher() 25 | } 26 | 27 | /// A publisher that emits whenever the user taps the return button and ends the editing on the text field. 28 | var returnPublisher: AnyPublisher { 29 | controlEventPublisher(for: .editingDidEndOnExit) 30 | } 31 | 32 | /// A publisher that emits whenever the user taps the text fields and begin the editing. 33 | var didBeginEditingPublisher: AnyPublisher { 34 | controlEventPublisher(for: .editingDidBegin) 35 | } 36 | } 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/Controls/UITextView+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView+Combine.swift 3 | // CombineCocoa 4 | // 5 | // Created by Shai Mishali on 10/08/2020. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import UIKit 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | public extension UITextView { 15 | /// A Combine publisher for the `UITextView's` value. 16 | /// 17 | /// - note: This uses the underlying `NSTextStorage` to make sure 18 | /// autocorrect changes are reflected as well. 19 | /// 20 | /// - seealso: https://git.io/JJM5Q 21 | var valuePublisher: AnyPublisher { 22 | Deferred { [weak textView = self] in 23 | textView?.textStorage 24 | .didProcessEditingRangeChangeInLengthPublisher 25 | .map { _ in textView?.text } 26 | .prepend(textView?.text) 27 | .eraseToAnyPublisher() ?? Empty().eraseToAnyPublisher() 28 | } 29 | .eraseToAnyPublisher() 30 | } 31 | 32 | var textPublisher: AnyPublisher { valuePublisher } 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/DelegateProxy/DelegateProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelegateProxy.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Foundation 11 | import Combine 12 | 13 | #if canImport(Runtime) 14 | import Runtime 15 | #endif 16 | 17 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 18 | open class DelegateProxy: ObjcDelegateProxy { 19 | private var dict: [Selector: [([Any]) -> Void]] = [:] 20 | private var subscribers = [AnySubscriber<[Any], Never>?]() 21 | 22 | public required override init() { 23 | super.init() 24 | } 25 | 26 | public override func interceptedSelector(_ selector: Selector, arguments: [Any]) { 27 | dict[selector]?.forEach { handler in 28 | handler(arguments) 29 | } 30 | } 31 | 32 | public func intercept(_ selector: Selector, _ handler: @escaping ([Any]) -> Void) { 33 | if dict[selector] != nil { 34 | dict[selector]?.append(handler) 35 | } else { 36 | dict[selector] = [handler] 37 | } 38 | } 39 | 40 | public func interceptSelectorPublisher(_ selector: Selector) -> AnyPublisher<[Any], Never> { 41 | DelegateProxyPublisher<[Any]> { subscriber in 42 | self.subscribers.append(subscriber) 43 | return self.intercept(selector) { args in 44 | _ = subscriber.receive(args) 45 | } 46 | }.eraseToAnyPublisher() 47 | } 48 | 49 | deinit { 50 | subscribers.forEach { $0?.receive(completion: .finished) } 51 | subscribers = [] 52 | } 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/DelegateProxy/DelegateProxyPublisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelegateProxyPublisher.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Foundation 11 | import Combine 12 | 13 | @available(iOS 13.0, *) 14 | internal class DelegateProxyPublisher: Publisher { 15 | typealias Failure = Never 16 | 17 | private let handler: (AnySubscriber) -> Void 18 | 19 | init(_ handler: @escaping (AnySubscriber) -> Void) { 20 | self.handler = handler 21 | } 22 | 23 | func receive(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { 24 | let subscription = Subscription(subscriber: AnySubscriber(subscriber), handler: handler) 25 | subscriber.receive(subscription: subscription) 26 | } 27 | } 28 | 29 | @available(iOS 13.0, *) 30 | private extension DelegateProxyPublisher { 31 | class Subscription: Combine.Subscription where S: Subscriber, Failure == S.Failure, Output == S.Input { 32 | private var subscriber: S? 33 | 34 | init(subscriber: S, handler: @escaping (S) -> Void) { 35 | self.subscriber = subscriber 36 | handler(subscriber) 37 | } 38 | 39 | func request(_ demand: Subscribers.Demand) { 40 | // We don't care for the demand. 41 | } 42 | 43 | func cancel() { 44 | subscriber = nil 45 | } 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/CombineCocoa/DelegateProxy/DelegateProxyType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelegateProxyType.swift 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #if !(os(iOS) && (arch(i386) || arch(arm))) 10 | import Foundation 11 | 12 | private var associatedKey = "delegateProxy" 13 | 14 | public protocol DelegateProxyType { 15 | associatedtype Object 16 | 17 | func setDelegate(to object: Object) 18 | } 19 | 20 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) 21 | public extension DelegateProxyType where Self: DelegateProxy { 22 | static func createDelegateProxy(for object: Object) -> Self { 23 | objc_sync_enter(self) 24 | defer { objc_sync_exit(self) } 25 | 26 | let delegateProxy: Self 27 | 28 | if let associatedObject = objc_getAssociatedObject(object, &associatedKey) as? Self { 29 | delegateProxy = associatedObject 30 | } else { 31 | delegateProxy = .init() 32 | objc_setAssociatedObject(object, &associatedKey, delegateProxy, .OBJC_ASSOCIATION_RETAIN) 33 | } 34 | 35 | delegateProxy.setDelegate(to: object) 36 | 37 | return delegateProxy 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 0.0.1 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/Runtime/ObjcDelegateProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjcDelegateProxy.m 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho & Shai Mishali on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "include/ObjcDelegateProxy.h" 11 | #import 12 | 13 | #define OBJECT_VALUE(object) [NSValue valueWithNonretainedObject:(object)] 14 | 15 | static NSMutableDictionary *> *allSelectors; 16 | 17 | @implementation ObjcDelegateProxy 18 | 19 | - (NSSet *)selectors { 20 | return allSelectors[OBJECT_VALUE(self.class)]; 21 | } 22 | 23 | + (void)initialize 24 | { 25 | @synchronized (ObjcDelegateProxy.class) { 26 | if (!allSelectors) { 27 | allSelectors = [NSMutableDictionary new]; 28 | } 29 | allSelectors[OBJECT_VALUE(self)] = [self selectorsOfClass:self 30 | withEncodedReturnType:[NSString stringWithFormat:@"%s", @encode(void)]]; 31 | } 32 | } 33 | 34 | - (BOOL)respondsToSelector:(SEL _Nonnull)aSelector { 35 | return [super respondsToSelector:aSelector] || [self canRespondToSelector:aSelector]; 36 | } 37 | 38 | - (BOOL)canRespondToSelector:(SEL _Nonnull)selector { 39 | for (id current in allSelectors[OBJECT_VALUE(self.class)]) { 40 | if (selector == (SEL) [current pointerValue]) { 41 | return true; 42 | } 43 | } 44 | 45 | return false; 46 | } 47 | 48 | - (void)interceptedSelector:(SEL _Nonnull)selector arguments:(NSArray * _Nonnull)arguments {} 49 | 50 | - (void)forwardInvocation:(NSInvocation *)anInvocation 51 | { 52 | NSArray * _Nonnull arguments = unpackInvocation(anInvocation); 53 | [self interceptedSelector:anInvocation.selector arguments:arguments]; 54 | } 55 | 56 | NSArray * _Nonnull unpackInvocation(NSInvocation * _Nonnull invocation) { 57 | NSUInteger numberOfArguments = invocation.methodSignature.numberOfArguments; 58 | NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:numberOfArguments - 2]; 59 | 60 | // Ignore `self` and `_cmd` at index 0 and 1. 61 | for (NSUInteger index = 2; index < numberOfArguments; ++index) { 62 | const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index]; 63 | 64 | // Skip const type qualifier. 65 | if (argumentType[0] == 'r') { 66 | argumentType++; 67 | } 68 | 69 | #define isArgumentType(type) \ 70 | strcmp(argumentType, @encode(type)) == 0 71 | 72 | #define extractTypeAndSetValue(type, value) \ 73 | type argument = 0; \ 74 | [invocation getArgument:&argument atIndex:index]; \ 75 | value = @(argument); \ 76 | 77 | id _Nonnull value; 78 | 79 | if (isArgumentType(id) || isArgumentType(Class) || isArgumentType(void (^)(void))) { 80 | __unsafe_unretained id argument = nil; 81 | [invocation getArgument:&argument atIndex:index]; 82 | value = argument; 83 | } 84 | else if (isArgumentType(char)) { 85 | extractTypeAndSetValue(char, value); 86 | } 87 | else if (isArgumentType(short)) { 88 | extractTypeAndSetValue(short, value); 89 | } 90 | else if (isArgumentType(int)) { 91 | extractTypeAndSetValue(int, value); 92 | } 93 | else if (isArgumentType(long)) { 94 | extractTypeAndSetValue(long, value); 95 | } 96 | else if (isArgumentType(long long)) { 97 | extractTypeAndSetValue(long long, value); 98 | } 99 | else if (isArgumentType(unsigned char)) { 100 | extractTypeAndSetValue(unsigned char, value); 101 | } 102 | else if (isArgumentType(unsigned short)) { 103 | extractTypeAndSetValue(unsigned short, value); 104 | } 105 | else if (isArgumentType(unsigned int)) { 106 | extractTypeAndSetValue(unsigned int, value); 107 | } 108 | else if (isArgumentType(unsigned long)) { 109 | extractTypeAndSetValue(unsigned long, value); 110 | } 111 | else if (isArgumentType(unsigned long long)) { 112 | extractTypeAndSetValue(unsigned long long, value); 113 | } 114 | else if (isArgumentType(float)) { 115 | extractTypeAndSetValue(float, value); 116 | } 117 | else if (isArgumentType(double)) { 118 | extractTypeAndSetValue(double, value); 119 | } 120 | else if (isArgumentType(BOOL)) { 121 | extractTypeAndSetValue(BOOL, value); 122 | } 123 | else if (isArgumentType(const char *)) { 124 | extractTypeAndSetValue(const char *, value); 125 | } 126 | else { 127 | NSUInteger size = 0; 128 | NSGetSizeAndAlignment(argumentType, &size, NULL); 129 | NSCParameterAssert(size > 0); 130 | uint8_t data[size]; 131 | [invocation getArgument:&data atIndex:index]; 132 | 133 | value = [NSValue valueWithBytes:&data objCType:argumentType]; 134 | } 135 | 136 | [arguments addObject:value]; 137 | } 138 | 139 | return arguments; 140 | } 141 | 142 | + (NSSet *) selectorsOfClass: (Class _Nonnull __unsafe_unretained) class 143 | withEncodedReturnType: (NSString *) encodedReturnType { 144 | unsigned int protocolsCount = 0; 145 | Protocol * __unsafe_unretained _Nonnull * _Nullable protocolPointer = class_copyProtocolList(class, &protocolsCount); 146 | 147 | NSMutableSet *allSelectors = [[self selectorsOfProtocolPointer:protocolPointer 148 | count:protocolsCount 149 | andEncodedReturnType:encodedReturnType] mutableCopy]; 150 | 151 | Class _Nonnull __unsafe_unretained superclass = class_getSuperclass(class); 152 | 153 | if(superclass != nil) { 154 | NSSet *superclassSelectors = [self selectorsOfClass:superclass 155 | withEncodedReturnType:encodedReturnType]; 156 | [allSelectors unionSet:superclassSelectors]; 157 | } 158 | 159 | free(protocolPointer); 160 | 161 | return allSelectors; 162 | } 163 | 164 | + (NSSet *) selectorsOfProtocol: (Protocol * __unsafe_unretained) protocol 165 | andEncodedReturnType: (NSString *) encodedReturnType { 166 | unsigned int protocolMethodCount = 0; 167 | struct objc_method_description * _Nullable methodDescriptions = protocol_copyMethodDescriptionList(protocol, false, true, &protocolMethodCount); 168 | 169 | // Protocol pointers 170 | unsigned int protocolsCount = 0; 171 | Protocol * __unsafe_unretained _Nonnull * _Nullable protocols = protocol_copyProtocolList(protocol, &protocolsCount); 172 | 173 | NSMutableSet *allSelectors = [NSMutableSet new]; 174 | 175 | // Protocol methods 176 | for (NSInteger idx = 0; idx < protocolMethodCount; idx++) { 177 | struct objc_method_description description = methodDescriptions[idx]; 178 | 179 | if ([self encodedMethodReturnTypeForMethod:description] == encodedReturnType) { 180 | [allSelectors addObject: [NSValue valueWithPointer:description.name]]; 181 | } 182 | } 183 | 184 | if (protocols != nil) { 185 | [allSelectors unionSet: [self selectorsOfProtocolPointer:protocols 186 | count:protocolsCount 187 | andEncodedReturnType:encodedReturnType]]; 188 | } 189 | 190 | free(methodDescriptions); 191 | free(protocols); 192 | 193 | return allSelectors; 194 | } 195 | 196 | + (NSSet *) selectorsOfProtocolPointer: (Protocol * __unsafe_unretained * _Nullable) pointer 197 | count: (NSInteger) count 198 | andEncodedReturnType: (NSString *) encodedReturnType { 199 | NSMutableSet *allSelectors = [NSMutableSet new]; 200 | 201 | for (NSInteger i = 0; i < count; i++) { 202 | Protocol * __unsafe_unretained _Nullable protocol = pointer[i]; 203 | 204 | if (protocol == nil) { continue; } 205 | [allSelectors unionSet:[self selectorsOfProtocol:protocol 206 | andEncodedReturnType:encodedReturnType]]; 207 | } 208 | 209 | return allSelectors; 210 | } 211 | 212 | + (NSString *)encodedMethodReturnTypeForMethod: (struct objc_method_description) method { 213 | return [[NSString alloc] initWithBytes:method.types 214 | length:1 215 | encoding:NSASCIIStringEncoding]; 216 | } 217 | 218 | 219 | @end 220 | 221 | -------------------------------------------------------------------------------- /Sources/Runtime/include/ObjcDelegateProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjcDelegateProxy.h 3 | // CombineCocoa 4 | // 5 | // Created by Joan Disho on 25/09/2019. 6 | // Copyright © 2020 Combine Community. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ObjcDelegateProxy: NSObject 12 | 13 | @property (nonnull, strong, atomic, readonly) NSSet *selectors; 14 | 15 | - (void)interceptedSelector:(SEL _Nonnull)selector arguments:(NSArray * _Nonnull)arguments; 16 | - (BOOL)respondsToSelector:(SEL _Nonnull)aSelector; 17 | - (BOOL)canRespondToSelector:(SEL _Nonnull)selector; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Sources/Runtime/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module Runtime { 2 | umbrella header "ObjcDelegateProxy.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests/**/*" 3 | 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | target: auto 9 | threshold: 1% 10 | base: auto -------------------------------------------------------------------------------- /scripts/carthage-archive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! which carthage > /dev/null; then 4 | echo 'Error: Carthage is not installed' >&2 5 | exit 1 6 | fi 7 | 8 | if [ ! -f Package.swift ]; then 9 | echo "Package.swift can't be found, please make sure you run scripts/carthage-archive.sh from the root folder" >&2 10 | exit 1 11 | fi 12 | 13 | if ! which swift > /dev/null; then 14 | echo 'Swift is not installed' >&2 15 | exit 1 16 | fi 17 | 18 | REQUIRED_SWIFT_TOOLING="5.1.0" 19 | TOOLS_VERSION=`swift package tools-version` 20 | 21 | if [ ! "$(printf '%s\n' "$REQUIRED_SWIFT_TOOLING" "$TOOLS_VERSION" | sort -V | head -n1)" = "$REQUIRED_SWIFT_TOOLING" ]; then 22 | echo 'You must have Swift Package Manager 5.1.0 or later.' 23 | exit 1 24 | fi 25 | 26 | swift package generate-xcodeproj 27 | carthage build --no-skip-current --platform iOS 28 | carthage archive 29 | 30 | echo "Upload CombineCocoa.framework.zip to the latest release" -------------------------------------------------------------------------------- /scripts/make_project.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'xcodeproj' 4 | 5 | ### This script creates an Xcode project using Swift Package Manager 6 | ### and then applies every needed configurations and other changes. 7 | ### 8 | ### Written by Shai Mishali, June 1st 2019. 9 | 10 | project_name = "CombineCocoa" 11 | project_file = "#{project_name}.xcodeproj" 12 | podspec = "#{project_name}.podspec" 13 | plist_file = "#{project_file}/#{project_name}_Info.plist" 14 | core_targets = [project_name, "#{project_name}Tests", "#{project_name}PackageDescription", "#{project_name}PackageTests"] 15 | 16 | # Make sure SPM is Installed 17 | system("swift package > /dev/null 2>&1") 18 | abort("SPM is not installed") unless $?.exitstatus == 0 19 | 20 | # Make sure PlistBuddy is Installed 21 | abort("PlistBuddy is not installed") unless File.file?("/usr/libexec/PlistBuddy") 22 | 23 | # Make sure we have a Package.swift file 24 | abort("Can't locate Package.swift") unless File.exist?("Package.swift") 25 | 26 | # Make sure Podspec exists and we can find a version 27 | abort("Can't locate #{podspec}") unless File.exist?(podspec) 28 | podspec_version = nil 29 | File.open(podspec).each do |line| 30 | version = line[/^\s+s\.version\s+=\s+\"(.*?)\"$/, 1] 31 | unless version.nil? 32 | podspec_version = version 33 | break 34 | end 35 | end 36 | 37 | abort("Can't find podspec vesrion") if podspec_version.nil? 38 | 39 | # Attempt generating Xcode Project 40 | system("rf -rf #{project_file}") 41 | system("swift package generate-xcodeproj --enable-code-coverage") 42 | 43 | # Apply CFBundleVersion and CFBundleShortVersionString 44 | fail("Can't find project #{project_file}") unless File.directory?(project_file) 45 | fail("Can't find plist #{plist_file}") unless File.file?(plist_file) 46 | 47 | system("/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion #{podspec_version}\" #{plist_file}") 48 | system("/usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString #{podspec_version}\" #{plist_file}") 49 | 50 | # Apply SwiftLint and other configurations to targets 51 | project = Xcodeproj::Project.open(project_file) 52 | 53 | # Disable arm64 to solve lipo fat binary bug in Xcode 12 54 | project.build_configurations.each do |config| 55 | config.build_settings['EXCLUDED_ARCHS'] = 'arm64' 56 | end 57 | 58 | project.targets.each do |target| 59 | if core_targets.include?(target.name) 60 | swiftlint = target.new_shell_script_build_phase('SwiftLint') 61 | swiftlint.shell_script = <<-SwiftLint 62 | if which swiftlint >/dev/null; then 63 | swiftlint 64 | else 65 | echo "warning: SwiftLint not installed" 66 | fi 67 | SwiftLint 68 | 69 | index = target.build_phases.index { |phase| (defined? phase.name) && phase.name == 'SwiftLint' } 70 | target.build_phases.move_from(index, 0) 71 | else 72 | target.build_configurations.each do |config| 73 | config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES' 74 | config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -suppress-warnings' 75 | end 76 | end 77 | end 78 | 79 | project::save() --------------------------------------------------------------------------------