├── .swift-version
├── Cartfile
├── .github
└── PULL_REQUEST_TEMPLATE.md
├── Logo
├── header.png
├── PNG
│ ├── Docs.png
│ ├── logo.png
│ ├── JoinSlack.png
│ ├── flat-logo.png
│ ├── mono-dark-bg.png
│ ├── logo-unpadded.png
│ └── mono-light-bg.png
├── Palette.png
├── Icons
│ ├── flat-icon.png
│ ├── gradient-icon.png
│ ├── mono-dark-icon.png
│ ├── mono-light-icon.png
│ ├── mono-dark-icon-alt.png
│ └── mono-light-icon-alt.png
└── README.md
├── ReactiveCocoaTests
├── ReactiveCocoaTests-Bridging-Header.h
├── test-data.json
├── ReactiveCocoaTestsConfiguration.swift
├── AppKit
│ ├── Swift4TestInteroperability.swift
│ ├── NSViewSpec.swift
│ ├── AppKitReusableComponentsSpec.swift
│ ├── NSImageViewSpec.swift
│ ├── NSTableViewSpec.swift
│ ├── NSCollectionViewSpec.swift
│ ├── NSPopUpButtonSpec.swift
│ └── NSButtonSpec.swift
├── UIKit
│ ├── UIResponderSpec.swift
│ ├── UISegmentedControlSpec.swift
│ ├── UIActivityIndicatorViewSpec.swift
│ ├── UIProgressViewSpec.swift
│ ├── UIDatePickerSpec.swift
│ ├── UIImageViewSpec.swift
│ ├── UIKitReusableComponentsSpec.swift
│ ├── UITableViewSpec.swift
│ ├── UICollectionViewSpec.swift
│ ├── UIControl+EnableSendActionsForControlEvents.swift
│ ├── UISliderSpec.swift
│ ├── UIStepperSpec.swift
│ ├── UITabBarItemSpec.swift
│ ├── UISwitchSpec.swift
│ ├── UILabelSpec.swift
│ ├── UIButtonSpec.swift
│ ├── UIControlSpec.swift
│ ├── UIRefreshControlSpec.swift
│ ├── UIViewSpec.swift
│ ├── UIViewControllerSpec.swift
│ ├── UIGestureRecognizerSpec.swift
│ ├── UITextViewSpec.swift
│ └── UIBarButtonItemSpec.swift
├── Info.plist
├── DeprecationsSpec.swift
├── TestError.swift
├── Shared
│ └── NSLayoutConstraintSpec.swift
├── SwizzlingSpec.swift
├── AssociationSpec.swift
├── SignalProducerNimbleMatchers.swift
├── CocoaActionSpec.swift
└── BindingTargetSpec.swift
├── Cartfile.private
├── ReactiveCocoa
├── NSObject+ReactiveExtensionsProvider.swift
├── include
│ └── module.modulemap
├── Synchronizing.swift
├── UIKit
│ ├── UITableView.swift
│ ├── UICollectionView.swift
│ ├── UINavigationBar.swift
│ ├── UIProgressView.swift
│ ├── UIApplication.swift
│ ├── UIActivityIndicatorView.swift
│ ├── iOS
│ │ ├── UIFeedbackGenerator.swift
│ │ ├── UIImpactFeedbackGenerator.swift
│ │ ├── UISelectionFeedbackGenerator.swift
│ │ ├── UINotificationFeedbackGenerator.swift
│ │ ├── UIDatePicker.swift
│ │ ├── UIStepper.swift
│ │ ├── UISwitch.swift
│ │ ├── UISlider.swift
│ │ ├── UIRefreshControl.swift
│ │ ├── UIPickerView.swift
│ │ └── UIKeyboard.swift
│ ├── UIImageView.swift
│ ├── UIKitReusableComponents.swift
│ ├── UITabBarItem.swift
│ ├── UISegmentedControl.swift
│ ├── UIBarItem.swift
│ ├── UIResponder.swift
│ ├── UILabel.swift
│ ├── UIGestureRecognizer.swift
│ ├── UIView.swift
│ ├── UIScrollView.swift
│ ├── UIButton.swift
│ ├── UIViewController.swift
│ ├── UIBarButtonItem.swift
│ ├── UITextField.swift
│ ├── UINavigationItem.swift
│ └── UITextView.swift
├── AppKit
│ ├── NSTableView.swift
│ ├── NSImageView.swift
│ ├── NSCollectionView.swift
│ ├── NSSlider.swift
│ ├── NSView.swift
│ ├── AppKitReusableComponents.swift
│ ├── NSSegmentedControl.swift
│ ├── NSTextView.swift
│ ├── NSPopUpButton.swift
│ ├── NSTextField.swift
│ ├── NSButton.swift
│ ├── NSControl.swift
│ └── ActionProxy.swift
├── WatchKit
│ ├── WKInterfaceSeparator.swift
│ ├── WKInterfaceController.swift
│ ├── WKInterfaceDate.swift
│ ├── WKInterfaceVolumeControl.swift
│ ├── WKInterfaceObject.swift
│ ├── WKInterfaceTimer.swift
│ ├── WKInterfaceActivityRing.swift
│ ├── WKInterfaceLabel.swift
│ ├── WKInterfacePicker.swift
│ ├── WKInterfaceImage.swift
│ ├── WKInterfaceSlider.swift
│ ├── WKInterfaceMovie.swift
│ ├── WKInterfaceSwitch.swift
│ ├── WKInterfaceInlineMovie.swift
│ ├── WKInterfaceGroup.swift
│ └── WKInterfaceButton.swift
├── NSObject+ObjCRuntime.swift
├── Shared
│ └── NSLayoutConstraint.swift
├── Deprecations+Removals.swift
├── ObjC+Runtime.swift
├── Info.plist
├── ReactiveSwift+Lifetime.swift
├── ObjC+Constants.swift
├── ObjC+Selector.swift
├── CocoaTarget.swift
├── ObjC+Messages.swift
├── NSObject+BindingTarget.swift
├── CocoaAction.swift
└── DynamicProperty.swift
├── ReactiveCocoa-iOS.playground
├── contents.xcplayground
├── Sources
│ └── PlaygroundUtility.swift
└── Pages
│ └── Sandbox.xcplaygroundpage
│ └── Contents.swift
├── ReactiveCocoa-macOS.playground
├── contents.xcplayground
├── Sources
│ └── PlaygroundUtility.swift
└── Pages
│ └── Sandbox.xcplaygroundpage
│ └── Contents.swift
├── ReactiveCocoa-tvOS.playground
├── contents.xcplayground
├── Sources
│ └── PlaygroundUtility.swift
└── Pages
│ └── Sandbox.xcplaygroundpage
│ └── Contents.swift
├── ReactiveCocoaObjCTestSupport
├── include
│ └── MessageForwardingEntity.h
└── MessageForwardingEntity.m
├── Cartfile.resolved
├── ReactiveCocoaObjC
├── include
│ ├── ReactiveCocoa.h
│ └── ObjCRuntimeAliases.h
└── ObjCRuntimeAliases.m
├── .gitignore
├── .gitmodules
├── ReactiveMapKitTests
├── Info.plist
└── MKMapViewSpec.swift
├── ReactiveCocoa.xcworkspace
└── contents.xcworkspacedata
├── script
├── update-version
├── build
└── validate-playground.sh
├── ReactiveMapKit
├── Info.plist
├── MKLocalSearchRequest.swift
└── MKMapView.swift
├── ReactiveMapKit.podspec
├── LICENSE.md
├── CONTRIBUTING.md
├── Package.swift
├── ReactiveCocoa.podspec
├── Package.resolved
├── .travis.yml
├── ReactiveCocoa.xcodeproj
└── xcshareddata
│ └── xcschemes
│ └── ReactiveCocoa-watchOS.xcscheme
└── Documentation
└── DebuggingTechniques.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "ReactiveCocoa/ReactiveSwift" ~> 6.2
2 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | #### Checklist
3 | - [ ] Updated CHANGELOG.md.
4 |
--------------------------------------------------------------------------------
/Logo/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/header.png
--------------------------------------------------------------------------------
/Logo/PNG/Docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/Docs.png
--------------------------------------------------------------------------------
/Logo/PNG/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/logo.png
--------------------------------------------------------------------------------
/Logo/Palette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Palette.png
--------------------------------------------------------------------------------
/ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "MessageForwardingEntity.h"
2 |
--------------------------------------------------------------------------------
/Logo/PNG/JoinSlack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/JoinSlack.png
--------------------------------------------------------------------------------
/Logo/PNG/flat-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/flat-logo.png
--------------------------------------------------------------------------------
/ReactiveCocoaTests/test-data.json:
--------------------------------------------------------------------------------
1 | [
2 | { "item": 1 },
3 | { "item": 2 },
4 | { "item": 3 }
5 | ]
6 |
--------------------------------------------------------------------------------
/Logo/Icons/flat-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/flat-icon.png
--------------------------------------------------------------------------------
/Logo/PNG/mono-dark-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/mono-dark-bg.png
--------------------------------------------------------------------------------
/Logo/PNG/logo-unpadded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/logo-unpadded.png
--------------------------------------------------------------------------------
/Logo/PNG/mono-light-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/PNG/mono-light-bg.png
--------------------------------------------------------------------------------
/Logo/Icons/gradient-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/gradient-icon.png
--------------------------------------------------------------------------------
/Logo/Icons/mono-dark-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/mono-dark-icon.png
--------------------------------------------------------------------------------
/Logo/Icons/mono-light-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/mono-light-icon.png
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "jspahrsummers/xcconfigs" "3d9d996"
2 | github "Quick/Quick" ~> 2.0
3 | github "Quick/Nimble" ~> 8.0.1
4 |
--------------------------------------------------------------------------------
/Logo/Icons/mono-dark-icon-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/mono-dark-icon-alt.png
--------------------------------------------------------------------------------
/Logo/Icons/mono-light-icon-alt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/ReactiveCocoa/master/Logo/Icons/mono-light-icon-alt.png
--------------------------------------------------------------------------------
/ReactiveCocoa/NSObject+ReactiveExtensionsProvider.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | extension NSObject: ReactiveExtensionsProvider {}
5 |
--------------------------------------------------------------------------------
/ReactiveCocoa-iOS.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ReactiveCocoa-macOS.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ReactiveCocoa-tvOS.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ReactiveCocoaObjCTestSupport/include/MessageForwardingEntity.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface MessageForwardingEntity : NSObject
4 | @property(nonatomic) BOOL hasInvoked;
5 | @end
6 |
--------------------------------------------------------------------------------
/ReactiveCocoa/include/module.modulemap:
--------------------------------------------------------------------------------
1 | framework module ReactiveCocoa {
2 | umbrella header "ReactiveCocoa.h"
3 | private header "ObjCRuntimeAliases.h"
4 |
5 | export *
6 | module * { export * }
7 | }
8 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "Quick/Nimble" "v8.0.5"
2 | github "Quick/Quick" "v2.2.0"
3 | github "ReactiveCocoa/ReactiveSwift" "6.2.0"
4 | github "jspahrsummers/xcconfigs" "3d9d99634cae6d586e272543d527681283b33eb0"
5 |
--------------------------------------------------------------------------------
/ReactiveCocoa/Synchronizing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | internal func synchronized(_ token: AnyObject, execute: () throws -> Result) rethrows -> Result {
4 | objc_sync_enter(token)
5 | defer { objc_sync_exit(token) }
6 | return try execute()
7 | }
8 |
--------------------------------------------------------------------------------
/ReactiveCocoaObjC/include/ReactiveCocoa.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | //! Project version number for ReactiveCocoa.
4 | FOUNDATION_EXPORT double ReactiveCocoaVersionNumber;
5 |
6 | //! Project version string for ReactiveCocoa.
7 | FOUNDATION_EXPORT const unsigned char ReactiveCocoaVersionString[];
8 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UITableView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UITableView {
6 | public var reloadData: BindingTarget<()> {
7 | return makeBindingTarget { base, _ in base.reloadData() }
8 | }
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa-iOS.playground/Sources/PlaygroundUtility.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public func scopedExample(_ exampleDescription: String, _ action: () -> Void) {
4 | print("\n--- \(exampleDescription) ---\n")
5 | action()
6 | }
7 |
8 | public enum PlaygroundError: Error {
9 | case example(String)
10 | }
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa-macOS.playground/Sources/PlaygroundUtility.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public func scopedExample(_ exampleDescription: String, _ action: () -> Void) {
4 | print("\n--- \(exampleDescription) ---\n")
5 | action()
6 | }
7 |
8 | public enum PlaygroundError: Error {
9 | case example(String)
10 | }
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa-tvOS.playground/Sources/PlaygroundUtility.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public func scopedExample(_ exampleDescription: String, _ action: () -> Void) {
4 | print("\n--- \(exampleDescription) ---\n")
5 | action()
6 | }
7 |
8 | public enum PlaygroundError: Error {
9 | case example(String)
10 | }
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UICollectionView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UICollectionView {
6 | public var reloadData: BindingTarget<()> {
7 | return makeBindingTarget { base, _ in base.reloadData() }
8 | }
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSTableView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSTableView {
6 | public var reloadData: BindingTarget<()> {
7 | return makeBindingTarget { base, _ in base.reloadData() }
8 | }
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceSeparator.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceSeparator {
6 | /// Sets the color of the separator.
7 | public var color: BindingTarget {
8 | return makeBindingTarget { $0.setColor($1) }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceController.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceController {
6 | /// Sets the title of the controller.
7 | public var title: BindingTarget {
8 | return makeBindingTarget { $0.setTitle($1) }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceDate.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceDate {
6 | /// Sets the color of the text of the date.
7 | public var textColor: BindingTarget {
8 | return makeBindingTarget { $0.setTextColor($1) }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSImageView {
6 | /// Sets the currently displayed image
7 | public var image: BindingTarget {
8 | return makeBindingTarget { $0.image = $1 }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSCollectionView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSCollectionView {
6 | @available(macOS 10.11, *)
7 | public var reloadData: BindingTarget<()> {
8 | return makeBindingTarget { base, _ in base.reloadData() }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UINavigationBar.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UINavigationBar {
6 | /// Sets the barTintColor of the navigation bar.
7 | public var barTintColor: BindingTarget {
8 | return makeBindingTarget { $0.barTintColor = $1 }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIProgressView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIProgressView {
6 | /// Sets the relative progress to be reflected by the progress view.
7 | public var progress: BindingTarget {
8 | return makeBindingTarget { $0.progress = $1 }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/ReactiveCocoaTestsConfiguration.swift:
--------------------------------------------------------------------------------
1 | import Quick
2 | #if canImport(UIKit)
3 | import UIKit
4 | #endif
5 |
6 | class ReactiveCocoaTestsConfiguration: QuickConfiguration {
7 | override class func configure(_ configuration: Configuration) {
8 | #if canImport(UIKit)
9 | configuration.beforeSuite {
10 | UIControl._initialize()
11 | }
12 | #endif
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSSlider.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSSlider {
6 |
7 | // Provided for cross-platform compatibility
8 |
9 | public var value: BindingTarget { return floatValue }
10 | public var values: Signal { return floatValues }
11 | }
12 | #endif
13 |
--------------------------------------------------------------------------------
/ReactiveCocoaObjC/ObjCRuntimeAliases.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | const IMP _rac_objc_msgForward = _objc_msgForward;
5 |
6 | void _rac_objc_setAssociatedObject(const void* object, const void* key, id value, objc_AssociationPolicy policy) {
7 | __unsafe_unretained id obj = (__bridge typeof(obj)) object;
8 | objc_setAssociatedObject(obj, key, value, policy);
9 | }
10 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIApplication.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIApplication {
6 | /// Sets the number as the badge of the app icon in Springboard.
7 | public var applicationIconBadgeNumber: BindingTarget {
8 | return makeBindingTarget({ $0.applicationIconBadgeNumber = $1 })
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/NSObject+ObjCRuntime.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension NSObject {
4 | /// The class of the instance reported by the ObjC `-class:` message.
5 | ///
6 | /// - note: `type(of:)` might return the runtime subclass, while this property
7 | /// always returns the original class.
8 | @nonobjc internal var objcClass: AnyClass {
9 | return (self as AnyObject).objcClass
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIActivityIndicatorView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIActivityIndicatorView {
6 | /// Sets whether the activity indicator should be animating.
7 | public var isAnimating: BindingTarget {
8 | return makeBindingTarget { $1 ? $0.startAnimating() : $0.stopAnimating() }
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceVolumeControl.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | @available(watchOSApplicationExtension 5.0, *)
6 | extension Reactive where Base: WKInterfaceVolumeControl {
7 | /// Sets the tint color of the volume control.
8 | public var tintColor: BindingTarget {
9 | return makeBindingTarget { $0.setTintColor($1) }
10 | }
11 | }
12 | #endif
13 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIFeedbackGenerator.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | @available(iOS 10.0, *)
6 | extension Reactive where Base: UIFeedbackGenerator {
7 | /// Prepares the feedback generator.
8 | public var prepare: BindingTarget<()> {
9 | return makeBindingTarget { generator, _ in
10 | generator.prepare()
11 | }
12 | }
13 | }
14 | #endif
15 |
--------------------------------------------------------------------------------
/ReactiveCocoa/Shared/NSLayoutConstraint.swift:
--------------------------------------------------------------------------------
1 | #if !os(watchOS)
2 | import ReactiveSwift
3 |
4 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
5 | import AppKit
6 | #else
7 | import UIKit
8 | #endif
9 |
10 | extension Reactive where Base: NSLayoutConstraint {
11 |
12 | /// Sets the constant.
13 | public var constant: BindingTarget {
14 | return makeBindingTarget { $0.constant = $1 }
15 | }
16 |
17 | }
18 | #endif
19 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIImpactFeedbackGenerator.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | @available(iOS 10.0, *)
6 | extension Reactive where Base: UIImpactFeedbackGenerator {
7 | /// Triggers the feedback.
8 | public var impactOccurred: BindingTarget<()> {
9 | return makeBindingTarget { generator, _ in
10 | generator.impactOccurred()
11 | }
12 | }
13 | }
14 | #endif
15 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UISelectionFeedbackGenerator.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | @available(iOS 10.0, *)
6 | extension Reactive where Base: UISelectionFeedbackGenerator {
7 | /// Triggers the feedback.
8 | public var selectionChanged: BindingTarget<()> {
9 | return makeBindingTarget { generator, _ in
10 | generator.selectionChanged()
11 | }
12 | }
13 | }
14 | #endif
15 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UINotificationFeedbackGenerator.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | @available(iOS 10.0, *)
6 | extension Reactive where Base: UINotificationFeedbackGenerator {
7 | /// Triggers the feedback.
8 | public var notificationOccurred: BindingTarget {
9 | return makeBindingTarget { $0.notificationOccurred($1) }
10 | }
11 | }
12 | #endif
13 |
--------------------------------------------------------------------------------
/ReactiveCocoaObjC/include/ObjCRuntimeAliases.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | NS_ASSUME_NONNULL_BEGIN
5 |
6 | extern const IMP _rac_objc_msgForward;
7 |
8 | /// A trampoline of `objc_setAssociatedObject` that is made to circumvent the
9 | /// reference counting calls in the imported version in Swift.
10 | void _rac_objc_setAssociatedObject(const void* object, const void* key, id _Nullable value, objc_AssociationPolicy policy);
11 |
12 | NS_ASSUME_NONNULL_END
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | build/*
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | *.xcworkspace
12 | !default.xcworkspace
13 | xcuserdata
14 | profile
15 | *.moved-aside
16 | PlaygroundUtility.remap
17 | *.xctimeline
18 | *.xcscmblueprint
19 | *.o
20 |
21 | # SwiftPM
22 | .build
23 | Packages
24 |
25 | # Carthage
26 | Carthage/Build
27 |
28 | # macOS
29 | .DS_Store
30 |
31 | # Jazzy
32 | docs
33 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceObject.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceObject {
6 | /// Sets the alpha value of the object.
7 | public var alpha: BindingTarget {
8 | return makeBindingTarget { $0.setAlpha($1) }
9 | }
10 |
11 | /// Sets whether the object is hidden.
12 | public var isHidden: BindingTarget {
13 | return makeBindingTarget { $0.setHidden($1) }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceTimer.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceTimer {
6 | /// Sets the start date of the timer.
7 | public var date: BindingTarget {
8 | return makeBindingTarget { $0.setDate($1) }
9 | }
10 |
11 | /// Sets the color of the text of the timer.
12 | public var textColor: BindingTarget {
13 | return makeBindingTarget { $0.setTextColor($1) }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSView {
6 | /// Sets the visibility of the view.
7 | public var isHidden: BindingTarget {
8 | return makeBindingTarget { $0.isHidden = $1 }
9 | }
10 |
11 | /// Sets the alpha value of the view.
12 | public var alphaValue: BindingTarget {
13 | return makeBindingTarget { $0.alphaValue = $1 }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIDatePicker.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIDatePicker {
6 | /// Sets the date of the date picker.
7 | public var date: BindingTarget {
8 | return makeBindingTarget { $0.date = $1 }
9 | }
10 |
11 | /// A signal of dates emitted by the date picker.
12 | public var dates: Signal {
13 | return mapControlEvents(.valueChanged) { $0.date }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIImageView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIImageView {
6 | /// Sets the image of the image view.
7 | public var image: BindingTarget {
8 | return makeBindingTarget { $0.image = $1 }
9 | }
10 |
11 | /// Sets the image of the image view for its highlighted state.
12 | public var highlightedImage: BindingTarget {
13 | return makeBindingTarget { $0.highlightedImage = $1 }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Carthage/Checkouts/Nimble"]
2 | path = Carthage/Checkouts/Nimble
3 | url = https://github.com/Quick/Nimble.git
4 | [submodule "Carthage/Checkouts/Quick"]
5 | path = Carthage/Checkouts/Quick
6 | url = https://github.com/Quick/Quick.git
7 | [submodule "Carthage/Checkouts/xcconfigs"]
8 | path = Carthage/Checkouts/xcconfigs
9 | url = https://github.com/jspahrsummers/xcconfigs.git
10 | [submodule "Carthage/Checkouts/ReactiveSwift"]
11 | path = Carthage/Checkouts/ReactiveSwift
12 | url = https://github.com/ReactiveCocoa/ReactiveSwift.git
13 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIKitReusableComponents.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import UIKit
3 | import ReactiveSwift
4 |
5 | @objc public protocol Reusable: class {
6 | func prepareForReuse()
7 | }
8 |
9 | extension Reactive where Base: NSObject, Base: Reusable {
10 | public var prepareForReuse: Signal<(), Never> {
11 | return trigger(for: #selector(base.prepareForReuse))
12 | }
13 | }
14 |
15 | extension UITableViewCell: Reusable {}
16 | extension UITableViewHeaderFooterView: Reusable {}
17 | extension UICollectionReusableView: Reusable {}
18 | #endif
19 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceActivityRing.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 | import HealthKit
5 |
6 | @available(watchOSApplicationExtension 2.2, *)
7 | extension Reactive where Base: WKInterfaceActivityRing {
8 | /// Sets the summary of the activity ring.
9 | ///
10 | /// - Parameter animated: Whether updates are animated.
11 | public func activitySummary(animated: Bool) -> BindingTarget {
12 | return makeBindingTarget { $0.setActivitySummary($1, animated: animated) }
13 | }
14 | }
15 | #endif
16 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/AppKitReusableComponents.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSView {
6 | public var prepareForReuse: Signal<(), Never> {
7 | return trigger(for: #selector(base.prepareForReuse))
8 | }
9 | }
10 |
11 | extension Reactive where Base: NSObject, Base: NSCollectionViewElement {
12 | @available(macOS 10.11, *)
13 | public var prepareForReuse: Signal<(), Never> {
14 | return trigger(for: #selector(base.prepareForReuse))
15 | }
16 | }
17 | #endif
18 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UITabBarItem.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UITabBarItem {
6 | /// Sets the badge value of the tab bar item.
7 | public var badgeValue: BindingTarget {
8 | return makeBindingTarget { $0.badgeValue = $1 }
9 | }
10 |
11 |
12 | /// Sets the badge color of the tab bar item.
13 | @available(iOS 10, *)
14 | @available(tvOS 10, *)
15 | public var badgeColor: BindingTarget {
16 | return makeBindingTarget { $0.badgeColor = $1 }
17 | }
18 | }
19 | #endif
20 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/Swift4TestInteroperability.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 |
4 | #if swift(>=4.0)
5 | internal typealias RACNSControlState = NSControl.StateValue
6 | internal let RACNSOnState = NSControl.StateValue.on
7 | internal let RACNSOffState = NSControl.StateValue.off
8 | internal let RACNSMixedState = NSControl.StateValue.mixed
9 | #else
10 | internal typealias RACNSControlState = Int
11 | internal let RACNSOnState = NSOnState
12 | internal let RACNSOffState = NSOffState
13 | internal let RACNSMixedState = NSMixedState
14 | #endif
15 | #endif
16 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UISegmentedControl.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UISegmentedControl {
6 | /// Changes the selected segment of the segmented control.
7 | public var selectedSegmentIndex: BindingTarget {
8 | return makeBindingTarget { $0.selectedSegmentIndex = $1 }
9 | }
10 |
11 | /// A signal of indexes of selections emitted by the segmented control.
12 | public var selectedSegmentIndexes: Signal {
13 | return mapControlEvents(.valueChanged) { $0.selectedSegmentIndex }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIBarItem.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIBarItem {
6 | /// Sets whether the bar item is enabled.
7 | public var isEnabled: BindingTarget {
8 | return makeBindingTarget { $0.isEnabled = $1 }
9 | }
10 |
11 | /// Sets image of bar item.
12 | public var image: BindingTarget {
13 | return makeBindingTarget { $0.image = $1 }
14 | }
15 |
16 | /// Sets the title of bar item.
17 | public var title: BindingTarget {
18 | return makeBindingTarget { $0.title = $1 }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIResponder.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIResponder {
6 | /// Asks UIKit to make this object the first responder in its window.
7 | public var becomeFirstResponder: BindingTarget<()> {
8 | return makeBindingTarget { base, _ in base.becomeFirstResponder() }
9 | }
10 |
11 | /// Notifies this object that it has been asked to relinquish its status as first responder in its window.
12 | public var resignFirstResponder: BindingTarget<()> {
13 | return makeBindingTarget { base, _ in base.resignFirstResponder() }
14 | }
15 | }
16 | #endif
17 |
--------------------------------------------------------------------------------
/Logo/README.md:
--------------------------------------------------------------------------------
1 | This folder contains brand assets.
2 |
3 | # Logo
4 |
5 | Four horizontal variations that include both the mark and the logotype. When
6 | using the logo in contexts where it's surrounded by other elements, leave
7 | a padding of about 10% of its height on each side.
8 |
9 | # Icon
10 |
11 | Four icon variations to be used on social media and other contexts where the
12 | horizontal logo wouldn't fit.
13 |
14 | # Colors
15 |
16 | Primary color: `#88CD79`
17 | Secondary color: `#41AD71`
18 | Tertiary color: `#4F6B97`
19 |
20 | 
21 |
22 | # Type
23 |
24 | Avenir Next, designed by Adrian Frutiger and Akira Kobayashi for Linotype.
25 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UILabel.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UILabel {
6 | /// Sets the text of the label.
7 | public var text: BindingTarget {
8 | return makeBindingTarget { $0.text = $1 }
9 | }
10 |
11 | /// Sets the attributed text of the label.
12 | public var attributedText: BindingTarget {
13 | return makeBindingTarget { $0.attributedText = $1 }
14 | }
15 |
16 | /// Sets the color of the text of the label.
17 | public var textColor: BindingTarget {
18 | return makeBindingTarget { $0.textColor = $1 }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceLabel.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceLabel {
6 | /// Sets the text of the label.
7 | public var text: BindingTarget {
8 | return makeBindingTarget { $0.setText($1) }
9 | }
10 |
11 | /// Sets the attributed text of the label.
12 | public var attributedText: BindingTarget {
13 | return makeBindingTarget { $0.setAttributedText($1) }
14 | }
15 |
16 | /// Sets the color of the text of the label.
17 | public var textColor: BindingTarget {
18 | return makeBindingTarget { $0.setTextColor($1) }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfacePicker.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfacePicker {
6 | /// Sets the list of items of the picker.
7 | public var items: BindingTarget<[WKPickerItem]?> {
8 | return makeBindingTarget { $0.setItems($1) }
9 | }
10 |
11 | /// Sets the selected item index of the picker.
12 | public var selectedItemIndex: BindingTarget {
13 | return makeBindingTarget { $0.setSelectedItemIndex($1) }
14 | }
15 |
16 | /// Sets whether the picker is enabled.
17 | public var isEnabled: BindingTarget {
18 | return makeBindingTarget { $0.setEnabled($1) }
19 | }
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/ReactiveCocoa/Deprecations+Removals.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | extension Action {
5 | @available(*, unavailable, message:"Use the `CocoaAction` initializers instead.")
6 | public var unsafeCocoaAction: CocoaAction { fatalError() }
7 | }
8 |
9 | extension Reactive where Base: NSObject {
10 | @available(*, deprecated, renamed: "producer(forKeyPath:)")
11 | public func values(forKeyPath keyPath: String) -> SignalProducer {
12 | return producer(forKeyPath: keyPath)
13 | }
14 | }
15 |
16 | #if os(watchOS)
17 | import WatchKit
18 | extension Reactive where Base: WKInterfaceButton {
19 | @available(*, deprecated, renamed: "title")
20 | public var text: BindingTarget {
21 | return title
22 | }
23 | }
24 | #endif
25 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIResponderSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UIResponderSpec: QuickSpec {
9 | override func spec() {
10 | it("should become and resign first responder") {
11 | let window = UIWindow(frame: .zero)
12 | let textField = UITextField(frame: .zero)
13 | window.addSubview(textField)
14 |
15 | expect(textField.isFirstResponder).to(beFalse())
16 | textField.reactive.becomeFirstResponder <~ SignalProducer(value: ())
17 | expect(textField.isFirstResponder).to(beTrue())
18 | textField.reactive.resignFirstResponder <~ SignalProducer(value: ())
19 | expect(textField.isFirstResponder).to(beFalse())
20 | }
21 | }
22 | }
23 | #endif
24 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceImage.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceImage {
6 | /// Sets the image.
7 | public var image: BindingTarget {
8 | return makeBindingTarget { $0.setImage($1) }
9 | }
10 |
11 | /// Sets the data of the image.
12 | public var imageData: BindingTarget {
13 | return makeBindingTarget { $0.setImageData($1) }
14 | }
15 |
16 | /// Sets the name of the image.
17 | public var imageNamed: BindingTarget {
18 | return makeBindingTarget { $0.setImageNamed($1) }
19 | }
20 |
21 | /// Sets the tint color of the template image.
22 | public var tintColor: BindingTarget {
23 | return makeBindingTarget { $0.setTintColor($1) }
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/ReactiveMapKitTests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ReactiveCocoa/ObjC+Runtime.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Search in `class` for any method that matches the supplied selector without
4 | /// propagating to the ancestors.
5 | ///
6 | /// - parameters:
7 | /// - class: The class to search the method in.
8 | /// - selector: The selector of the method.
9 | ///
10 | /// - returns: The matching method, or `nil` if none is found.
11 | internal func class_getImmediateMethod(_ `class`: AnyClass, _ selector: Selector) -> Method? {
12 | var total: UInt32 = 0
13 |
14 | if let methods = class_copyMethodList(`class`, &total) {
15 | defer { free(methods) }
16 |
17 | for index in 0 ..< Int(total) {
18 | let method = methods[index]
19 |
20 | if method_getName(method) == selector {
21 | return method
22 | }
23 | }
24 | }
25 |
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceSlider.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceSlider {
6 | /// Sets the value of the slider.
7 | public var value: BindingTarget {
8 | return makeBindingTarget { $0.setValue($1) }
9 | }
10 |
11 | /// Sets the number of steps of the slider.
12 | public var numberOfSteps: BindingTarget {
13 | return makeBindingTarget { $0.setNumberOfSteps($1) }
14 | }
15 |
16 | /// Sets the color of the slider.
17 | public var color: BindingTarget {
18 | return makeBindingTarget { $0.setColor($1) }
19 | }
20 |
21 | /// Sets whether the slider is enabled.
22 | public var isEnabled: BindingTarget {
23 | return makeBindingTarget { $0.setEnabled($1) }
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceMovie.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceMovie {
6 | /// Sets the url of the movie.
7 | public var movieURL: BindingTarget {
8 | return makeBindingTarget { $0.setMovieURL($1) }
9 | }
10 |
11 | /// Sets the video gravity of the movie.
12 | public var videoGravity: BindingTarget {
13 | return makeBindingTarget { $0.setVideoGravity($1) }
14 | }
15 |
16 | /// Sets the poster image of the movie.
17 | public var posterImage: BindingTarget {
18 | return makeBindingTarget { $0.setPosterImage($1) }
19 | }
20 |
21 | /// Whether the movie loops.
22 | public var loops: BindingTarget {
23 | return makeBindingTarget { $0.setLoops($1) }
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/ReactiveCocoa-iOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | > ## IMPORTANT: To use `ReactiveCocoa-iOS.playground`, please:
3 |
4 | 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory:
5 | - `git submodule update --init --recursive`
6 | **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed
7 | - `carthage checkout --no-use-binaries`
8 | 1. Open `ReactiveCocoa.xcworkspace`
9 | 1. Build `ReactiveSwift-iOS` scheme
10 | 1. Build `ReactiveCocoa-iOS` scheme
11 | 1. Finally open the `ReactiveCocoa-iOS.playground`
12 | 1. Choose `View > Show Debug Area`
13 | */
14 |
15 | import ReactiveCocoa
16 | import ReactiveSwift
17 | import UIKit
18 |
19 | /*:
20 | ## Sandbox
21 |
22 | A place where you can build your sand castles 🏖.
23 | */
24 |
--------------------------------------------------------------------------------
/ReactiveCocoa-tvOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | > ## IMPORTANT: To use `ReactiveCocoa-tvOS.playground`, please:
3 |
4 | 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory:
5 | - `git submodule update --init --recursive`
6 | **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed
7 | - `carthage checkout --no-use-binaries`
8 | 1. Open `ReactiveCocoa.xcworkspace`
9 | 1. Build `ReactiveSwift-tvOS` scheme
10 | 1. Build `ReactiveCocoa-tvOS` scheme
11 | 1. Finally open the `ReactiveCocoa-tvOS.playground`
12 | 1. Choose `View > Show Debug Area`
13 | */
14 |
15 | import ReactiveCocoa
16 | import ReactiveSwift
17 | import UIKit
18 |
19 | /*:
20 | ## Sandbox
21 |
22 | A place where you can build your sand castles 🏖.
23 | */
24 |
--------------------------------------------------------------------------------
/ReactiveCocoa.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
18 |
19 |
21 |
22 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ReactiveCocoa-macOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | /*:
2 | > ## IMPORTANT: To use `ReactiveCocoa-macOS.playground`, please:
3 |
4 | 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory:
5 | - `git submodule update --init --recursive`
6 | **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed
7 | - `carthage checkout --no-use-binaries`
8 | 1. Open `ReactiveCocoa.xcworkspace`
9 | 1. Build `ReactiveSwift-macOS` scheme
10 | 1. Build `ReactiveCocoa-macOS` scheme
11 | 1. Finally open the `ReactiveCocoa-macOS.playground`
12 | 1. Choose `View > Show Debug Area`
13 | */
14 |
15 | import ReactiveCocoa
16 | import ReactiveSwift
17 | import AppKit
18 |
19 | /*:
20 | ## Sandbox
21 |
22 | A place where you can build your sand castles 🏖.
23 | */
24 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 6.1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/script/update-version:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ -z "$1" ]]; then
4 | echo "Please specify a version tag."
5 | exit
6 | fi
7 |
8 | PRERELEASE_STRIPPED=$(echo "$1" | perl -0777 -ne '/([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?/ and print "$1.$2.$3"')
9 |
10 | if [[ -z "$PRERELEASE_STRIPPED" ]]; then
11 | echo "The version tag is not semver compliant."
12 | exit
13 | fi
14 |
15 | CURRENT_TAG=$(perl -0777 -ne '/s.version([\s]+)=([\s]+)"(.+)"/ and print $3 and last' *.podspec)
16 | echo "Current tag: $CURRENT_TAG"
17 |
18 | perl -0777 -i -pe 's/s.version([\s]+)=([\s]+)"'${CURRENT_TAG}'"/s.version$1=$2"'${1}'"/' *.podspec
19 | perl -0777 -i -pe 's/g>'${CURRENT_TAG}'<\/str/g>'${PRERELEASE_STRIPPED}'<\/str/' */Info.plist
20 | perl -0777 -i -pe 's/g>'${CURRENT_TAG}'<\/str/g>'${PRERELEASE_STRIPPED}'<\/str/' */*/Info.plist
21 | sed -i '' '3i\
22 | \
23 | # '${1} CHANGELOG.md
24 |
25 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIStepper.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import UIKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: UIStepper {
6 |
7 | /// Sets the stepper's value.
8 | public var value: BindingTarget {
9 | return makeBindingTarget { $0.value = $1 }
10 | }
11 |
12 | /// Sets stepper's minimum value.
13 | public var minimumValue: BindingTarget {
14 | return makeBindingTarget { $0.minimumValue = $1 }
15 | }
16 |
17 | /// Sets stepper's maximum value.
18 | public var maximumValue: BindingTarget {
19 | return makeBindingTarget { $0.maximumValue = $1 }
20 | }
21 |
22 | /// A signal of double values emitted by the stepper upon each user's
23 | /// interaction.
24 | public var values: Signal {
25 | return mapControlEvents(.valueChanged) { $0.value }
26 | }
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/ReactiveMapKit/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 | FMWK
17 | CFBundleShortVersionString
18 | 6.0.1
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIGestureRecognizer {
6 | /// Create a signal which sends a `next` event for each gesture event
7 | ///
8 | /// - returns: A trigger signal.
9 | public var stateChanged: Signal {
10 | return Signal { observer, signalLifetime in
11 | let receiver = CocoaTarget(observer) { gestureRecognizer in
12 | return gestureRecognizer as! Base
13 | }
14 | base.addTarget(receiver, action: #selector(receiver.invoke))
15 |
16 | let disposable = lifetime.ended.observeCompleted(observer.sendCompleted)
17 |
18 | signalLifetime.observeEnded { [weak base] in
19 | disposable?.dispose()
20 | base?.removeTarget(receiver, action: #selector(receiver.invoke))
21 | }
22 | }
23 | }
24 | }
25 | #endif
26 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UISegmentedControlSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UISegmentedControlSpec: QuickSpec {
9 | override func spec() {
10 | it("should accept changes from bindings to its selected segment index") {
11 | let s = UISegmentedControl(items: ["0", "1", "2"])
12 | s.selectedSegmentIndex = UISegmentedControl.noSegment
13 | expect(s.numberOfSegments) == 3
14 |
15 | let (pipeSignal, observer) = Signal.pipe()
16 | s.reactive.selectedSegmentIndex <~ SignalProducer(pipeSignal)
17 |
18 | expect(s.selectedSegmentIndex) == UISegmentedControl.noSegment
19 |
20 | observer.send(value: 1)
21 | expect(s.selectedSegmentIndex) == 1
22 |
23 | observer.send(value: 2)
24 | expect(s.selectedSegmentIndex) == 2
25 | }
26 | }
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/ReactiveMapKit/MKLocalSearchRequest.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 | import ReactiveCocoa
3 | import MapKit
4 |
5 | private let defaultLocalSearchError = NSError(domain: "org.reactivecocoa.ReactiveCocoa.Reactivity.MKLocalSearchRequest",
6 | code: 1,
7 | userInfo: nil)
8 | @available(tvOS 9.2, *)
9 | extension Reactive where Base: MKLocalSearch.Request {
10 | /// A SignalProducer which performs an `MKLocalSearch`.
11 | public var search: SignalProducer {
12 | return SignalProducer {[base = self.base] observer, lifetime in
13 | let search = MKLocalSearch(request: base)
14 | search.start { response, error in
15 | if let response = response {
16 | observer.send(value: response)
17 | observer.sendCompleted()
18 | } else {
19 | observer.send(error: error ?? defaultLocalSearchError)
20 | }
21 | }
22 | lifetime.observeEnded(search.cancel)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/DeprecationsSpec.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveCocoa
3 | import ReactiveSwift
4 | import Quick
5 | import Nimble
6 |
7 | class DeprecationsSpec: QuickSpec {
8 | override func spec() {
9 | describe("NSObject.reactive.values(forKeyPath:)") {
10 | class TestKVOObject: NSObject {
11 | @objc dynamic var value: Int = 0
12 | }
13 |
14 | it("should observe the initial value and changes for the key path") {
15 | let object = TestKVOObject()
16 | var values: [Int] = []
17 |
18 | object.reactive.producer(forKeyPath: #keyPath(TestKVOObject.value)).startWithValues { value in
19 | values.append(value as! Int)
20 | }
21 |
22 | expect(values) == [0]
23 |
24 | object.value = 1
25 | expect(values) == [0, 1]
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSSegmentedControl.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSSegmentedControl {
6 | /// Changes the selected segment of the segmented control.
7 | public var selectedSegment: BindingTarget {
8 | return makeBindingTarget { $0.selectedSegment = $1 }
9 | }
10 |
11 | /// A signal of indexes of selections emitted by the segmented control.
12 | public var selectedSegments: Signal {
13 | return proxy.invoked.map { $0.selectedSegment }
14 | }
15 |
16 | /// The below are provided for cross-platform compatibility
17 |
18 | /// Changes the selected segment of the segmented control.
19 | public var selectedSegmentIndex: BindingTarget { return selectedSegment }
20 | /// A signal of indexes of selections emitted by the segmented control.
21 | public var selectedSegmentIndexes: Signal { return selectedSegments }
22 | }
23 | #endif
24 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UISwitch.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UISwitch {
6 | /// The action to be triggered when the switch is changed. It also controls
7 | /// the enabled state of the switch
8 | public var toggled: CocoaAction? {
9 | get {
10 | return associatedAction.withValue { info in
11 | return info.flatMap { info in
12 | return info.controlEvents == .valueChanged ? info.action : nil
13 | }
14 | }
15 | }
16 |
17 | nonmutating set {
18 | setAction(newValue, for: .valueChanged)
19 | }
20 | }
21 | /// Sets the on-off state of the switch.
22 | public var isOn: BindingTarget {
23 | return makeBindingTarget { $0.isOn = $1 }
24 | }
25 |
26 | /// A signal of on-off states in `Bool` emitted by the switch.
27 | public var isOnValues: Signal {
28 | return mapControlEvents(.valueChanged) { $0.isOn }
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceSwitch.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceSwitch {
6 | /// Sets the title of the switch.
7 | public var title: BindingTarget {
8 | return makeBindingTarget { $0.setTitle($1) }
9 | }
10 |
11 | /// Sets the attributed title of the switch.
12 | public var attributedTitle: BindingTarget {
13 | return makeBindingTarget { $0.setAttributedTitle($1) }
14 | }
15 |
16 | /// Sets the color of the switch.
17 | public var color: BindingTarget {
18 | return makeBindingTarget { $0.setColor($1) }
19 | }
20 |
21 | /// Sets whether the switch is on.
22 | public var isOn: BindingTarget {
23 | return makeBindingTarget { $0.setOn($1) }
24 | }
25 |
26 | /// Sets whether the switch is enabled.
27 | public var isEnabled: BindingTarget {
28 | return makeBindingTarget { $0.setEnabled($1) }
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIView {
6 | /// Sets the alpha value of the view.
7 | public var alpha: BindingTarget {
8 | return makeBindingTarget { $0.alpha = $1 }
9 | }
10 |
11 | /// Sets whether the view is hidden.
12 | public var isHidden: BindingTarget {
13 | return makeBindingTarget { $0.isHidden = $1 }
14 | }
15 |
16 | /// Sets whether the view accepts user interactions.
17 | public var isUserInteractionEnabled: BindingTarget {
18 | return makeBindingTarget { $0.isUserInteractionEnabled = $1 }
19 | }
20 |
21 | /// Sets the background color of the view.
22 | public var backgroundColor: BindingTarget {
23 | return makeBindingTarget { $0.backgroundColor = $1 }
24 | }
25 |
26 | /// Sets the tintColor of the view
27 | public var tintColor: BindingTarget {
28 | return makeBindingTarget { $0.tintColor = $1 }
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UISlider.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import UIKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: UISlider {
6 |
7 | /// Sets slider's value.
8 | public var value: BindingTarget {
9 | return makeBindingTarget { $0.value = $1 }
10 | }
11 |
12 | /// Sets slider's minimum value.
13 | public var minimumValue: BindingTarget {
14 | return makeBindingTarget { $0.minimumValue = $1 }
15 | }
16 |
17 | /// Sets slider's maximum value.
18 | public var maximumValue: BindingTarget {
19 | return makeBindingTarget { $0.maximumValue = $1 }
20 | }
21 |
22 | /// A signal of float values emitted by the slider while being dragged by
23 | /// the user.
24 | ///
25 | /// - note: If slider's `isContinuous` property is `false` then values are
26 | /// sent only when user releases the slider.
27 | public var values: Signal {
28 | return mapControlEvents(.valueChanged) { $0.value }
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/NSViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import Quick
3 | import Nimble
4 | import ReactiveSwift
5 | import ReactiveCocoa
6 | import AppKit
7 |
8 | class NSViewSpec: QuickSpec {
9 | override func spec() {
10 | describe("NSView") {
11 | var view: NSView!
12 |
13 | beforeEach {
14 | view = NSView()
15 | }
16 |
17 | it("should allow binding of `isHidden` property") {
18 | let (hSignal, hSink) = Signal.pipe()
19 | expect(view.isHidden) == false
20 |
21 | view.reactive.isHidden <~ hSignal
22 | hSink.send(value: true)
23 |
24 | expect(view.isHidden) == true
25 | }
26 |
27 | it("should allow binding of `alphaValue` property") {
28 | let (avSignal, avSink) = Signal.pipe()
29 | expect(view.alphaValue) == 1.0
30 |
31 | view.reactive.alphaValue <~ avSignal
32 | avSink.send(value: 0.5)
33 |
34 | expect(view.alphaValue) == 0.5
35 | }
36 | }
37 | }
38 | }
39 | #endif
40 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/TestError.swift:
--------------------------------------------------------------------------------
1 | import ReactiveCocoa
2 | import ReactiveSwift
3 |
4 | internal enum TestError: Int {
5 | case `default` = 0
6 | case error1 = 1
7 | case error2 = 2
8 | }
9 |
10 | extension TestError: Error {
11 | }
12 |
13 |
14 | internal extension SignalProducer {
15 | /// Halts if an error is emitted in the receiver signal.
16 | /// This is useful in tests to be able to just use `startWithNext`
17 | /// in cases where we know that an error won't be emitted.
18 | func assumeNoErrors() -> SignalProducer {
19 | return lift { $0.assumeNoErrors() }
20 | }
21 | }
22 |
23 | internal extension Signal {
24 | /// Halts if an error is emitted in the receiver signal.
25 | /// This is useful in tests to be able to just use `startWithNext`
26 | /// in cases where we know that an error won't be emitted.
27 | func assumeNoErrors() -> Signal {
28 | return mapError { error in
29 | fatalError("Unexpected error: \(error)")
30 |
31 | ()
32 | }
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/ReactiveMapKit/MKMapView.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 | import ReactiveCocoa
3 | import MapKit
4 |
5 | @available(tvOS 9.2, *)
6 | extension Reactive where Base: MKMapView {
7 |
8 | /// Sets the map type.
9 | public var mapType: BindingTarget {
10 | return makeBindingTarget { $0.mapType = $1 }
11 | }
12 |
13 | /// Sets if zoom is enabled for map.
14 | public var isZoomEnabled: BindingTarget {
15 | return makeBindingTarget { $0.isZoomEnabled = $1 }
16 | }
17 |
18 | /// Sets if scroll is enabled for map.
19 | public var isScrollEnabled: BindingTarget {
20 | return makeBindingTarget { $0.isScrollEnabled = $1 }
21 | }
22 |
23 | #if !os(tvOS)
24 | /// Sets if pitch is enabled for map.
25 | public var isPitchEnabled: BindingTarget {
26 | return makeBindingTarget { $0.isPitchEnabled = $1 }
27 | }
28 |
29 | /// Sets if rotation is enabled for map.
30 | public var isRotateEnabled: BindingTarget {
31 | return makeBindingTarget { $0.isRotateEnabled = $1 }
32 | }
33 | #endif
34 | }
35 |
--------------------------------------------------------------------------------
/ReactiveCocoa/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 | 10.2.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2014 GitHub. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceInlineMovie.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | @available(watchOSApplicationExtension 3.0, *)
6 | extension Reactive where Base: WKInterfaceInlineMovie {
7 | /// Sets the url of the movie.
8 | public var movieURL: BindingTarget {
9 | return makeBindingTarget { $0.setMovieURL($1) }
10 | }
11 |
12 | /// Sets the video gravity of the movie.
13 | public var videoGravity: BindingTarget {
14 | return makeBindingTarget { $0.setVideoGravity($1) }
15 | }
16 |
17 | /// Sets the poster image of the movie.
18 | public var posterImage: BindingTarget {
19 | return makeBindingTarget { $0.setPosterImage($1) }
20 | }
21 |
22 | /// Whether the movie loops.
23 | public var loops: BindingTarget {
24 | return makeBindingTarget { $0.setLoops($1) }
25 | }
26 |
27 | /// Whether the movie autoplays.
28 | public var autoplays: BindingTarget {
29 | return makeBindingTarget { $0.setAutoplays($1) }
30 | }
31 | }
32 | #endif
33 |
--------------------------------------------------------------------------------
/ReactiveCocoaObjCTestSupport/MessageForwardingEntity.m:
--------------------------------------------------------------------------------
1 | #import "MessageForwardingEntity.h"
2 | #pragma GCC diagnostic ignored "-Wundeclared-selector"
3 |
4 | @implementation MessageForwardingEntity
5 |
6 | - (instancetype) init {
7 | if (self = [super init]) {
8 | self.hasInvoked = NO;
9 | }
10 | return self;
11 | }
12 |
13 | - (BOOL) respondsToSelector:(SEL)aSelector {
14 | if (aSelector == @selector(_rac_test_forwarding)) {
15 | return YES;
16 | }
17 | return [super respondsToSelector:aSelector];
18 | }
19 |
20 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
21 | if (aSelector == @selector(_rac_test_forwarding)) {
22 | return [NSMethodSignature signatureWithObjCTypes:"v@:"];
23 | }
24 | return [super methodSignatureForSelector:aSelector];
25 | }
26 |
27 | - (void)forwardInvocation:(NSInvocation *)anInvocation {
28 | if (anInvocation.selector == @selector(_rac_test_forwarding)) {
29 | [self setHasInvoked:YES];
30 | return;
31 | }
32 | return [super forwardInvocation:anInvocation];
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIActivityIndicatorViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 | import Quick
4 | import Nimble
5 | import ReactiveSwift
6 | import ReactiveCocoa
7 |
8 | class UIActivityIndicatorSpec: QuickSpec {
9 | override func spec() {
10 | var activityIndicatorView: UIActivityIndicatorView!
11 | weak var _activityIndicatorView: UIActivityIndicatorView?
12 |
13 | beforeEach {
14 | activityIndicatorView = UIActivityIndicatorView(frame: .zero)
15 | _activityIndicatorView = activityIndicatorView
16 | }
17 |
18 | afterEach {
19 | activityIndicatorView = nil
20 | expect(_activityIndicatorView).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its animating state") {
24 | let (pipeSignal, observer) = Signal.pipe()
25 | activityIndicatorView.reactive.isAnimating <~ SignalProducer(pipeSignal)
26 |
27 | observer.send(value: true)
28 | expect(activityIndicatorView.isAnimating) == true
29 |
30 | observer.send(value: false)
31 | expect(activityIndicatorView.isAnimating) == false
32 | }
33 | }
34 | }
35 | #endif
36 |
--------------------------------------------------------------------------------
/ReactiveMapKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "ReactiveMapKit"
3 | s.version = "10.2.0"
4 | s.summary = "MapKit bindings for ReactiveCocoa."
5 | s.description = <<-DESC
6 | Provide MapKit bindings for ReactiveCocoa. ReactiveCocoa (RAC) is a Cocoa framework built on top of ReactiveSwift. It provides APIs for using ReactiveSwift with Apple's Cocoa frameworks.
7 | DESC
8 | s.homepage = "https://github.com/ReactiveCocoa/ReactiveCocoa"
9 | s.license = { :type => "MIT", :file => "LICENSE.md" }
10 | s.author = "ReactiveCocoa"
11 |
12 | s.osx.deployment_target = "10.9"
13 | s.ios.deployment_target = "8.0"
14 | s.tvos.deployment_target = "9.0"
15 |
16 | s.source = { :git => "https://github.com/ReactiveCocoa/ReactiveCocoa.git", :tag => "#{s.version}" }
17 | s.source_files = "ReactiveMapKit/*.{swift,h,m}"
18 |
19 | s.dependency 'ReactiveCocoa', "#{s.version}"
20 |
21 | s.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" }
22 | s.swift_version = '5.0'
23 | end
24 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIProgressViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UIProgressViewSpec: QuickSpec {
9 | override func spec() {
10 | var progressView: UIProgressView!
11 | weak var _progressView: UIProgressView?
12 |
13 | beforeEach {
14 | progressView = UIProgressView(frame: .zero)
15 | _progressView = progressView
16 | }
17 |
18 | afterEach {
19 | progressView = nil
20 | expect(_progressView).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its progress value") {
24 | let firstChange: Float = 0.5
25 | let secondChange: Float = 0.0
26 |
27 | progressView.progress = 1.0
28 |
29 | let (pipeSignal, observer) = Signal.pipe()
30 | progressView.reactive.progress <~ SignalProducer(pipeSignal)
31 |
32 | observer.send(value: firstChange)
33 | expect(progressView.progress) ≈ firstChange
34 |
35 | observer.send(value: secondChange)
36 | expect(progressView.progress) ≈ secondChange
37 | }
38 | }
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/Shared/NSLayoutConstraintSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) || canImport(UIKit)
2 |
3 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
4 | import AppKit
5 | #elseif canImport(UIKit)
6 | import UIKit
7 | #endif
8 |
9 | import ReactiveSwift
10 | import ReactiveCocoa
11 | import Quick
12 | import Nimble
13 |
14 | class NSLayoutConstraintSpec: QuickSpec {
15 | override func spec() {
16 | var constraint: NSLayoutConstraint!
17 | weak var _constraint: NSLayoutConstraint?
18 |
19 | beforeEach {
20 | constraint = NSLayoutConstraint()
21 | _constraint = constraint
22 | }
23 |
24 | afterEach {
25 | constraint = nil
26 | expect(_constraint).to(beNil())
27 | }
28 |
29 | it("should accept changes from bindings to its constant") {
30 | expect(constraint.constant).to(equal(0.0))
31 |
32 | let (pipeSignal, observer) = Signal.pipe()
33 |
34 | constraint.reactive.constant <~ pipeSignal
35 |
36 | observer.send(value: 5.0)
37 | expect(constraint.constant) ≈ 5.0
38 |
39 | observer.send(value: -3.0)
40 | expect(constraint.constant) ≈ -3.0
41 | }
42 | }
43 | }
44 | #endif
45 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | **Copyright (c) 2012 - 2016, GitHub, Inc.**
2 | **All rights reserved.**
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | the Software, and to permit persons to whom the Software is furnished to do so,
9 | subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIRefreshControl.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIRefreshControl {
6 | /// Sets whether the refresh control should be refreshing.
7 | public var isRefreshing: BindingTarget {
8 | return makeBindingTarget { $1 ? $0.beginRefreshing() : $0.endRefreshing() }
9 | }
10 |
11 | /// Sets the attributed title of the refresh control.
12 | public var attributedTitle: BindingTarget {
13 | return makeBindingTarget { $0.attributedTitle = $1 }
14 | }
15 |
16 | /// The action to be triggered when the refresh control is refreshed. It
17 | /// also controls the enabled and refreshing states of the refresh control.
18 | public var refresh: CocoaAction? {
19 | get {
20 | return associatedAction.withValue { info in
21 | return info.flatMap { info in
22 | return info.controlEvents == .valueChanged ? info.action : nil
23 | }
24 | }
25 | }
26 |
27 | nonmutating set {
28 | let disposable = newValue.flatMap { isRefreshing <~ $0.isExecuting }
29 | setAction(newValue, for: .valueChanged, disposable: disposable)
30 | }
31 | }
32 | }
33 | #endif
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | We love that you're interested in contributing to this project!
2 |
3 | To make the process as painless as possible, we have just a couple of guidelines
4 | that should make life easier for everyone involved.
5 |
6 | ## Prefer Pull Requests
7 |
8 | If you know exactly how to implement the feature being suggested or fix the bug
9 | being reported, please open a pull request instead of an issue. Pull requests are easier than
10 | patches or inline code blocks for discussing and merging the changes.
11 |
12 | If you can't make the change yourself, please open an issue after making sure
13 | that one isn't already logged. We are also happy to help you in our Slack room (ping [@ReactiveCocoa](https://twitter.com/ReactiveCocoa) for an invitation).
14 |
15 | ## Contributing Code
16 |
17 | Fork this repository, make it awesomer (preferably in a branch named for the
18 | topic), send a pull request!
19 |
20 | All code contributions should match our coding conventions ([Objective-c](https://github.com/github/objective-c-conventions) and [Swift](https://github.com/github/swift-style-guide)). If your particular case is not described in the coding convention, check the ReactiveCocoa codebase.
21 |
22 | Thanks for contributing! :boom::camel:
23 |
--------------------------------------------------------------------------------
/script/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | BUILD_DIRECTORY="build"
4 | CONFIGURATION=Release
5 | XCODE_WORKSPACE=ReactiveCocoa.xcworkspace
6 |
7 | if [[ -z $XCODE_SCHEME ]]; then
8 | echo "Error: \$XCODE_SCHEME is not set!"
9 | exit 1
10 | fi
11 |
12 | if [[ -z $XCODE_ACTION ]]; then
13 | echo "Error: \$XCODE_ACTION is not set!"
14 | exit 1
15 | fi
16 |
17 | if [[ -z $XCODE_SDK ]]; then
18 | echo "Error: \$XCODE_SDK is not set!"
19 | exit 1
20 | fi
21 |
22 | if [[ -z $XCODE_DESTINATION ]]; then
23 | echo "Error: \$XCODE_DESTINATION is not set!"
24 | exit 1
25 | fi
26 |
27 | set -o pipefail
28 | xcodebuild $XCODE_ACTION \
29 | -workspace "$XCODE_WORKSPACE" \
30 | -scheme "$XCODE_SCHEME" \
31 | -sdk "$XCODE_SDK" \
32 | -destination "$XCODE_DESTINATION" \
33 | -derivedDataPath "${BUILD_DIRECTORY}" \
34 | -configuration $CONFIGURATION \
35 | ENABLE_TESTABILITY=YES \
36 | GCC_GENERATE_DEBUGGING_SYMBOLS=NO \
37 | RUN_CLANG_STATIC_ANALYZER=NO | xcpretty
38 | result=$?
39 |
40 | if [ "$result" -ne 0 ]; then
41 | exit $result
42 | fi
43 |
44 | # Compile code in playgrounds
45 | if [[ "$XCODE_PLAYGROUND_TARGET" ]]; then
46 | echo "Validating playground..."
47 | . script/validate-playground.sh
48 | fi
49 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSTextView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSTextView {
6 | private var notifications: Signal {
7 | #if swift(>=4.0)
8 | let name = NSTextView.didChangeNotification
9 | #else
10 | let name = Notification.Name.NSControlTextDidChange
11 | #endif
12 |
13 | return NotificationCenter.default
14 | .reactive
15 | .notifications(forName: name, object: base)
16 | .take(during: lifetime)
17 | }
18 |
19 | /// A signal of values in `String` from the text field upon any changes.
20 | public var continuousStringValues: Signal {
21 | return notifications
22 | .map { notification in
23 | let textView = notification.object as! NSTextView
24 | #if swift(>=4.0)
25 | return textView.string
26 | #else
27 | return textView.string ?? ""
28 | #endif
29 | }
30 | }
31 |
32 | /// A signal of values in `NSAttributedString` from the text field upon any
33 | /// changes.
34 | public var continuousAttributedStringValues: Signal {
35 | return notifications
36 | .map { ($0.object as! NSTextView).attributedString() }
37 | }
38 |
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/ReactiveCocoa/ReactiveSwift+Lifetime.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 |
3 | extension Signal {
4 | /// Forward events from `self` until `object` deinitializes, at which point the
5 | /// returned signal will complete.
6 | ///
7 | /// - parameters:
8 | /// - object: An object of which the deinitialization would complete the returned
9 | /// `Signal`. Both Objective-C and native Swift objects are supported.
10 | ///
11 | /// - returns: A signal that will deliver events until `object` deinitializes.
12 | public func take(duringLifetimeOf object: AnyObject) -> Signal {
13 | return take(during: Lifetime.of(object))
14 | }
15 | }
16 |
17 | extension SignalProducer {
18 | /// Forward events from `self` until `object` deinitializes, at which point the
19 | /// returned producer will complete.
20 | ///
21 | /// - parameters:
22 | /// - object: An object of which the deinitialization would complete the returned
23 | /// `Signal`. Both Objective-C and native Swift objects are supported.
24 | ///
25 | /// - returns: A producer that will deliver events until `object` deinitializes.
26 | public func take(duringLifetimeOf object: AnyObject) -> SignalProducer {
27 | return take(during: Lifetime.of(object))
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceGroup.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceGroup {
6 | /// Sets the background color of the group.
7 | public var backgroundColor: BindingTarget {
8 | return makeBindingTarget { $0.setBackgroundColor($1) }
9 | }
10 |
11 | /// Sets the background image of the group.
12 | public var backgroundImage: BindingTarget {
13 | return makeBindingTarget { $0.setBackgroundImage($1) }
14 | }
15 |
16 | /// Sets the background image data of the group.
17 | public var backgroundImageData: BindingTarget {
18 | return makeBindingTarget { $0.setBackgroundImageData($1) }
19 | }
20 |
21 | /// Sets the background named image of the group.
22 | public var backgroundImageNamed: BindingTarget {
23 | return makeBindingTarget { $0.setBackgroundImageNamed($1) }
24 | }
25 |
26 | /// Sets the corner radius of the group.
27 | public var cornerRadius: BindingTarget {
28 | return makeBindingTarget { $0.setCornerRadius($1) }
29 | }
30 |
31 | /// Sets the content inset of the group.
32 | public var contentInset: BindingTarget {
33 | return makeBindingTarget { $0.setContentInset($1) }
34 | }
35 | }
36 | #endif
37 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/AppKitReusableComponentsSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import Quick
3 | import Nimble
4 | import ReactiveSwift
5 | import ReactiveCocoa
6 | import AppKit
7 |
8 | class ReusableComponentsSpec: QuickSpec {
9 | override func spec() {
10 | describe("NSTableCellView") {
11 | it("should send a `value` event when `prepareForReuse` is triggered") {
12 | let cell = NSTableCellView()
13 |
14 | var isTriggered = false
15 | cell.reactive.prepareForReuse.observeValues {
16 | isTriggered = true
17 | }
18 |
19 | expect(isTriggered) == false
20 |
21 | cell.prepareForReuse()
22 | expect(isTriggered) == true
23 | }
24 | }
25 |
26 | if #available(macOS 10.11, *) {
27 | describe("NSCollectionViewItem") {
28 | it("should send a `value` event when `prepareForReuse` is triggered") {
29 | let item = TestViewItem()
30 |
31 | var isTriggered = false
32 | item.reactive.prepareForReuse.observeValues {
33 | isTriggered = true
34 | }
35 |
36 | expect(isTriggered) == false
37 |
38 | item.prepareForReuse()
39 | expect(isTriggered) == true
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | private class TestViewItem: NSCollectionViewItem {
47 | override func loadView() {
48 | view = NSView()
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/script/validate-playground.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Bash script to lint the content of playgrounds
4 | # Heavily based on RxSwift's
5 | # https://github.com/ReactiveX/RxSwift/blob/master/scripts/validate-playgrounds.sh
6 |
7 | if [ -z "$BUILD_DIRECTORY" ]; then
8 | echo "\$BUILD_DIRECTORY is not set. Are you trying to run \`validate-playgrounds.sh\` without building RAC first?\n"
9 | echo "To validate the playground, run \`script/build\`."
10 | exit 1
11 | fi
12 |
13 | if [ -z "$PLAYGROUND" ]; then
14 | echo "\$PLAYGROUND is not set."
15 | exit 1
16 | fi
17 |
18 | if [ -z "$XCODE_PLAYGROUND_TARGET" ]; then
19 | echo "\$XCODE_PLAYGROUND_TARGET is not set."
20 | exit 1
21 | fi
22 |
23 | BUILD_DIR_PATH=
24 |
25 | if [ "$XCODE_SDK" == "macosx" ]; then
26 | BUILD_DIR_PATH=Products/${CONFIGURATION}
27 | else
28 | BUILD_DIR_PATH=Intermediates/CodeCoverage/Products/${CONFIGURATION}-${XCODE_SDK}
29 | fi
30 |
31 | SDK_ROOT=$(xcrun --sdk ${XCODE_SDK} --show-sdk-path)
32 | PAGES_PATH=${BUILD_DIRECTORY}/Build/${BUILD_DIR_PATH}/all-playground-pages.swift
33 |
34 | cat ${PLAYGROUND}/Sources/*.swift ${PLAYGROUND}/Pages/**/*.swift > ${PAGES_PATH}
35 |
36 | swift -v -target ${XCODE_PLAYGROUND_TARGET} -sdk ${SDK_ROOT} -D NOT_IN_PLAYGROUND -F ${BUILD_DIRECTORY}/Build/${BUILD_DIR_PATH} ${PAGES_PATH} > /dev/null
37 | result=$?
38 |
39 | # Cleanup
40 | rm -Rf $BUILD_DIRECTORY
41 |
42 | exit $result
43 |
--------------------------------------------------------------------------------
/ReactiveCocoa/ObjC+Constants.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // Unavailable selectors in Swift.
4 | internal enum ObjCSelector {
5 | static let forwardInvocation = Selector((("forwardInvocation:")))
6 | static let methodSignatureForSelector = Selector((("methodSignatureForSelector:")))
7 | static let getClass = Selector((("class")))
8 | }
9 |
10 | // Method encoding of the unavailable selectors.
11 | internal enum ObjCMethodEncoding {
12 | static let forwardInvocation = extract("v@:@")
13 | static let methodSignatureForSelector = extract("v@::")
14 | static let getClass = extract("#@:")
15 |
16 | private static func extract(_ string: StaticString) -> UnsafePointer {
17 | return UnsafeRawPointer(string.utf8Start).assumingMemoryBound(to: CChar.self)
18 | }
19 | }
20 |
21 | /// Objective-C type encoding.
22 | ///
23 | /// The enum does not cover all options, but only those that are expressive in
24 | /// Swift.
25 | internal enum ObjCTypeEncoding: Int8 {
26 | case char = 99
27 | case int = 105
28 | case short = 115
29 | case long = 108
30 | case longLong = 113
31 |
32 | case unsignedChar = 67
33 | case unsignedInt = 73
34 | case unsignedShort = 83
35 | case unsignedLong = 76
36 | case unsignedLongLong = 81
37 |
38 | case float = 102
39 | case double = 100
40 |
41 | case bool = 66
42 |
43 | case object = 64
44 | case type = 35
45 | case selector = 58
46 |
47 | case undefined = -1
48 | }
49 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/NSImageViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import Quick
3 | import Nimble
4 | import ReactiveSwift
5 | import ReactiveCocoa
6 | import AppKit
7 |
8 | class NSImageViewSpec: QuickSpec {
9 | override func spec() {
10 | var imageView: NSImageView!
11 | weak var _imageView: NSImageView?
12 |
13 | beforeEach {
14 | imageView = NSImageView(frame: .zero)
15 | _imageView = imageView
16 | }
17 |
18 | afterEach {
19 | imageView = nil
20 | expect(_imageView).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its enabling state") {
24 | imageView.isEnabled = false
25 |
26 | let (pipeSignal, observer) = Signal.pipe()
27 | imageView.reactive.isEnabled <~ SignalProducer(pipeSignal)
28 |
29 | observer.send(value: true)
30 | expect(imageView.isEnabled) == true
31 |
32 | observer.send(value: false)
33 | expect(imageView.isEnabled) == false
34 | }
35 |
36 | it("should accept changes from bindings to its image") {
37 |
38 | let (pipeSignal, observer) = Signal.pipe()
39 | imageView.reactive.image <~ SignalProducer(pipeSignal)
40 |
41 | let theImage = NSImage(named: NSImage.userName)
42 |
43 | observer.send(value: theImage)
44 | expect(imageView.image) == theImage
45 |
46 | observer.send(value: nil)
47 | expect(imageView.image).to(beNil())
48 | }
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIDatePickerSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UIDatePickerSpec: QuickSpec {
9 | override func spec() {
10 | var date: Date!
11 | var picker: UIDatePicker!
12 | weak var _picker: UIDatePicker?
13 |
14 | beforeEach {
15 | let formatter = DateFormatter()
16 | formatter.dateFormat = "MM/dd/YYYY"
17 | date = formatter.date(from: "11/29/1988")!
18 |
19 | picker = UIDatePicker(frame: .zero)
20 | _picker = picker
21 | }
22 |
23 | afterEach {
24 | picker = nil
25 | expect(_picker).to(beNil())
26 | }
27 |
28 | it("should accept changes from bindings to its date value") {
29 | picker.reactive.date.action(date)
30 | expect(picker.date) == date
31 | }
32 |
33 | it("should emit user initiated changes to its date value") {
34 | let expectation = self.expectation(description: "Expected rac_date to send an event when picker's date value is changed by a UI event")
35 | defer { self.waitForExpectations(timeout: 2, handler: nil) }
36 |
37 | picker.reactive.dates.observeValues { changedDate in
38 | expect(changedDate) == date
39 | expectation.fulfill()
40 | }
41 |
42 | picker.date = date
43 | picker.isEnabled = true
44 | picker.isUserInteractionEnabled = true
45 | picker.sendActions(for: .valueChanged)
46 | }
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/ReactiveCocoa/WatchKit/WKInterfaceButton.swift:
--------------------------------------------------------------------------------
1 | #if canImport(WatchKit)
2 | import ReactiveSwift
3 | import WatchKit
4 |
5 | extension Reactive where Base: WKInterfaceButton {
6 | /// Sets the title of the button.
7 | public var title: BindingTarget {
8 | return makeBindingTarget { $0.setTitle($1) }
9 | }
10 |
11 | /// Sets the attributed title of the button.
12 | public var attributedTitle: BindingTarget {
13 | return makeBindingTarget { $0.setAttributedTitle($1) }
14 | }
15 |
16 | /// Sets the background color of the button.
17 | public var backgroundColor: BindingTarget {
18 | return makeBindingTarget { $0.setBackgroundColor($1) }
19 | }
20 |
21 | /// Sets the background image of the button.
22 | public var backgroundImage: BindingTarget {
23 | return makeBindingTarget { $0.setBackgroundImage($1) }
24 | }
25 |
26 | /// Sets the background image data of the button.
27 | public var backgroundImageData: BindingTarget {
28 | return makeBindingTarget { $0.setBackgroundImageData($1) }
29 | }
30 |
31 | /// Sets the background named image of the button.
32 | public var backgroundImageNamed: BindingTarget {
33 | return makeBindingTarget { $0.setBackgroundImageNamed($1) }
34 | }
35 |
36 | /// Sets whether the button is enabled.
37 | public var isEnabled: BindingTarget {
38 | return makeBindingTarget { $0.setEnabled($1) }
39 | }
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSPopUpButton.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSPopUpButton {
6 |
7 | /// A signal of selected indexes
8 | public var selectedIndexes: Signal {
9 | return proxy.invoked.map { $0.indexOfSelectedItem }
10 | }
11 |
12 | /// Sets the button with an index.
13 | public var selectedIndex: BindingTarget {
14 | return makeBindingTarget {
15 | $0.selectItem(at: $1 ?? -1)
16 | }
17 | }
18 |
19 | /// A signal of selected title
20 | public var selectedTitles: Signal {
21 | return proxy.invoked.map { $0.titleOfSelectedItem }.skipNil()
22 | }
23 |
24 | /// Sets the button with title.
25 | /// note: emitting nil to this target will set selectedTitle to empty string
26 | public var selectedTitle: BindingTarget {
27 | return makeBindingTarget {
28 | $0.selectItem(withTitle: $1 ?? "")
29 | }
30 | }
31 |
32 | public var selectedItems: Signal {
33 | return proxy.invoked.map { $0.selectedItem }.skipNil()
34 | }
35 |
36 |
37 | /// A signal of selected tags
38 | public var selectedTags: Signal {
39 | return proxy.invoked.map { $0.selectedTag() }
40 | }
41 |
42 | /// Sets the selected tag
43 | public var selectedTag: BindingTarget {
44 | return makeBindingTarget {
45 | $0.selectItem(withTag: $1)
46 | }
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "ReactiveCocoa",
7 | platforms: [
8 | .macOS(.v10_10), .iOS(.v8), .tvOS(.v9), .watchOS(.v2)
9 | ],
10 | products: [
11 | .library(name: "ReactiveCocoa", targets: ["ReactiveCocoa"])
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "6.2.0"),
15 | .package(url: "https://github.com/Quick/Quick.git", from: "2.0.0"),
16 | .package(url: "https://github.com/Quick/Nimble.git", from: "8.0.0"),
17 | ],
18 | targets: [
19 | .target(
20 | name: "ReactiveCocoaObjC",
21 | dependencies: [],
22 | path: "ReactiveCocoaObjC"),
23 |
24 | .target(
25 | name: "ReactiveCocoa",
26 | dependencies: ["ReactiveSwift", "ReactiveCocoaObjC"],
27 | path: "ReactiveCocoa"),
28 |
29 | .target(
30 | name: "ReactiveCocoaObjCTestSupport",
31 | path: "ReactiveCocoaObjCTestSupport"),
32 |
33 | .testTarget(
34 | name: "ReactiveCocoaTests",
35 | dependencies: [
36 | "ReactiveCocoa",
37 | "ReactiveCocoaObjCTestSupport",
38 | "Quick",
39 | "Nimble"
40 | ],
41 | path: "ReactiveCocoaTests"),
42 | ]
43 | )
44 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIScrollView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIScrollView {
6 | /// Sets the content inset of the scroll view.
7 | public var contentInset: BindingTarget {
8 | return makeBindingTarget { $0.contentInset = $1 }
9 | }
10 |
11 | /// Sets the scroll indicator insets of the scroll view.
12 | public var scrollIndicatorInsets: BindingTarget {
13 | return makeBindingTarget { $0.scrollIndicatorInsets = $1 }
14 | }
15 |
16 | /// Sets whether scrolling the scroll view is enabled.
17 | public var isScrollEnabled: BindingTarget {
18 | return makeBindingTarget { $0.isScrollEnabled = $1 }
19 | }
20 |
21 | /// Sets the zoom scale of the scroll view.
22 | public var zoomScale: BindingTarget {
23 | return makeBindingTarget { $0.zoomScale = $1 }
24 | }
25 |
26 | /// Sets the minimum zoom scale of the scroll view.
27 | public var minimumZoomScale: BindingTarget {
28 | return makeBindingTarget { $0.minimumZoomScale = $1 }
29 | }
30 |
31 | /// Sets the maximum zoom scale of the scroll view.
32 | public var maximumZoomScale: BindingTarget {
33 | return makeBindingTarget { $0.maximumZoomScale = $1 }
34 | }
35 |
36 | #if os(iOS)
37 | /// Sets whether the scroll view scrolls to the top when the menu is tapped.
38 | public var scrollsToTop: BindingTarget {
39 | return makeBindingTarget { $0.scrollsToTop = $1 }
40 | }
41 | #endif
42 | }
43 | #endif
44 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIImageViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UIImageViewSpec: QuickSpec {
9 | override func spec() {
10 | var imageView: UIImageView!
11 | weak var _imageView: UIImageView?
12 |
13 | beforeEach {
14 | imageView = UIImageView(frame: .zero)
15 | _imageView = imageView
16 | }
17 |
18 | afterEach {
19 | imageView = nil
20 | expect(_imageView).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its displaying image") {
24 | let firstChange = UIImage()
25 | let secondChange = UIImage()
26 |
27 | let (pipeSignal, observer) = Signal.pipe()
28 | imageView.reactive.image <~ SignalProducer(pipeSignal)
29 |
30 | observer.send(value: firstChange)
31 | expect(imageView.image) == firstChange
32 |
33 | observer.send(value: secondChange)
34 | expect(imageView.image) == secondChange
35 | }
36 |
37 | it("should accept changes from bindings to its displaying image when highlighted") {
38 | let firstChange = UIImage()
39 | let secondChange = UIImage()
40 |
41 | let (pipeSignal, observer) = Signal.pipe()
42 | imageView.reactive.highlightedImage <~ SignalProducer(pipeSignal)
43 |
44 | observer.send(value: firstChange)
45 | expect(imageView.highlightedImage) == firstChange
46 |
47 | observer.send(value: secondChange)
48 | expect(imageView.highlightedImage) == secondChange
49 | }
50 | }
51 | }
52 | #endif
53 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIKitReusableComponentsSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import Quick
3 | import Nimble
4 | import ReactiveSwift
5 | import ReactiveCocoa
6 | import UIKit
7 |
8 | class ReusableComponentsSpec: QuickSpec {
9 | override func spec() {
10 | describe("UITableViewCell") {
11 | it("should send a `value` event when `prepareForReuse` is triggered") {
12 | let cell = UITableViewCell()
13 |
14 | var isTriggered = false
15 | cell.reactive.prepareForReuse.observeValues {
16 | isTriggered = true
17 | }
18 |
19 | expect(isTriggered) == false
20 |
21 | cell.prepareForReuse()
22 | expect(isTriggered) == true
23 | }
24 | }
25 |
26 | describe("UITableViewHeaderFooterView") {
27 | it("should send a `value` event when `prepareForReuse` is triggered") {
28 | let cell = UITableViewHeaderFooterView()
29 |
30 | var isTriggered = false
31 | cell.reactive.prepareForReuse.observeValues {
32 | isTriggered = true
33 | }
34 |
35 | expect(isTriggered) == false
36 |
37 | cell.prepareForReuse()
38 | expect(isTriggered) == true
39 | }
40 | }
41 |
42 | describe("UICollectionReusableView") {
43 | it("should send a `value` event when `prepareForReuse` is triggered") {
44 | let cell = UICollectionReusableView()
45 |
46 | var isTriggered = false
47 | cell.reactive.prepareForReuse.observeValues {
48 | isTriggered = true
49 | }
50 |
51 | expect(isTriggered) == false
52 |
53 | cell.prepareForReuse()
54 | expect(isTriggered) == true
55 | }
56 | }
57 | }
58 | }
59 | #endif
60 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIButton.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIButton {
6 | /// The action to be triggered when the button is pressed. It also controls
7 | /// the enabled state of the button.
8 | public var pressed: CocoaAction? {
9 | get {
10 | return associatedAction.withValue { info in
11 | return info.flatMap { info in
12 | return info.controlEvents == pressEvent ? info.action : nil
13 | }
14 | }
15 | }
16 |
17 | nonmutating set {
18 | setAction(newValue, for: pressEvent)
19 | }
20 | }
21 |
22 | private var pressEvent: UIControl.Event {
23 | if #available(iOS 9.0, tvOS 9.0, *) {
24 | return .primaryActionTriggered
25 | } else {
26 | return .touchUpInside
27 | }
28 | }
29 |
30 | /// Sets the title of the button for its normal state.
31 | public var title: BindingTarget {
32 | return makeBindingTarget { $0.setTitle($1, for: .normal) }
33 | }
34 |
35 | /// Sets the title of the button for the specified state.
36 | public func title(for state: UIControl.State) -> BindingTarget {
37 | return makeBindingTarget { $0.setTitle($1, for: state) }
38 | }
39 |
40 | /// Sets the image of the button for the specified state.
41 | public func image(for state: UIControl.State) -> BindingTarget {
42 | return makeBindingTarget { $0.setImage($1, for: state) }
43 | }
44 |
45 | /// Sets the image of the button for the .normal state
46 | public var image: BindingTarget {
47 | return image(for: .normal)
48 | }
49 | }
50 | #endif
51 |
--------------------------------------------------------------------------------
/ReactiveCocoa.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "ReactiveCocoa"
3 | s.version = "10.2.0"
4 | s.summary = "Streams of values over time"
5 | s.description = <<-DESC
6 | ReactiveCocoa (RAC) is a Cocoa framework built on top of ReactiveSwift. It provides APIs for using ReactiveSwift with Apple's Cocoa frameworks.
7 | DESC
8 | s.homepage = "https://github.com/ReactiveCocoa/ReactiveCocoa"
9 | s.license = { :type => "MIT", :file => "LICENSE.md" }
10 | s.author = "ReactiveCocoa"
11 |
12 | s.osx.deployment_target = "10.9"
13 | s.ios.deployment_target = "8.0"
14 | s.tvos.deployment_target = "9.0"
15 | s.watchos.deployment_target = "2.0"
16 |
17 | s.source = { :git => "https://github.com/ReactiveCocoa/ReactiveCocoa.git", :tag => "#{s.version}" }
18 | s.source_files = "ReactiveCocoa/*.{swift,h,m}", "ReactiveCocoa/Shared/*.{swift}", "ReactiveCocoaObjC/**/*.{h,m}"
19 | s.public_header_files = "ReactiveCocoaObjC/include/ObjCRuntimeAliases.h"
20 | s.osx.source_files = "ReactiveCocoa/AppKit/*.{swift}"
21 | s.ios.source_files = "ReactiveCocoa/UIKit/*.{swift}", "ReactiveCocoa/UIKit/iOS/*.{swift}"
22 | s.tvos.source_files = "ReactiveCocoa/UIKit/*.{swift}"
23 | s.watchos.exclude_files = "ReactiveCocoa/Shared/*.{swift}"
24 | s.watchos.source_files = "ReactiveCocoa/WatchKit/*.{swift}"
25 | s.module_name = 'ReactiveCocoa'
26 |
27 | s.dependency 'ReactiveSwift', '~> 6.2'
28 |
29 | s.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" }
30 | s.swift_version = '5.0'
31 | end
32 |
--------------------------------------------------------------------------------
/ReactiveCocoa/ObjC+Selector.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Selector {
4 | /// `self` as a pointer. It is uniqued across instances, similar to
5 | /// `StaticString`.
6 | internal var utf8Start: UnsafePointer {
7 | return unsafeBitCast(self, to: UnsafePointer.self)
8 | }
9 |
10 | /// An alias of `self`, used in method interception.
11 | internal var alias: Selector {
12 | return prefixing("rac0_")
13 | }
14 |
15 | /// An alias of `self`, used in method interception specifically for
16 | /// preserving (if found) an immediate implementation of `self` in the
17 | /// runtime subclass.
18 | internal var interopAlias: Selector {
19 | return prefixing("rac1_")
20 | }
21 |
22 | /// An alias of `self`, used for delegate proxies.
23 | internal var delegateProxyAlias: Selector {
24 | return prefixing("rac2_")
25 | }
26 |
27 | internal func prefixing(_ prefix: StaticString) -> Selector {
28 | let length = Int(strlen(utf8Start))
29 | let prefixedLength = length + prefix.utf8CodeUnitCount
30 |
31 | let asciiPrefix = UnsafeRawPointer(prefix.utf8Start).assumingMemoryBound(to: Int8.self)
32 |
33 | let cString = UnsafeMutablePointer.allocate(capacity: prefixedLength + 1)
34 | defer {
35 | cString.deinitialize(count: prefixedLength + 1)
36 | cString.deallocate()
37 | }
38 |
39 | cString.initialize(from: asciiPrefix, count: prefix.utf8CodeUnitCount)
40 | (cString + prefix.utf8CodeUnitCount).initialize(from: utf8Start, count: length)
41 | (cString + prefixedLength).initialize(to: Int8(UInt8(ascii: "\0")))
42 |
43 | return sel_registerName(cString)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIViewController.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIViewController {
6 | /// Set's the title of the view controller.
7 | public var title: BindingTarget {
8 | return makeBindingTarget({ $0.title = $1 })
9 | }
10 |
11 | /// A signal that sends a value event every time `viewWillAppear` is invoked.
12 | public var viewWillAppear: Signal {
13 | return trigger(for: #selector(Base.viewWillAppear))
14 | }
15 |
16 | /// A signal that sends a value event every time `viewDidAppear` is invoked.
17 | public var viewDidAppear: Signal {
18 | return trigger(for: #selector(Base.viewDidAppear))
19 | }
20 |
21 | /// A signal that sends a value event every time `viewWillDisappear` is invoked.
22 | public var viewWillDisappear: Signal {
23 | return trigger(for: #selector(Base.viewWillDisappear))
24 | }
25 |
26 | /// A signal that sends a value event every time `viewDidDisappear` is invoked.
27 | public var viewDidDisappear: Signal {
28 | return trigger(for: #selector(Base.viewDidDisappear))
29 | }
30 |
31 | /// A signal that sends a value event every time `viewWillLayoutSubviews` is invoked.
32 | public var viewWillLayoutSubviews: Signal {
33 | return trigger(for: #selector(Base.viewWillLayoutSubviews))
34 | }
35 |
36 | /// A signal that sends a value event every time `viewDidLayoutSubviews` is invoked.
37 | public var viewDidLayoutSubviews: Signal {
38 | return trigger(for: #selector(Base.viewDidLayoutSubviews))
39 | }
40 | }
41 | #endif
42 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "CwlCatchException",
6 | "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "7cd2f8cacc4d22f21bc0b2309c3b18acf7957b66",
10 | "version": "1.2.0"
11 | }
12 | },
13 | {
14 | "package": "CwlPreconditionTesting",
15 | "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "c228db5d2ad1b01ebc84435e823e6cca4e3db98b",
19 | "version": "1.2.0"
20 | }
21 | },
22 | {
23 | "package": "Nimble",
24 | "repositoryURL": "https://github.com/Quick/Nimble.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "b02b00b30b6353632aa4a5fb6124f8147f7140c0",
28 | "version": "8.0.5"
29 | }
30 | },
31 | {
32 | "package": "Quick",
33 | "repositoryURL": "https://github.com/Quick/Quick.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "33682c2f6230c60614861dfc61df267e11a1602f",
37 | "version": "2.2.0"
38 | }
39 | },
40 | {
41 | "package": "ReactiveSwift",
42 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift",
43 | "state": {
44 | "branch": null,
45 | "revision": "e8703715afe26a8efbb2ecfdb3454d648f58d691",
46 | "version": "6.2.0"
47 | }
48 | }
49 | ]
50 | },
51 | "version": 1
52 | }
53 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UIBarButtonItem.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UIBarButtonItem {
6 | /// The current associated action of `self`.
7 | private var associatedAction: Atomic<(action: CocoaAction, disposable: Disposable?)?> {
8 | return associatedValue { _ in Atomic(nil) }
9 | }
10 |
11 | /// The action to be triggered when the button is pressed. It also controls
12 | /// the enabled state of the button.
13 | public var pressed: CocoaAction? {
14 | get {
15 | return associatedAction.value?.action
16 | }
17 |
18 | nonmutating set {
19 | base.target = newValue
20 | base.action = newValue.map { _ in CocoaAction.selector }
21 |
22 | associatedAction
23 | .swap(newValue.map { action in
24 | let disposable = isEnabled <~ action.isEnabled
25 | return (action, disposable)
26 | })?
27 | .disposable?.dispose()
28 | }
29 | }
30 |
31 | /// Sets the style of the bar button item.
32 | public var style: BindingTarget {
33 | return makeBindingTarget { $0.style = $1 }
34 | }
35 |
36 | /// Sets the width of the bar button item.
37 | public var width: BindingTarget {
38 | return makeBindingTarget { $0.width = $1 }
39 | }
40 |
41 | /// Sets the possible titles of the bar button item.
42 | public var possibleTitles: BindingTarget?> {
43 | return makeBindingTarget { $0.possibleTitles = $1 }
44 | }
45 |
46 | /// Sets the custom view of the bar button item.
47 | public var customView: BindingTarget {
48 | return makeBindingTarget { $0.customView = $1 }
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSTextField.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSTextField {
6 | private var notifications: Signal {
7 | #if swift(>=4.0)
8 | let name = NSControl.textDidChangeNotification
9 | #else
10 | let name = Notification.Name.NSControlTextDidChange
11 | #endif
12 |
13 | return NotificationCenter.default
14 | .reactive
15 | .notifications(forName: name, object: base)
16 | .take(during: lifetime)
17 | }
18 |
19 | /// A signal of values in `String` from the text field upon any changes.
20 | public var continuousStringValues: Signal {
21 | return notifications
22 | .map { ($0.object as! NSTextField).stringValue }
23 | }
24 |
25 | /// A signal of values in `NSAttributedString` from the text field upon any
26 | /// changes.
27 | public var continuousAttributedStringValues: Signal {
28 | return notifications
29 | .map { ($0.object as! NSTextField).attributedStringValue }
30 | }
31 |
32 | /// Wraps the `stringValue` binding target from NSControl for
33 | /// cross-platform compatibility
34 | public var text: BindingTarget {
35 | return stringValue
36 | }
37 |
38 | /// Wraps the `stringValue` binding target from NSControl for
39 | /// cross-platform compatibility
40 | public var attributedText: BindingTarget {
41 | return attributedStringValue
42 | }
43 |
44 | /// Sets the color of the text with an `NSColor`.
45 | public var textColor: BindingTarget {
46 | return makeBindingTarget { $0.textColor = $1 }
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/ReactiveCocoa/CocoaTarget.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | /// A target that accepts action messages.
5 | internal final class CocoaTarget: NSObject {
6 | private enum State {
7 | case idle
8 | case sending(queue: [Value])
9 | }
10 |
11 | private let observer: Signal.Observer
12 | private let transform: (Any?) -> Value
13 |
14 | private var state: State
15 |
16 | internal init(_ observer: Signal.Observer, transform: @escaping (Any?) -> Value) {
17 | self.observer = observer
18 | self.transform = transform
19 | self.state = .idle
20 | }
21 |
22 | /// Broadcast the action message to all observers.
23 | ///
24 | /// Reentrancy is supported, and the action message would be deferred until the
25 | /// delivery of the current message has completed.
26 | ///
27 | /// - note: It should only be invoked on the main queue.
28 | ///
29 | /// - parameters:
30 | /// - sender: The object which sends the action message.
31 | @objc internal func invoke(_ sender: Any?) {
32 | switch state {
33 | case .idle:
34 | state = .sending(queue: [])
35 | observer.send(value: transform(sender))
36 |
37 | while case let .sending(values) = state {
38 | guard !values.isEmpty else {
39 | break
40 | }
41 |
42 | state = .sending(queue: Array(values.dropFirst()))
43 | observer.send(value: values[0])
44 | }
45 |
46 | state = .idle
47 |
48 | case let .sending(values):
49 | state = .sending(queue: values + [transform(sender)])
50 | }
51 | }
52 | }
53 |
54 | extension CocoaTarget where Value == Void {
55 | internal convenience init(_ observer: Signal<(), Never>.Observer) {
56 | self.init(observer, transform: { _ in })
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/SwizzlingSpec.swift:
--------------------------------------------------------------------------------
1 | @testable import ReactiveCocoa
2 | import Foundation
3 | import Quick
4 | import Nimble
5 |
6 | class SwizzledObject: NSObject {}
7 |
8 | class SwizzlingSpec: QuickSpec {
9 | override func spec() {
10 | describe("runtime subclassing") {
11 | it("should swizzle the instance while still reporting the perceived class in `-class` and `+class`") {
12 | let object = SwizzledObject()
13 | expect(type(of: object)).to(beIdenticalTo(SwizzledObject.self))
14 |
15 | let subclass: AnyClass = swizzleClass(object)
16 | expect(type(of: object)).to(beIdenticalTo(SwizzledObject.self))
17 | expect(object.objcClass).to(beIdenticalTo(SwizzledObject.self))
18 | expect(object_getClass(object)).to(beIdenticalTo(subclass))
19 |
20 | let objcClass: AnyClass = (object as AnyObject).objcClass
21 | expect(objcClass).to(beIdenticalTo(SwizzledObject.self))
22 | expect((objcClass as AnyObject).objcClass as Any?).to(beIdenticalTo(SwizzledObject.self as AnyClass))
23 |
24 | expect(String(cString: class_getName(subclass))).to(contain("_RACSwift"))
25 | }
26 |
27 | it("should reuse the runtime subclass across instances") {
28 | let object = SwizzledObject()
29 | let subclass: AnyClass = swizzleClass(object)
30 |
31 | let object2 = SwizzledObject()
32 | let subclass2: AnyClass = swizzleClass(object2)
33 |
34 | expect(subclass).to(beIdenticalTo(subclass2))
35 | }
36 |
37 | it("should return the known runtime subclass") {
38 | let object = SwizzledObject()
39 | let subclass: AnyClass = swizzleClass(object)
40 | let subclass2: AnyClass = swizzleClass(object)
41 |
42 | expect(subclass).to(beIdenticalTo(subclass2))
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/NSTableViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import Quick
3 | import Nimble
4 | import ReactiveCocoa
5 | import ReactiveSwift
6 | import AppKit
7 |
8 | final class NSTableViewSpec: QuickSpec {
9 | override func spec() {
10 | var tableView: TestTableView!
11 |
12 | beforeEach {
13 | tableView = TestTableView()
14 | }
15 |
16 | describe("reloadData") {
17 | var bindingSignal: Signal<(), Never>!
18 | var bindingObserver: Signal<(), Never>.Observer!
19 |
20 | var reloadDataCount = 0
21 |
22 | beforeEach {
23 | let (signal, observer) = Signal<(), Never>.pipe()
24 | (bindingSignal, bindingObserver) = (signal, observer)
25 |
26 | reloadDataCount = 0
27 |
28 | tableView.reloadDataSignal.observeValues {
29 | reloadDataCount += 1
30 | }
31 | }
32 |
33 | it("invokes reloadData whenever the bound signal sends a value") {
34 | tableView.reactive.reloadData <~ bindingSignal
35 |
36 | bindingObserver.send(value: ())
37 | bindingObserver.send(value: ())
38 |
39 | expect(reloadDataCount) == 2
40 | }
41 | }
42 | }
43 | }
44 |
45 | private final class TestTableView: NSTableView {
46 | let reloadDataSignal: Signal<(), Never>
47 | private let reloadDataObserver: Signal<(), Never>.Observer
48 |
49 | override init(frame: CGRect) {
50 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
51 | super.init(frame: frame)
52 | }
53 |
54 | required init?(coder aDecoder: NSCoder) {
55 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
56 | super.init(coder: aDecoder)
57 | }
58 |
59 | override func reloadData() {
60 | super.reloadData()
61 | reloadDataObserver.send(value: ())
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UITableViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | final class UITableViewSpec: QuickSpec {
9 | override func spec() {
10 | var tableView: TestTableView!
11 |
12 | beforeEach {
13 | tableView = TestTableView()
14 | }
15 |
16 | describe("reloadData") {
17 | var bindingSignal: Signal<(), Never>!
18 | var bindingObserver: Signal<(), Never>.Observer!
19 |
20 | var reloadDataCount = 0
21 |
22 | beforeEach {
23 | let (signal, observer) = Signal<(), Never>.pipe()
24 | (bindingSignal, bindingObserver) = (signal, observer)
25 |
26 | reloadDataCount = 0
27 |
28 | tableView.reloadDataSignal.observeValues {
29 | reloadDataCount += 1
30 | }
31 | }
32 |
33 | it("invokes reloadData whenever the bound signal sends a value") {
34 | tableView.reactive.reloadData <~ bindingSignal
35 |
36 | bindingObserver.send(value: ())
37 | bindingObserver.send(value: ())
38 |
39 | expect(reloadDataCount) == 2
40 | }
41 | }
42 | }
43 | }
44 |
45 | private final class TestTableView: UITableView {
46 | let reloadDataSignal: Signal<(), Never>
47 | private let reloadDataObserver: Signal<(), Never>.Observer
48 |
49 | override init(frame: CGRect, style: UITableView.Style) {
50 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
51 | super.init(frame: frame, style: style)
52 | }
53 |
54 | required init?(coder aDecoder: NSCoder) {
55 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
56 | super.init(coder: aDecoder)
57 | }
58 |
59 | override func reloadData() {
60 | super.reloadData()
61 | reloadDataObserver.send(value: ())
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/ReactiveCocoa/AppKit/NSButton.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import AppKit
3 | import ReactiveSwift
4 |
5 | extension Reactive where Base: NSButton {
6 |
7 | internal var associatedAction: Atomic<(action: CocoaAction, disposable: CompositeDisposable)?> {
8 | return associatedValue { _ in Atomic(nil) }
9 | }
10 |
11 | public var pressed: CocoaAction? {
12 | get {
13 | return associatedAction.value?.action
14 | }
15 |
16 | nonmutating set {
17 | associatedAction
18 | .modify { action in
19 | action?.disposable.dispose()
20 | action = newValue.map { action in
21 | let disposable = CompositeDisposable()
22 | disposable += isEnabled <~ action.isEnabled
23 | disposable += proxy.invoked.observeValues(action.execute)
24 | return (action, disposable)
25 | }
26 | }
27 | }
28 | }
29 |
30 | #if swift(>=4.0)
31 | /// A signal of integer states (On, Off, Mixed), emitted by the button.
32 | public var states: Signal {
33 | return proxy.invoked.map { $0.state }
34 | }
35 |
36 | /// Sets the button's state
37 | public var state: BindingTarget {
38 | return makeBindingTarget { $0.state = $1 }
39 | }
40 | #else
41 | /// A signal of integer states (On, Off, Mixed), emitted by the button.
42 | public var states: Signal {
43 | return proxy.invoked.map { $0.state }
44 | }
45 |
46 | /// Sets the button's state
47 | public var state: BindingTarget {
48 | return makeBindingTarget { $0.state = $1 }
49 | }
50 | #endif
51 |
52 | /// Sets the button's image
53 | public var image: BindingTarget {
54 | return makeBindingTarget { $0.image = $1 }
55 | }
56 | }
57 | #endif
58 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/iOS/UIPickerView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS) && !os(watchOS)
2 | import Foundation
3 | import ReactiveSwift
4 | import UIKit
5 |
6 | private class PickerViewDelegateProxy: DelegateProxy, UIPickerViewDelegate {
7 | @objc func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
8 | forwardee?.pickerView?(pickerView, didSelectRow: row, inComponent: component)
9 | }
10 | }
11 |
12 | extension Reactive where Base: UIPickerView {
13 |
14 | private var proxy: PickerViewDelegateProxy {
15 | return .proxy(for: base,
16 | setter: #selector(setter: base.delegate),
17 | getter: #selector(getter: base.delegate))
18 | }
19 |
20 | /// Sets the selected row in the specified component, without animating the
21 | /// position.
22 | public func selectedRow(inComponent component: Int) -> BindingTarget {
23 | return makeBindingTarget { $0.selectRow($1, inComponent: component, animated: false) }
24 | }
25 |
26 | /// Reloads all components
27 | public var reloadAllComponents: BindingTarget<()> {
28 | return makeBindingTarget { base, _ in base.reloadAllComponents() }
29 | }
30 |
31 | /// Reloads the specified component
32 | public var reloadComponent: BindingTarget {
33 | return makeBindingTarget { $0.reloadComponent($1) }
34 | }
35 |
36 | /// Create a signal which sends a `value` event for each row selection
37 | ///
38 | /// - returns: A trigger signal.
39 | public var selections: Signal<(row: Int, component: Int), Never> {
40 | return proxy.intercept(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)))
41 | .map { (row: $0[1] as! Int, component: $0[2] as! Int) }
42 | }
43 | }
44 | #endif
45 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UICollectionViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import Quick
3 | import Nimble
4 | import ReactiveCocoa
5 | import ReactiveSwift
6 | import UIKit
7 |
8 | final class UICollectionViewSpec: QuickSpec {
9 | override func spec() {
10 | var collectionView: TestCollectionView!
11 |
12 | beforeEach {
13 | collectionView = TestCollectionView()
14 | }
15 |
16 | describe("reloadData") {
17 | var bindingSignal: Signal<(), Never>!
18 | var bindingObserver: Signal<(), Never>.Observer!
19 |
20 | var reloadDataCount = 0
21 |
22 | beforeEach {
23 | let (signal, observer) = Signal<(), Never>.pipe()
24 | (bindingSignal, bindingObserver) = (signal, observer)
25 |
26 | reloadDataCount = 0
27 |
28 | collectionView.reloadDataSignal.observeValues {
29 | reloadDataCount += 1
30 | }
31 | }
32 |
33 | it("invokes reloadData whenever the bound signal sends a value") {
34 | collectionView.reactive.reloadData <~ bindingSignal
35 |
36 | bindingObserver.send(value: ())
37 | bindingObserver.send(value: ())
38 |
39 | expect(reloadDataCount) == 2
40 | }
41 | }
42 | }
43 | }
44 |
45 | private final class TestCollectionView: UICollectionView {
46 | let reloadDataSignal: Signal<(), Never>
47 | private let reloadDataObserver: Signal<(), Never>.Observer
48 |
49 | init() {
50 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
51 | super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
52 | }
53 |
54 | required init?(coder aDecoder: NSCoder) {
55 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
56 | super.init(coder: aDecoder)
57 | }
58 |
59 | override func reloadData() {
60 | super.reloadData()
61 | reloadDataObserver.send(value: ())
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIControl+EnableSendActionsForControlEvents.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 |
4 | private func rac_swizzle() {
5 | let originalSelector = #selector(UIControl.sendAction(_:to:for:))
6 | let swizzledSelector = #selector(UIControl.rac_sendAction(_:to:forEvent:))
7 |
8 | let originalMethod = class_getInstanceMethod(UIControl.self, originalSelector)!
9 | let swizzledMethod = class_getInstanceMethod(UIControl.self, swizzledSelector)!
10 |
11 | let didAddMethod = class_addMethod(UIControl.self,
12 | originalSelector,
13 | method_getImplementation(swizzledMethod),
14 | method_getTypeEncoding(swizzledMethod)!)
15 |
16 | if didAddMethod {
17 | class_replaceMethod(UIControl.self,
18 | swizzledSelector,
19 | method_getImplementation(originalMethod),
20 | method_getTypeEncoding(originalMethod)!)
21 | } else {
22 | method_exchangeImplementations(originalMethod, swizzledMethod)
23 | }
24 | }
25 |
26 | /// Unfortunately, there's an apparent limitation in using `sendActionsForControlEvents`
27 | /// on unit-tests for any control besides `UIButton` which is very unfortunate since we
28 | /// want test our bindings for `UIDatePicker`, `UISwitch`, `UITextField` and others
29 | /// in the future. To be able to test them, we're now using swizzling to manually invoke
30 | /// the pair target+action.
31 | extension UIControl {
32 | static func _initialize() {
33 | rac_swizzle()
34 | }
35 |
36 | // MARK: - Method Swizzling
37 |
38 | @objc func rac_sendAction(_ action: Selector, to target: AnyObject?, forEvent event: UIEvent?) {
39 | _ = target?.perform(action, with: self)
40 | }
41 | }
42 | #endif
43 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UISliderSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UISliderSpec: QuickSpec {
9 | override func spec() {
10 | var slider: UISlider!
11 | weak var _slider: UISlider?
12 |
13 | beforeEach {
14 | slider = UISlider()
15 | _slider = slider
16 | }
17 |
18 | afterEach {
19 | slider = nil
20 | expect(_slider).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its value") {
24 | expect(slider.value) == 0.0
25 |
26 | let (pipeSignal, observer) = Signal.pipe()
27 |
28 | slider.reactive.value <~ pipeSignal
29 |
30 | observer.send(value: 0.5)
31 | expect(slider.value) ≈ 0.5
32 | }
33 |
34 | it("should accept changes from bindings to its minimum value") {
35 | expect(slider.minimumValue) == 0.0
36 |
37 | let (pipeSignal, observer) = Signal.pipe()
38 |
39 | slider.reactive.minimumValue <~ pipeSignal
40 |
41 | observer.send(value: 0.3)
42 | expect(slider.minimumValue) ≈ 0.3
43 | }
44 |
45 | it("should accept changes from bindings to its maximum value") {
46 | expect(slider.maximumValue) == 1.0
47 |
48 | let (pipeSignal, observer) = Signal.pipe()
49 |
50 | slider.reactive.maximumValue <~ pipeSignal
51 |
52 | observer.send(value: 0.7)
53 | expect(slider.maximumValue) ≈ 0.7
54 | }
55 |
56 | it("should emit user's changes for its value") {
57 | slider.value = 0.25
58 |
59 | var updatedValue: Float?
60 | slider.reactive.values.observeValues { value in
61 | updatedValue = value
62 | }
63 |
64 | expect(updatedValue).to(beNil())
65 | slider.sendActions(for: .valueChanged)
66 | expect(updatedValue) ≈ 0.25
67 | }
68 | }
69 | }
70 | #endif
71 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AppKit/NSCollectionViewSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
2 | import Quick
3 | import Nimble
4 | import ReactiveCocoa
5 | import ReactiveSwift
6 | import AppKit
7 |
8 | @available(macOS 10.11, *)
9 | final class NSCollectionViewSpec: QuickSpec {
10 | override func spec() {
11 | var collectionView: TestCollectionView!
12 |
13 | beforeEach {
14 | collectionView = TestCollectionView()
15 | }
16 |
17 | describe("reloadData") {
18 | var bindingSignal: Signal<(), Never>!
19 | var bindingObserver: Signal<(), Never>.Observer!
20 |
21 | var reloadDataCount = 0
22 |
23 | beforeEach {
24 | let (signal, observer) = Signal<(), Never>.pipe()
25 | (bindingSignal, bindingObserver) = (signal, observer)
26 |
27 | reloadDataCount = 0
28 |
29 | collectionView.reloadDataSignal.observeValues {
30 | reloadDataCount += 1
31 | }
32 | }
33 |
34 | it("invokes reloadData whenever the bound signal sends a value") {
35 | collectionView.reactive.reloadData <~ bindingSignal
36 |
37 | bindingObserver.send(value: ())
38 | bindingObserver.send(value: ())
39 |
40 | expect(reloadDataCount) == 2
41 | }
42 | }
43 | }
44 | }
45 |
46 | @available(macOS 10.11, *)
47 | private final class TestCollectionView: NSCollectionView {
48 | let reloadDataSignal: Signal<(), Never>
49 | private let reloadDataObserver: Signal<(), Never>.Observer
50 |
51 | override init(frame: CGRect) {
52 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
53 | super.init(frame: frame)
54 | }
55 |
56 | required init?(coder aDecoder: NSCoder) {
57 | (reloadDataSignal, reloadDataObserver) = Signal.pipe()
58 | super.init(coder: aDecoder)
59 | }
60 |
61 | override func reloadData() {
62 | super.reloadData()
63 | reloadDataObserver.send(value: ())
64 | }
65 | }
66 | #endif
67 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIStepperSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UIStepperSpec: QuickSpec {
9 | override func spec() {
10 | var stepper: UIStepper!
11 | weak var _stepper: UIStepper?
12 |
13 | beforeEach {
14 | stepper = UIStepper()
15 | _stepper = stepper
16 | }
17 |
18 | afterEach {
19 | stepper = nil
20 | expect(_stepper).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its value") {
24 | expect(stepper.value) == 0.0
25 |
26 | let (pipeSignal, observer) = Signal.pipe()
27 |
28 | stepper.reactive.value <~ pipeSignal
29 |
30 | observer.send(value: 0.5)
31 | expect(stepper.value) ≈ 0.5
32 | }
33 |
34 | it("should accept changes from bindings to its minimum value") {
35 | expect(stepper.minimumValue) == 0.0
36 |
37 | let (pipeSignal, observer) = Signal.pipe()
38 |
39 | stepper.reactive.minimumValue <~ pipeSignal
40 |
41 | observer.send(value: 0.3)
42 | expect(stepper.minimumValue) ≈ 0.3
43 | }
44 |
45 | it("should accept changes from bindings to its maximum value") {
46 | expect(stepper.maximumValue) == 100.0
47 |
48 | let (pipeSignal, observer) = Signal.pipe()
49 |
50 | stepper.reactive.maximumValue <~ pipeSignal
51 |
52 | observer.send(value: 33.0)
53 | expect(stepper.maximumValue) ≈ 33.0
54 | }
55 |
56 | it("should emit user's changes for its value") {
57 | stepper.value = 0.25
58 |
59 | var updatedValue: Double?
60 | stepper.reactive.values.observeValues { value in
61 | updatedValue = value
62 | }
63 |
64 | expect(updatedValue).to(beNil())
65 | stepper.sendActions(for: .valueChanged)
66 | expect(updatedValue) ≈ 0.25
67 | }
68 | }
69 | }
70 | #endif
71 |
--------------------------------------------------------------------------------
/ReactiveCocoa/ObjC+Messages.swift:
--------------------------------------------------------------------------------
1 | // Unavailable classes like `NSInvocation` can still be passed into Swift as
2 | // `AnyClass` and `AnyObject`, and receive messages as `AnyClass` and
3 | // `AnyObject` existentials.
4 | //
5 | // These `@objc` protocols host the method signatures so that they can be used
6 | // with `AnyObject`.
7 |
8 | import Foundation
9 |
10 | internal let NSInvocation: AnyClass = NSClassFromString("NSInvocation")!
11 | internal let NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")!
12 |
13 | // Signatures defined in `@objc` protocols would be available for ObjC message
14 | // sending via `AnyObject`.
15 | @objc internal protocol ObjCClassReporting {
16 | // An alias for `-class`, which is unavailable in Swift.
17 | @objc(class)
18 | var objcClass: AnyClass! { get }
19 |
20 | @objc(methodSignatureForSelector:)
21 | func objcMethodSignature(for selector: Selector) -> AnyObject
22 | }
23 |
24 | // Methods of `NSInvocation`.
25 | @objc internal protocol ObjCInvocation {
26 | @objc(setSelector:)
27 | func objcSetSelector(_ selector: Selector)
28 |
29 | @objc(methodSignature)
30 | var objcMethodSignature: AnyObject { get }
31 |
32 | @objc(getArgument:atIndex:)
33 | func objcCopy(to buffer: UnsafeMutableRawPointer?, forArgumentAt index: Int)
34 |
35 | @objc(invoke)
36 | func objcInvoke()
37 |
38 | @objc(invocationWithMethodSignature:)
39 | static func objcInvocation(withMethodSignature signature: AnyObject) -> AnyObject
40 | }
41 |
42 | // Methods of `NSMethodSignature`.
43 | @objc internal protocol ObjCMethodSignature {
44 | @objc(numberOfArguments)
45 | var objcNumberOfArguments: UInt { get }
46 |
47 | @objc(getArgumentTypeAtIndex:)
48 | func objcArgumentType(at index: UInt) -> UnsafePointer
49 |
50 | @objc(signatureWithObjCTypes:)
51 | static func objcSignature(withObjCTypes typeEncoding: UnsafePointer) -> AnyObject
52 | }
53 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UITabBarItemSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UITabBarSpec: QuickSpec {
9 | override func spec() {
10 | var tabBarItem: UITabBarItem!
11 | weak var _tabBarItem: UITabBarItem?
12 |
13 | beforeEach {
14 | tabBarItem = UITabBarItem(tabBarSystemItem: .downloads, tag: 1)
15 | _tabBarItem = tabBarItem
16 | }
17 |
18 | afterEach {
19 | tabBarItem = nil
20 | expect(_tabBarItem).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its badge value") {
24 | let firstChange = "first"
25 | let secondChange = "second"
26 |
27 | tabBarItem.badgeValue = ""
28 |
29 | let (pipeSignal, observer) = Signal.pipe()
30 | tabBarItem.reactive.badgeValue <~ pipeSignal
31 |
32 | observer.send(value: firstChange)
33 | expect(tabBarItem.badgeValue) == firstChange
34 |
35 | observer.send(value: secondChange)
36 | expect(tabBarItem.badgeValue) == secondChange
37 |
38 | observer.send(value: nil)
39 | expect(tabBarItem.badgeValue).to(beNil())
40 | }
41 |
42 | if #available(iOS 10, *), #available(tvOS 10, *) {
43 | it("should accept changes from bindings to its badge color value") {
44 | let firstChange: UIColor = .red
45 | let secondChange: UIColor = .green
46 |
47 | tabBarItem.badgeColor = .blue
48 |
49 | let (pipeSignal, observer) = Signal.pipe()
50 | tabBarItem.reactive.badgeColor <~ pipeSignal
51 |
52 | observer.send(value: firstChange)
53 | expect(tabBarItem.badgeColor) == firstChange
54 |
55 | observer.send(value: secondChange)
56 | expect(tabBarItem.badgeColor) == secondChange
57 |
58 | observer.send(value: nil)
59 | expect(tabBarItem.badgeColor).to(beNil())
60 | }
61 | }
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/AssociationSpec.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import Nimble
4 | import Quick
5 | @testable import ReactiveCocoa
6 |
7 | class AssociationSpec: QuickSpec {
8 | override func spec() {
9 | it("should create and retrieve the same object") {
10 | let object = NSObject()
11 | let token = NSObject()
12 |
13 | var counter = 0
14 |
15 | func retrieve() -> NSObject {
16 | return object.reactive.associatedValue { _ in
17 | counter += 1
18 | return token
19 | }
20 | }
21 |
22 | expect(counter) == 0
23 |
24 | let firstResult = retrieve()
25 | expect(counter) == 1
26 |
27 | let secondResult = retrieve()
28 | expect(counter) == 1
29 |
30 | expect(firstResult).to(beIdenticalTo(secondResult))
31 | expect(firstResult).to(beIdenticalTo(token))
32 | }
33 |
34 | it("should support multiple keys per object") {
35 | let object = NSObject()
36 | let token = NSObject()
37 | let token2 = NSObject()
38 |
39 | var counter = 0
40 | var counter2 = 0
41 |
42 |
43 | func retrieve() -> NSObject {
44 | return object.reactive.associatedValue { _ in
45 | counter += 1
46 | return token
47 | }
48 | }
49 |
50 | func retrieve2() -> NSObject {
51 | return object.reactive.associatedValue(forKey: "customKey") { _ in
52 | counter2 += 1
53 | return token2
54 | }
55 | }
56 |
57 | expect(counter) == 0
58 |
59 | let firstResult = retrieve()
60 | expect(counter) == 1
61 |
62 | let secondResult = retrieve()
63 | expect(counter) == 1
64 |
65 | expect(firstResult).to(beIdenticalTo(secondResult))
66 | expect(firstResult).to(beIdenticalTo(token))
67 |
68 | expect(counter2) == 0
69 |
70 | let thirdResult = retrieve2()
71 | expect(counter2) == 1
72 |
73 | let forthResult = retrieve2()
74 | expect(counter2) == 1
75 |
76 | expect(thirdResult).to(beIdenticalTo(forthResult))
77 | expect(thirdResult).to(beIdenticalTo(token2))
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UISwitchSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(tvOS)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UISwitchSpec: QuickSpec {
9 | override func spec() {
10 | var toggle: UISwitch!
11 | weak var _toggle: UISwitch?
12 |
13 | beforeEach {
14 | toggle = UISwitch(frame: .zero)
15 | _toggle = toggle
16 | }
17 |
18 | afterEach {
19 | toggle = nil
20 | if #available(*, iOS 10.2) {
21 | expect(_toggle).to(beNil())
22 | }
23 | }
24 |
25 | it("should accept changes from bindings to its `isOn` state") {
26 | toggle.isOn = false
27 |
28 | let (pipeSignal, observer) = Signal.pipe()
29 | toggle.reactive.isOn <~ SignalProducer(pipeSignal)
30 |
31 | observer.send(value: true)
32 | expect(toggle.isOn) == true
33 |
34 | observer.send(value: false)
35 | expect(toggle.isOn) == false
36 | }
37 |
38 | it("should emit user initiated changes to its `isOn` state") {
39 | var latestValue: Bool?
40 | toggle.reactive.isOnValues.observeValues { latestValue = $0 }
41 |
42 | toggle.isOn = true
43 | toggle.sendActions(for: .valueChanged)
44 | expect(latestValue!) == true
45 | }
46 |
47 | it("should execute the `toggled` action upon receiving a `valueChanged` action message.") {
48 | toggle.isOn = false
49 | toggle.isEnabled = true
50 | toggle.isUserInteractionEnabled = true
51 |
52 | let isOn = MutableProperty(false)
53 | let action = Action { isOn in
54 | return SignalProducer(value: isOn)
55 | }
56 | isOn <~ SignalProducer(action.values)
57 |
58 | toggle.reactive.toggled = CocoaAction(action) { return $0.isOn }
59 |
60 | expect(isOn.value) == false
61 |
62 | toggle.isOn = true
63 | toggle.sendActions(for: .valueChanged)
64 | expect(isOn.value) == true
65 |
66 | toggle.isOn = false
67 | toggle.sendActions(for: .valueChanged)
68 | expect(isOn.value) == false
69 |
70 | }
71 | }
72 | }
73 | #endif
74 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode10.2
3 | before_install: true
4 | install: true
5 | branches:
6 | only:
7 | - master
8 | - /^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/
9 | - /^hotfix-(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/
10 | cache:
11 | directories:
12 | - $HOME/Library/Caches/org.carthage.CarthageKit/dependencies
13 | - Carthage/Build
14 | jobs:
15 | include:
16 | - stage: unit tests
17 | osx_image: xcode10.2
18 | script:
19 | - XCODE_SCHEME=ReactiveCocoa-macOS
20 | XCODE_SDK=macosx
21 | XCODE_ACTION="build-for-testing test-without-building"
22 | XCODE_DESTINATION="arch=x86_64"
23 | XCODE_PLAYGROUND_TARGET="x86_64-apple-macosx10.10"
24 | PLAYGROUND="ReactiveCocoa-macOS.playground"
25 | script/build
26 | - XCODE_SCHEME=ReactiveCocoa-iOS
27 | XCODE_SDK=iphonesimulator
28 | XCODE_ACTION="build-for-testing test-without-building"
29 | XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 6s"
30 | script/build
31 | - XCODE_SCHEME=ReactiveCocoa-tvOS
32 | XCODE_SDK=appletvsimulator
33 | XCODE_ACTION="build-for-testing test-without-building"
34 | XCODE_DESTINATION="platform=tvOS Simulator,name=Apple TV"
35 | script/build
36 | - XCODE_SCHEME=ReactiveCocoa-watchOS
37 | XCODE_SDK=watchsimulator
38 | XCODE_ACTION=build
39 | XCODE_DESTINATION="platform=watchOS Simulator,name=Apple Watch Series 3 - 38mm"
40 | script/build
41 | - stage: package manager tests
42 | install: gem update cocoapods
43 | script:
44 | - pod repo update
45 | - pod lib lint ReactiveCocoa.podspec --use-libraries
46 | - pod lib lint ReactiveMapKit.podspec --use-libraries --include-podspecs=ReactiveCocoa.podspec
47 | env:
48 | - JOB=PODSPEC
49 | - script: carthage build --cache-builds --no-skip-current
50 | - script: swift build
51 |
--------------------------------------------------------------------------------
/ReactiveCocoa/NSObject+BindingTarget.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | extension Reactive where Base: AnyObject {
5 | /// Creates a binding target which uses the lifetime of the object, and
6 | /// weakly references the object so that the supplied `action` is triggered
7 | /// only if the object has not deinitialized.
8 | ///
9 | /// - parameters:
10 | /// - scheduler: An optional scheduler that the binding target uses. If it
11 | /// is not specified, a UI scheduler would be used.
12 | /// - action: The action to consume values from the bindings.
13 | ///
14 | /// - returns: A binding target that holds no strong references to the
15 | /// object.
16 | public func makeBindingTarget(on scheduler: Scheduler = UIScheduler(), _ action: @escaping (Base, U) -> Void) -> BindingTarget {
17 | return BindingTarget(on: scheduler, lifetime: Lifetime.of(base)) { [weak base = self.base] value in
18 | if let base = base {
19 | action(base, value)
20 | }
21 | }
22 | }
23 | }
24 |
25 | #if swift(>=3.2)
26 | extension Reactive where Base: AnyObject {
27 | /// Creates a binding target that writes to the object with the given key path on a
28 | /// `UIScheduler`.
29 | ///
30 | /// - parameters:
31 | /// - keyPath: The key path to be written to.
32 | ///
33 | /// - returns: A binding target.
34 | public subscript(keyPath: ReferenceWritableKeyPath) -> BindingTarget {
35 | return BindingTarget(on: UIScheduler(), lifetime: Lifetime.of(base), object: base, keyPath: keyPath)
36 | }
37 |
38 | /// Creates a binding target that writes to the object with the given key path.
39 | ///
40 | /// - parameters:
41 | /// - keyPath: The key path to be written to.
42 | /// - scheduler: The scheduler to perform the write on.
43 | ///
44 | /// - returns: A binding target.
45 | public subscript(keyPath: ReferenceWritableKeyPath, on scheduler: Scheduler) -> BindingTarget {
46 | return BindingTarget(on: scheduler, lifetime: Lifetime.of(base), object: base, keyPath: keyPath)
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UILabelSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import ReactiveSwift
3 | import ReactiveCocoa
4 | import UIKit
5 | import Quick
6 | import Nimble
7 |
8 | class UILabelSpec: QuickSpec {
9 | override func spec() {
10 | var label: UILabel!
11 | weak var _label: UILabel?
12 |
13 | beforeEach {
14 | label = UILabel(frame: .zero)
15 | _label = label
16 | }
17 |
18 | afterEach {
19 | label = nil
20 | expect(_label).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its text value") {
24 | let firstChange = "first"
25 | let secondChange = "second"
26 |
27 | label.text = ""
28 |
29 | let (pipeSignal, observer) = Signal.pipe()
30 | label.reactive.text <~ SignalProducer(pipeSignal)
31 |
32 | observer.send(value: firstChange)
33 | expect(label.text) == firstChange
34 |
35 | observer.send(value: secondChange)
36 | expect(label.text) == secondChange
37 |
38 | observer.send(value: nil)
39 | expect(label.text).to(beNil())
40 | }
41 |
42 | it("should accept changes from bindings to its attributed text value") {
43 | let firstChange = NSAttributedString(string: "first")
44 | let secondChange = NSAttributedString(string: "second")
45 |
46 | label.attributedText = NSAttributedString(string: "")
47 |
48 | let (pipeSignal, observer) = Signal.pipe()
49 | label.reactive.attributedText <~ SignalProducer(pipeSignal)
50 |
51 | observer.send(value: firstChange)
52 | expect(label.attributedText) == firstChange
53 |
54 | observer.send(value: secondChange)
55 | expect(label.attributedText) == secondChange
56 | }
57 |
58 | it("should accept changes from bindings to its text color value") {
59 | let firstChange = UIColor.red
60 | let secondChange = UIColor.black
61 |
62 | let label = UILabel(frame: .zero)
63 |
64 | let (pipeSignal, observer) = Signal.pipe()
65 | label.textColor = UIColor.black
66 | label.reactive.textColor <~ SignalProducer(pipeSignal)
67 |
68 | observer.send(value: firstChange)
69 | expect(label.textColor) == firstChange
70 |
71 | observer.send(value: secondChange)
72 | expect(label.textColor) == secondChange
73 | }
74 | }
75 | }
76 | #endif
77 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/UIKit/UIButtonSpec.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import Quick
3 | import Nimble
4 | import ReactiveSwift
5 | import ReactiveCocoa
6 | import UIKit
7 |
8 | class UIButtonSpec: QuickSpec {
9 | override func spec() {
10 | var button: UIButton!
11 | weak var _button: UIButton?
12 |
13 | beforeEach {
14 | button = UIButton(frame: .zero)
15 | _button = button
16 | }
17 |
18 | afterEach {
19 | button = nil
20 | expect(_button).to(beNil())
21 | }
22 |
23 | it("should accept changes from bindings to its titles under different states") {
24 | let firstTitle = "First title"
25 | let secondTitle = "Second title"
26 |
27 | let (pipeSignal, observer) = Signal.pipe()
28 | button.reactive.title <~ SignalProducer(pipeSignal)
29 | button.setTitle("", for: .selected)
30 | button.setTitle("", for: .highlighted)
31 |
32 | observer.send(value: firstTitle)
33 | expect(button.title(for: UIControl.State())) == firstTitle
34 | expect(button.title(for: .highlighted)) == ""
35 | expect(button.title(for: .selected)) == ""
36 |
37 | observer.send(value: secondTitle)
38 | expect(button.title(for: UIControl.State())) == secondTitle
39 | expect(button.title(for: .highlighted)) == ""
40 | expect(button.title(for: .selected)) == ""
41 | }
42 |
43 | let pressedTest: (UIButton, UIControl.Event) -> Void = { button, event in
44 | button.isEnabled = true
45 | button.isUserInteractionEnabled = true
46 |
47 | let pressed = MutableProperty(false)
48 | let action = Action<(), Bool, Never> { _ in
49 | SignalProducer(value: true)
50 | }
51 |
52 | pressed <~ SignalProducer(action.values)
53 |
54 | button.reactive.pressed = CocoaAction(action)
55 | expect(pressed.value) == false
56 |
57 | button.sendActions(for: event)
58 |
59 | expect(pressed.value) == true
60 | }
61 |
62 | if #available(iOS 9.0, tvOS 9.0, *) {
63 | it("should execute the `pressed` action upon receiving a `primaryActionTriggered` action message.") {
64 | pressedTest(button, .primaryActionTriggered)
65 | }
66 | } else {
67 | it("should execute the `pressed` action upon receiving a `touchUpInside` action message.") {
68 | pressedTest(button, .touchUpInside)
69 | }
70 | }
71 | }
72 | }
73 | #endif
74 |
--------------------------------------------------------------------------------
/ReactiveCocoaTests/SignalProducerNimbleMatchers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignalProducerNimbleMatchers.swift
3 | // ReactiveSwift
4 | //
5 | // Created by Javier Soto on 1/25/15.
6 | // Copyright (c) 2015 GitHub. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | import ReactiveSwift
12 | import Nimble
13 |
14 | public func sendValue(_ value: T?, sendError: E?, complete: Bool) -> Predicate> {
15 | return sendValues(value.map { [$0] } ?? [], sendError: sendError, complete: complete)
16 | }
17 |
18 | public func sendValues(_ values: [T], sendError maybeSendError: E?, complete: Bool) -> Predicate> {
19 | return Predicate> { actualExpression in
20 | precondition(maybeSendError == nil || !complete, "Signals can't both send an error and complete")
21 | guard let signalProducer = try actualExpression.evaluate() else {
22 | let message = ExpectationMessage.fail("The SignalProducer was not created.")
23 | .appendedBeNilHint()
24 | return PredicateResult(status: .fail, message: message)
25 | }
26 |
27 | var sentValues: [T] = []
28 | var sentError: E?
29 | var signalCompleted = false
30 |
31 | signalProducer.start { event in
32 | switch event {
33 | case let .value(value):
34 | sentValues.append(value)
35 | case .completed:
36 | signalCompleted = true
37 | case let .failed(error):
38 | sentError = error
39 | default:
40 | break
41 | }
42 | }
43 |
44 | if sentValues != values {
45 | let message = ExpectationMessage.expectedCustomValueTo(
46 | "send values <\(values)>",
47 | "<\(sentValues)>"
48 | )
49 | return PredicateResult(status: .doesNotMatch, message: message)
50 | }
51 |
52 | if sentError != maybeSendError {
53 | let message = ExpectationMessage.expectedCustomValueTo(
54 | "send error <\(String(describing: maybeSendError))>",
55 | "<\(String(describing: sentError))>"
56 | )
57 | return PredicateResult(status: .doesNotMatch, message: message)
58 | }
59 |
60 | let completeMessage = complete ?
61 | "complete, but the producer did not complete" :
62 | "not to complete, but the producer completed"
63 | let message = ExpectationMessage.expectedTo(completeMessage)
64 | return PredicateResult(bool: signalCompleted == complete, message: message)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ReactiveCocoa/UIKit/UITextField.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit) && !os(watchOS)
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | extension Reactive where Base: UITextField {
6 | /// Sets the text of the text field.
7 | public var text: BindingTarget {
8 | return makeBindingTarget { $0.text = $1 }
9 | }
10 |
11 | /// A signal of text values emitted by the text field upon end of editing.
12 | ///
13 | /// - note: To observe text values that change on all editing events,
14 | /// see `continuousTextValues`.
15 | public var textValues: Signal {
16 | return mapControlEvents([.editingDidEnd, .editingDidEndOnExit]) { $0.text ?? "" }
17 | }
18 |
19 | /// A signal of text values emitted by the text field upon any changes.
20 | ///
21 | /// - note: To observe text values only when editing ends, see `textValues`.
22 | public var continuousTextValues: Signal {
23 | return mapControlEvents(.allEditingEvents) { $0.text ?? "" }
24 | }
25 |
26 | /// Sets the attributed text of the text field.
27 | public var attributedText: BindingTarget {
28 | return makeBindingTarget { $0.attributedText = $1 }
29 | }
30 |
31 | /// Sets the placeholder text of the text field.
32 | public var placeholder: BindingTarget {
33 | return makeBindingTarget { $0.placeholder = $1 }
34 | }
35 |
36 | /// Sets the textColor of the text field.
37 | public var textColor: BindingTarget {
38 | return makeBindingTarget { $0.textColor = $1 }
39 | }
40 |
41 | /// A signal of attributed text values emitted by the text field upon end of editing.
42 | ///
43 | /// - note: To observe attributed text values that change on all editing events,
44 | /// see `continuousAttributedTextValues`.
45 | public var attributedTextValues: Signal {
46 | return mapControlEvents([.editingDidEnd, .editingDidEndOnExit]) { $0.attributedText ?? NSAttributedString() }
47 | }
48 |
49 | /// A signal of attributed text values emitted by the text field upon any changes.
50 | ///
51 | /// - note: To observe attributed text values only when editing ends, see `attributedTextValues`.
52 | public var continuousAttributedTextValues: Signal {
53 | return mapControlEvents(.allEditingEvents) { $0.attributedText ?? NSAttributedString() }
54 | }
55 |
56 | /// Sets the secure text entry attribute on the text field.
57 | public var isSecureTextEntry: BindingTarget {
58 | return makeBindingTarget { $0.isSecureTextEntry = $1 }
59 | }
60 | }
61 | #endif
62 |
--------------------------------------------------------------------------------
/ReactiveCocoa/CocoaAction.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | /// CocoaAction wraps an `Action` for use by a UI control (such as `NSControl` or
5 | /// `UIControl`).
6 | public final class CocoaAction: NSObject {
7 | /// The selector for message senders.
8 | public static var selector: Selector {
9 | return #selector(CocoaAction.execute(_:))
10 | }
11 |
12 | /// Whether the action is enabled.
13 | ///
14 | /// This property will only change on the main thread.
15 | public let isEnabled: Property
16 |
17 | /// Whether the action is executing.
18 | ///
19 | /// This property will only change on the main thread.
20 | public let isExecuting: Property
21 |
22 | private let _execute: (Sender) -> Void
23 |
24 | /// Initialize a CocoaAction that invokes the given Action by mapping the
25 | /// sender to the input type of the Action.
26 | ///
27 | /// - parameters:
28 | /// - action: The Action.
29 | /// - inputTransform: A closure that maps Sender to the input type of the
30 | /// Action.
31 | public init(_ action: Action, _ inputTransform: @escaping (Sender) -> Input) {
32 | _execute = { sender in
33 | let producer = action.apply(inputTransform(sender))
34 | producer.start()
35 | }
36 |
37 | isEnabled = Property(initial: action.isEnabled.value,
38 | then: action.isEnabled.producer.observe(on: UIScheduler()))
39 | isExecuting = Property(initial: action.isExecuting.value,
40 | then: action.isExecuting.producer.observe(on: UIScheduler()))
41 |
42 | super.init()
43 | }
44 |
45 | /// Initialize a CocoaAction that invokes the given Action.
46 | ///
47 | /// - parameters:
48 | /// - action: The Action.
49 | public convenience init