From 2aebe1499f3f484605d239be08e87f7beeab5d8c Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Sat, 13 Feb 2016 21:25:31 +0100 Subject: [PATCH] Adds `UIBindingObserver`. --- Rx.xcodeproj/project.pbxproj | 10 +++ .../Common/CocoaUnits/UIBindingObserver.swift | 63 ++++++++++++++++++ RxCocoa/Common/DelegateProxyType.swift | 2 +- RxCocoa/OSX/NSButton+Rx.swift | 13 ++-- RxCocoa/OSX/NSControl+Rx.swift | 48 +++++--------- RxCocoa/OSX/NSImageView+Rx.swift | 36 ++++------- RxCocoa/OSX/NSSlider+Rx.swift | 14 ++-- RxCocoa/OSX/NSTextField+Rx.swift | 20 ++---- RxCocoa/OSX/NSView+Rx.swift | 32 ++-------- RxCocoa/iOS/UIActivityIndicatorView+Rx.swift | 21 ++---- RxCocoa/iOS/UIBarButtonItem+Rx.swift | 16 +---- RxCocoa/iOS/UIControl+Rx.swift | 64 +++++++------------ RxCocoa/iOS/UIDatePicker+Rx.swift | 13 ++-- RxCocoa/iOS/UIImageView+Rx.swift | 36 ++++------- RxCocoa/iOS/UILabel+Rx.swift | 32 ++-------- RxCocoa/iOS/UIRefreshControl+Rx.swift | 22 ++----- RxCocoa/iOS/UIScrollView+Rx.swift | 17 ++--- RxCocoa/iOS/UISearchBar+Rx.swift | 15 ++--- RxCocoa/iOS/UISegmentedControl+Rx.swift | 13 ++-- RxCocoa/iOS/UISlider+Rx.swift | 13 ++-- RxCocoa/iOS/UIStepper+Rx.swift | 13 ++-- RxCocoa/iOS/UISwitch+Rx.swift | 13 ++-- RxCocoa/iOS/UITextField+Rx.swift | 13 ++-- RxCocoa/iOS/UITextView+Rx.swift | 15 ++--- RxCocoa/iOS/UIView+Rx.swift | 32 ++-------- .../UINavigationController+Extensions.swift | 23 +++---- .../UIImageView+DownloadableImage.swift | 41 ++++-------- 27 files changed, 281 insertions(+), 369 deletions(-) create mode 100644 RxCocoa/Common/CocoaUnits/UIBindingObserver.swift diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index c5fc008d..6734661c 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -912,6 +912,10 @@ C8FA89201C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; C8FA89211C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; C8FA89221C30424100CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; + C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; CB255BD71BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; CB255BD81BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; CB255BD91BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; @@ -1639,6 +1643,7 @@ C8FA89131C30405400CD3A17 /* VirtualTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeScheduler.swift; sourceTree = ""; }; C8FA89161C30409900CD3A17 /* HistoricalScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalScheduler.swift; sourceTree = ""; }; C8FA891B1C30412A00CD3A17 /* HistoricalSchedulerTimeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalSchedulerTimeConverter.swift; sourceTree = ""; }; + C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = ""; }; CB255BD61BC46A9C00798A4C /* RetryWhen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryWhen.swift; sourceTree = ""; }; CB30D9E81BF0E3500084C1C0 /* SingleAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleAsync.swift; sourceTree = ""; }; CB883B3A1BE24355000AC2EE /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; @@ -2119,6 +2124,7 @@ C80DDE8C1BCE69BA006A1832 /* Driver */, C80D33931B922FB00014629D /* ControlEvent.swift */, C80D33941B922FB00014629D /* ControlProperty.swift */, + C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */, ); path = CocoaUnits; sourceTree = ""; @@ -3273,6 +3279,7 @@ C88254301B8A752B00B02D69 /* UISearchBar+Rx.swift in Sources */, C88254181B8A752B00B02D69 /* ItemEvents.swift in Sources */, C8DB96881BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C882541B1B8A752B00B02D69 /* RxTableViewDataSourceType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3291,6 +3298,7 @@ C8DB967F1BF7496C0084BD53 /* KVORepresentable.swift in Sources */, C8093EFE1B8A732E0088E94D /* RxTarget.swift in Sources */, C8093ED21B8A732E0088E94D /* _RX.m in Sources */, + C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C8093EFC1B8A732E0088E94D /* RxCocoa.swift in Sources */, C8DB968E1BF7595D0084BD53 /* KVORepresentable+Swift.swift in Sources */, C80D33991B922FB00014629D /* ControlEvent.swift in Sources */, @@ -4162,6 +4170,7 @@ C8F0C03B1BBBFBB9001B112F /* UISearchBar+Rx.swift in Sources */, C8F0C03C1BBBFBB9001B112F /* ItemEvents.swift in Sources */, C8DB968B1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C8F0C03D1BBBFBB9001B112F /* RxTableViewDataSourceType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4253,6 +4262,7 @@ D203C4FD1BB9C53700D02D00 /* RxSearchBarDelegateProxy.swift in Sources */, D2138C8A1BB9BEBE00339B5C /* Logging.swift in Sources */, C8DB968A1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, D203C50F1BB9C53E00D02D00 /* UIStepper+Rx.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift b/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift new file mode 100644 index 00000000..04b46037 --- /dev/null +++ b/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift @@ -0,0 +1,63 @@ +// +// UIBindingObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/7/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation +#if !RX_NO_MODULE + import RxSwift +#endif + +/** +Observer that enforces interface binding rules: + * can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged) + * ensures binding is performed on main thread + +`InterfaceBindingObserver` doesn't retain target interface and in case owned interface element is released, element isn't bound. +*/ +public class UIBindingObserver : ObserverType { + public typealias E = Value + + weak var UIElement: UIElementType? + + let binding: (UIElementType, Value) -> Void + + /** + Initializes `ViewBindingObserver` using + */ + public init(UIElement: UIElementType, binding: (UIElementType, Value) -> Void) { + self.UIElement = UIElement + self.binding = binding + } + + /** + Binds next element to owner view as described in `binding`. + */ + public func on(event: Event) { + MainScheduler.ensureExecutingOnScheduler() + + switch event { + case .Next(let element): + if let view = self.UIElement { + binding(view, element) + } + case .Error(let error): + bindingErrorToInterface(error) + break + case .Completed: + break + } + } + + /** + Erases type of observer. + + - returns: type erased observer. + */ + public func asObserver() -> AnyObserver { + return AnyObserver(eventHandler: on) + } +} \ No newline at end of file diff --git a/RxCocoa/Common/DelegateProxyType.swift b/RxCocoa/Common/DelegateProxyType.swift index 57c92ca6..9db51bc8 100644 --- a/RxCocoa/Common/DelegateProxyType.swift +++ b/RxCocoa/Common/DelegateProxyType.swift @@ -223,7 +223,7 @@ extension ObservableType { -> Disposable { let proxy = proxyForObject(P.self, object) let disposable = installDelegate(proxy, delegate: dataSource, retainDelegate: retainDataSource, onProxyForObject: object) - + let subscription = self.asObservable() // source can't ever end, otherwise it will release the subscriber .concat(Observable.never()) diff --git a/RxCocoa/OSX/NSButton+Rx.swift b/RxCocoa/OSX/NSButton+Rx.swift index 0c6e5f6c..c18492a6 100644 --- a/RxCocoa/OSX/NSButton+Rx.swift +++ b/RxCocoa/OSX/NSButton+Rx.swift @@ -25,10 +25,13 @@ extension NSButton { Reactive wrapper for `state` property`. */ public var rx_state: ControlProperty { - return rx_value(getter: { [weak self] in - return self?.state ?? 0 - }, setter: { [weak self] state in - self?.state = state - }) + return NSButton.rx_value( + self, + getter: { control in + return control.state + }, setter: { control, state in + control.state = state + } + ) } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSControl+Rx.swift b/RxCocoa/OSX/NSControl+Rx.swift index 7380a2e2..d9dc98e0 100644 --- a/RxCocoa/OSX/NSControl+Rx.swift +++ b/RxCocoa/OSX/NSControl+Rx.swift @@ -43,57 +43,41 @@ extension NSControl { return ControlEvent(events: source) } - func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { + static func rx_value(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty { MainScheduler.ensureExecutingOnScheduler() - let source = rx_lazyInstanceObservable(&rx_value_key) { () -> Observable in - return Observable.create { [weak self] observer in - guard let control = self else { + let source = control.rx_lazyInstanceObservable(&rx_value_key) { () -> Observable in + return Observable.create { [weak weakControl = control] (observer: AnyObserver) in + guard let control = weakControl else { observer.on(.Completed) return NopDisposable.instance } - observer.on(.Next(getter())) + observer.on(.Next(getter(control))) - let observer = ControlTarget(control: control) { control in - observer.on(.Next(getter())) + let observer = ControlTarget(control: control) { _ in + if let control = weakControl { + observer.on(.Next(getter(control))) + } } return observer } - .distinctUntilChanged() - .takeUntil(self.rx_deallocated) + .distinctUntilChanged() + .takeUntil(control.rx_deallocated) } + let bindingObserver = UIBindingObserver(UIElement: control, binding: setter) - return ControlProperty(values: source, valueSink: AnyObserver { event in - switch event { - case .Next(let value): - setter(value) - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } /** Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { (owner, value) in + owner.enabled = value + }.asObserver() } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSImageView+Rx.swift b/RxCocoa/OSX/NSImageView+Rx.swift index 0ff16138..a494152c 100644 --- a/RxCocoa/OSX/NSImageView+Rx.swift +++ b/RxCocoa/OSX/NSImageView+Rx.swift @@ -27,31 +27,21 @@ extension NSImageView { - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...) */ public func rx_imageAnimated(transitionType: String?) -> AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - if let transitionType = transitionType { - if value != nil { - let transition = CATransition() - transition.duration = 0.25 - transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - transition.type = transitionType - self?.layer?.addAnimation(transition, forKey: kCATransition) - } + return UIBindingObserver(UIElement: self) { control, value in + if let transitionType = transitionType { + if value != nil { + let transition = CATransition() + transition.duration = 0.25 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = transitionType + control.layer?.addAnimation(transition, forKey: kCATransition) } - else { - self?.layer?.removeAllAnimations() - } - self?.image = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break } - } + else { + control.layer?.removeAllAnimations() + } + control.image = value + }.asObserver() } } diff --git a/RxCocoa/OSX/NSSlider+Rx.swift b/RxCocoa/OSX/NSSlider+Rx.swift index bbe8f33f..b1d9edd9 100644 --- a/RxCocoa/OSX/NSSlider+Rx.swift +++ b/RxCocoa/OSX/NSSlider+Rx.swift @@ -18,11 +18,15 @@ extension NSSlider { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - return self?.doubleValue ?? 0 - }, setter: { [weak self] value in - self?.doubleValue = value - }) + return NSControl.rx_value( + self, + getter: { control in + return control.doubleValue + }, + setter: { control, value in + control.doubleValue = value + } + ) } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSTextField+Rx.swift b/RxCocoa/OSX/NSTextField+Rx.swift index d3d6f831..568cf4de 100644 --- a/RxCocoa/OSX/NSTextField+Rx.swift +++ b/RxCocoa/OSX/NSTextField+Rx.swift @@ -105,20 +105,12 @@ extension NSTextField { let source = Observable.deferred { [weak self] in delegate.textSubject.startWith(self?.stringValue ?? "") }.takeUntil(rx_deallocated) - - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.stringValue = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - }) + + let observer = UIBindingObserver(UIElement: self) { control, value in + control.stringValue = value + } + + return ControlProperty(values: source, valueSink: observer.asObserver()) } } diff --git a/RxCocoa/OSX/NSView+Rx.swift b/RxCocoa/OSX/NSView+Rx.swift index 04bcc221..57a1e005 100644 --- a/RxCocoa/OSX/NSView+Rx.swift +++ b/RxCocoa/OSX/NSView+Rx.swift @@ -17,37 +17,17 @@ extension NSView { Bindable sink for `hidden` property. */ public var rx_hidden: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.hidden = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, value in + view.hidden = value + }.asObserver() } /** Bindable sink for `alphaValue` property. */ public var rx_alpha: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.alphaValue = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, value in + view.alphaValue = value + }.asObserver() } } \ No newline at end of file diff --git a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift index 14a10277..f1a76967 100644 --- a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift +++ b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift @@ -19,22 +19,13 @@ extension UIActivityIndicatorView { Bindable sink for `startAnimating()`, `stopAnimating()` methods. */ public var rx_animating: AnyObserver { - return AnyObserver {event in - MainScheduler.ensureExecutingOnScheduler() - - switch (event) { - case .Next(let value): - if value { - self.startAnimating() - } else { - self.stopAnimating() - } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break + return UIBindingObserver(UIElement: self) { activityIndicator, active in + if active { + self.startAnimating() + } else { + self.stopAnimating() } - } + }.asObserver() } } diff --git a/RxCocoa/iOS/UIBarButtonItem+Rx.swift b/RxCocoa/iOS/UIBarButtonItem+Rx.swift index 0f048016..ef45e73d 100644 --- a/RxCocoa/iOS/UIBarButtonItem+Rx.swift +++ b/RxCocoa/iOS/UIBarButtonItem+Rx.swift @@ -21,19 +21,9 @@ extension UIBarButtonItem { Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { UIElement, value in + UIElement.enabled = value + }.asObserver() } /** diff --git a/RxCocoa/iOS/UIControl+Rx.swift b/RxCocoa/iOS/UIControl+Rx.swift index 3e7f356f..9a634ff9 100644 --- a/RxCocoa/iOS/UIControl+Rx.swift +++ b/RxCocoa/iOS/UIControl+Rx.swift @@ -20,19 +20,9 @@ extension UIControl { Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { control, value in + control.enabled = value + }.asObserver() } /** @@ -62,39 +52,31 @@ extension UIControl { return ControlEvent(events: source) } - func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { - let source: Observable = Observable.create { [weak self] observer in - guard let control = self else { - observer.on(.Completed) - return NopDisposable.instance - } + static func rx_value(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty { + let source: Observable = Observable.create { [weak weakControl = control] observer in + guard let control = weakControl else { + observer.on(.Completed) + return NopDisposable.instance + } - observer.on(.Next(getter())) + observer.on(.Next(getter(control))) - let controlTarget = ControlTarget(control: control, controlEvents: [.AllEditingEvents, .ValueChanged]) { control in - observer.on(.Next(getter())) + let controlTarget = ControlTarget(control: control, controlEvents: [.AllEditingEvents, .ValueChanged]) { _ in + if let control = weakControl { + observer.on(.Next(getter(control))) + } + } + + return AnonymousDisposable { + controlTarget.dispose() + } } - - return AnonymousDisposable { - controlTarget.dispose() - } - } .distinctUntilChanged() - .takeUntil(rx_deallocated) - - return ControlProperty(values: source, valueSink: AnyObserver { event in - MainScheduler.ensureExecutingOnScheduler() + .takeUntil(control.rx_deallocated) - switch event { - case .Next(let value): - setter(value) - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - }) + let bindingObserver = UIBindingObserver(UIElement: control, binding: setter) + + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UIDatePicker+Rx.swift b/RxCocoa/iOS/UIDatePicker+Rx.swift index 36f594c1..a09c9c6f 100644 --- a/RxCocoa/iOS/UIDatePicker+Rx.swift +++ b/RxCocoa/iOS/UIDatePicker+Rx.swift @@ -20,11 +20,14 @@ extension UIDatePicker { Reactive wrapper for `date` property. */ public var rx_date: ControlProperty { - return rx_value(getter: { [weak self] in - self?.date ?? NSDate() - }, setter: { [weak self] value in - self?.date = value - }) + return UIControl.rx_value( + self, + getter: { datePicker in + datePicker.date + }, setter: { datePicker, value in + datePicker.date = value + } + ) } } diff --git a/RxCocoa/iOS/UIImageView+Rx.swift b/RxCocoa/iOS/UIImageView+Rx.swift index 5b84643e..6d0caf04 100644 --- a/RxCocoa/iOS/UIImageView+Rx.swift +++ b/RxCocoa/iOS/UIImageView+Rx.swift @@ -29,31 +29,21 @@ extension UIImageView { - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...) */ public func rx_imageAnimated(transitionType: String?) -> AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - if let transitionType = transitionType { - if value != nil { - let transition = CATransition() - transition.duration = 0.25 - transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - transition.type = transitionType - self?.layer.addAnimation(transition, forKey: kCATransition) - } + return UIBindingObserver(UIElement: self) { imageView, image in + if let transitionType = transitionType { + if image != nil { + let transition = CATransition() + transition.duration = 0.25 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = transitionType + imageView.layer.addAnimation(transition, forKey: kCATransition) } - else { - self?.layer.removeAllAnimations() - } - self?.image = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break } - } + else { + imageView.layer.removeAllAnimations() + } + imageView.image = image + }.asObserver() } } diff --git a/RxCocoa/iOS/UILabel+Rx.swift b/RxCocoa/iOS/UILabel+Rx.swift index 69aa9729..7e5822fb 100644 --- a/RxCocoa/iOS/UILabel+Rx.swift +++ b/RxCocoa/iOS/UILabel+Rx.swift @@ -20,38 +20,18 @@ extension UILabel { Bindable sink for `text` property. */ public var rx_text: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { label, text in + label.text = text + }.asObserver() } /** Bindable sink for `attributedText` property. */ public var rx_attributedText: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.attributedText = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { label, text in + label.attributedText = text + }.asObserver() } } diff --git a/RxCocoa/iOS/UIRefreshControl+Rx.swift b/RxCocoa/iOS/UIRefreshControl+Rx.swift index 8107252b..de4c6acb 100644 --- a/RxCocoa/iOS/UIRefreshControl+Rx.swift +++ b/RxCocoa/iOS/UIRefreshControl+Rx.swift @@ -19,23 +19,13 @@ extension UIRefreshControl { Bindable sink for `beginRefreshing()`, `endRefreshing()` methods. */ public var rx_refreshing: AnyObserver { - return AnyObserver {event in - MainScheduler.ensureExecutingOnScheduler() - - switch (event) { - case .Next(let value): - if value { - self.beginRefreshing() - } else { - self.endRefreshing() - } - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break + return UIBindingObserver(UIElement: self) { refreshControl, refresh in + if refresh { + self.beginRefreshing() + } else { + self.endRefreshing() } - } + }.asObserver() } } diff --git a/RxCocoa/iOS/UIScrollView+Rx.swift b/RxCocoa/iOS/UIScrollView+Rx.swift index 004dbec6..2aaf8e4d 100644 --- a/RxCocoa/iOS/UIScrollView+Rx.swift +++ b/RxCocoa/iOS/UIScrollView+Rx.swift @@ -39,17 +39,12 @@ extension UIScrollView { */ public var rx_contentOffset: ControlProperty { let proxy = proxyForObject(RxScrollViewDelegateProxy.self, self) - - return ControlProperty(values: proxy.contentOffsetSubject, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.contentOffset = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + + let bindingObserver = UIBindingObserver(UIElement: self) { scrollView, contentOffset in + scrollView.contentOffset = contentOffset + } + + return ControlProperty(values: proxy.contentOffsetSubject, valueSink: bindingObserver) } /** diff --git a/RxCocoa/iOS/UISearchBar+Rx.swift b/RxCocoa/iOS/UISearchBar+Rx.swift index 5c8c07ba..13bd2d32 100644 --- a/RxCocoa/iOS/UISearchBar+Rx.swift +++ b/RxCocoa/iOS/UISearchBar+Rx.swift @@ -40,17 +40,12 @@ extension UISearchBar { } .startWith(text) } + + let bindingObserver = UIBindingObserver(UIElement: self) { (searchBar, text: String) in + searchBar.text = text + } - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UISegmentedControl+Rx.swift b/RxCocoa/iOS/UISegmentedControl+Rx.swift index bc0f2c3f..be46f4a7 100644 --- a/RxCocoa/iOS/UISegmentedControl+Rx.swift +++ b/RxCocoa/iOS/UISegmentedControl+Rx.swift @@ -20,11 +20,14 @@ extension UISegmentedControl { Reactive wrapper for `selectedSegmentIndex` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.selectedSegmentIndex ?? 0 - }, setter: { [weak self] value in - self?.selectedSegmentIndex = value - }) + return UIControl.rx_value( + self, + getter: { segmentedControl in + segmentedControl.selectedSegmentIndex + }, setter: { segmentedControl, value in + segmentedControl.selectedSegmentIndex = value + } + ) } } diff --git a/RxCocoa/iOS/UISlider+Rx.swift b/RxCocoa/iOS/UISlider+Rx.swift index 0dabe9aa..2c02bd08 100644 --- a/RxCocoa/iOS/UISlider+Rx.swift +++ b/RxCocoa/iOS/UISlider+Rx.swift @@ -20,11 +20,14 @@ extension UISlider { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.value ?? 0.0 - }, setter: { [weak self] value in - self?.value = value - }) + return UIControl.rx_value( + self, + getter: { slider in + slider.value + }, setter: { slider, value in + slider.value = value + } + ) } } diff --git a/RxCocoa/iOS/UIStepper+Rx.swift b/RxCocoa/iOS/UIStepper+Rx.swift index 7f29d934..9dc40c5c 100644 --- a/RxCocoa/iOS/UIStepper+Rx.swift +++ b/RxCocoa/iOS/UIStepper+Rx.swift @@ -20,11 +20,14 @@ extension UIStepper { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.value ?? 0 - }, setter: { [weak self] value in - self?.value = value - }) + return UIControl.rx_value( + self, + getter: { stepper in + stepper.value + }, setter: { stepper, value in + stepper.value = value + } + ) } } diff --git a/RxCocoa/iOS/UISwitch+Rx.swift b/RxCocoa/iOS/UISwitch+Rx.swift index 50b0285c..bfc5b11f 100644 --- a/RxCocoa/iOS/UISwitch+Rx.swift +++ b/RxCocoa/iOS/UISwitch+Rx.swift @@ -20,11 +20,14 @@ extension UISwitch { Reactive wrapper for `on` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.on ?? false - }, setter: { [weak self] value in - self?.on = value - }) + return UIControl.rx_value( + self, + getter: { uiSwitch in + uiSwitch.on + }, setter: { uiSwitch, value in + uiSwitch.on = value + } + ) } } diff --git a/RxCocoa/iOS/UITextField+Rx.swift b/RxCocoa/iOS/UITextField+Rx.swift index acd00865..35f888fc 100644 --- a/RxCocoa/iOS/UITextField+Rx.swift +++ b/RxCocoa/iOS/UITextField+Rx.swift @@ -20,11 +20,14 @@ extension UITextField { Reactive wrapper for `text` property. */ public var rx_text: ControlProperty { - return rx_value(getter: { [weak self] in - self?.text ?? "" - }, setter: { [weak self] value in - self?.text = value - }) + return UIControl.rx_value( + self, + getter: { textField in + textField.text ?? "" + }, setter: { textField, value in + textField.text = value + } + ) } } diff --git a/RxCocoa/iOS/UITextView+Rx.swift b/RxCocoa/iOS/UITextView+Rx.swift index 6961e766..f21d31a9 100644 --- a/RxCocoa/iOS/UITextView+Rx.swift +++ b/RxCocoa/iOS/UITextView+Rx.swift @@ -45,17 +45,12 @@ extension UITextView { .startWith(text) .distinctUntilChanged() } + + let bindingObserver = UIBindingObserver(UIElement: self) { (textView, text: String) in + textView.text = text + } - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UIView+Rx.swift b/RxCocoa/iOS/UIView+Rx.swift index 105496f4..ea0ee82b 100644 --- a/RxCocoa/iOS/UIView+Rx.swift +++ b/RxCocoa/iOS/UIView+Rx.swift @@ -19,38 +19,18 @@ extension UIView { Bindable sink for `hidden` property. */ public var rx_hidden: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.hidden = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, hidden in + view.hidden = hidden + }.asObserver() } /** Bindable sink for `alpha` property. */ public var rx_alpha: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.alpha = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, alpha in + view.alpha = alpha + }.asObserver() } } diff --git a/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift b/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift index 02b290e3..f2ed9706 100644 --- a/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift +++ b/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift @@ -20,22 +20,15 @@ struct Colors { extension UINavigationController { var rx_serviceState: AnyObserver { - return AnyObserver { event in - switch event { - case .Next(let maybeServiceState): - // if nil is being bound, then don't change color, it's not perfect, but :) - if let serviceState = maybeServiceState { - let isOffline = serviceState ?? .Online == .Offline + return UIBindingObserver(owner: self) { navigationController, maybeServiceState in + // if nil is being bound, then don't change color, it's not perfect, but :) + if let serviceState = maybeServiceState { + let isOffline = serviceState ?? .Online == .Offline - self.navigationBar.backgroundColor = isOffline - ? Colors.OfflineColor - : Colors.OnlineColor - } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break + self.navigationBar.backgroundColor = isOffline + ? Colors.OfflineColor + : Colors.OnlineColor } - } + }.asObserver() } } diff --git a/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift b/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift index 7927120f..9e4e32b9 100644 --- a/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift +++ b/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift @@ -21,34 +21,21 @@ extension UIImageView{ } func rxex_downloadableImageAnimated(transitionType:String?) -> AnyObserver { - - return AnyObserver { [weak self] event in - - guard let strongSelf = self else { return } - MainScheduler.ensureExecutingOnScheduler() - - switch event{ - case .Next(let value): - for subview in strongSelf.subviews { - subview.removeFromSuperview() - } - switch value{ - case .Content(let image): - strongSelf.rx_image.onNext(image) - case .OfflinePlaceholder: - let label = UILabel(frame: strongSelf.bounds) - label.textAlignment = .Center - label.font = UIFont.systemFontOfSize(35) - label.text = "⚠️" - strongSelf.addSubview(label) - } - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break + return UIBindingObserver(UIElement: self) { imageView, image in + for subview in strongSelf.subviews { + subview.removeFromSuperview() } - } + switch value{ + case .Content(let image): + strongSelf.rx_image.onNext(image) + case .OfflinePlaceholder: + let label = UILabel(frame: strongSelf.bounds) + label.textAlignment = .Center + label.font = UIFont.systemFontOfSize(35) + label.text = "⚠️" + strongSelf.addSubview(label) + } + }.asObserver() } } #endif