Skip to content

Instantly share code, notes, and snippets.

@thebarndog
Last active February 26, 2016 20:23
Show Gist options
  • Save thebarndog/abe2bed00783f17679ce to your computer and use it in GitHub Desktop.
Save thebarndog/abe2bed00783f17679ce to your computer and use it in GitHub Desktop.
ReactiveCocoa extensions for for RAC4
//
// ReactiveAnimation.swift
// Created by Brendan Conron on 2/12/16.
//
import Foundation
import UIKit
import ReactiveCocoa
extension UIViewAnimationCurve {
public var mediaTimingFunction: String {
switch self {
case .EaseInOut:
return kCAMediaTimingFunctionEaseInEaseOut
case .EaseIn:
return kCAMediaTimingFunctionEaseIn
case .EaseOut:
return kCAMediaTimingFunctionEaseOut
case .Linear:
return kCAMediaTimingFunctionLinear
}
}
}
extension SignalType {
public func animate(duration: NSTimeInterval? = nil, curve: UIViewAnimationCurve = .Linear) -> Signal<Self.Value, Self.Error> {
return self.flatMap(.Concat) { value in
return SignalProducer { observer, disposable in
let options: UIViewAnimationOptions = [UIViewAnimationOptions(rawValue: UInt(curve.rawValue)), .LayoutSubviews, .BeginFromCurrentState, .OverrideInheritedCurve]
UIView.animateWithDuration(duration ?? 0.2, delay: 0, options: options, animations: {
observer.sendNext(value)
}, completion: { finished in
if finished {
observer.sendCompleted()
} else {
observer.sendInterrupted()
}
})
}
}
}
}
extension SignalProducerType {
public func animate(duration: NSTimeInterval? = nil, curve: UIViewAnimationCurve = .Linear) -> SignalProducer<Self.Value, Self.Error> {
return self.flatMap(.Concat) { value in
return SignalProducer { observer, disposable in
let options: UIViewAnimationOptions = [UIViewAnimationOptions(rawValue: UInt(curve.rawValue)), .LayoutSubviews, .BeginFromCurrentState, .OverrideInheritedCurve]
UIView.animateWithDuration(duration ?? 0.2, delay: 0, options: options, animations: {
observer.sendNext(value)
}, completion: { finished in
if finished {
observer.sendCompleted()
} else {
observer.sendInterrupted()
}
})
}
}
}
public func animateEach(duration: NSTimeInterval? = nil, curve: UIViewAnimationCurve = .Linear) -> SignalProducer<SignalProducer<Self.Value, NoError>, Self.Error> {
return self.map { value in
return SignalProducer { observer, disposable in
let options: UIViewAnimationOptions = [UIViewAnimationOptions(rawValue: UInt(curve.rawValue)), .LayoutSubviews, .BeginFromCurrentState, .OverrideInheritedCurve]
UIView.animateWithDuration(duration ?? 0.2, delay: 0, options: options, animations: {
observer.sendNext(value)
}, completion: { finished in
if finished {
observer.sendCompleted()
} else {
observer.sendInterrupted()
}
})
}
}
}
}
//
// ReactiveConstraint.swift
// Created by Brendan Conron on 2/12/16.
//
import Foundation
import ReactiveCocoa
public struct ReactiveConstraint {
private weak var constraint: NSLayoutConstraint?
private weak var view: UIView?
private let willDealloc: SignalProducer<(), NoError>
public init(_ constraint: NSLayoutConstraint, view: UIView?) {
self.constraint = constraint
self.view = view
self.willDealloc = constraint.rac_willDeallocSignal()
.toSignalProducer()
.map { _ in () }
.flatMapError {
fatalError("rac_willDeallocSignal failed with error: \($0)")
()
}
}
}
public func <~ (constraint: ReactiveConstraint, signal: Signal<CGFloat, NoError>) -> Disposable {
let disposable = CompositeDisposable()
let constraintDisposable = constraint.willDealloc.startWithCompleted {
disposable.dispose()
}
disposable.addDisposable(constraintDisposable)
let signalDisposable = signal.observe(Observer(
next: {
constraint.constraint?.constant = $0
constraint.view?.layoutIfNeeded()
},
completed: {
disposable.dispose()
}
))
disposable.addDisposable(signalDisposable)
return disposable
}
public func <~ (constraint: ReactiveConstraint, producer: SignalProducer<CGFloat, NoError>) -> Disposable {
var disposable: Disposable!
producer.startWithSignal { signal, signalDisposable in
constraint <~ signal
disposable = signalDisposable
constraint.willDealloc.startWithCompleted {
signalDisposable.dispose()
}
}
return disposable
}
public func <~ <P: PropertyType where P.Value == CGFloat>(destinationConstraint: ReactiveConstraint, sourceProperty: P) -> Disposable {
return destinationConstraint <~ sourceProperty.producer
}
//
// ReactiveExtensions+NSLayoutConstraint.swift
// Created by Brendan Conron on 2/11/16.
//
import Foundation
import ReactiveCocoa
extension NSLayoutConstraint {
public var rac_constant: DynamicProperty {
return DynamicProperty(object: self, keyPath: "constant")
}
}
//
// ReactiveExtensions+CoreLocation.swift
// Created by Brendan Conron on 2/11/16.
//
import Foundation
import ReactiveCocoa
extension CLLocationManager {
/**
Emits the current authorization status then completes.
- returns: SignalProducer value.
*/
public static func authorizationStatusProducer() -> SignalProducer<CLAuthorizationStatus, NoError> {
return SignalProducer(value: self.authorizationStatus())
}
}
//
// ReactiveExtensions+NSObject.swift
//
// Created by Brendan Conron on 2/11/16.
//
import Foundation
import ReactiveCocoa
extension NSObject {
public final func willDeallocSignal() -> Signal<Void, NoError> {
return self.rac_willDeallocSignal().toTriggerSignal()
}
}
//
// ReactiveExtensions.swift
// Created by Brendan Conron on 2/10/16.
//
import Foundation
import UIKit
import ReactiveCocoa
extension UIView {
/**
Sends changes when the view's alpha changes.
- returns: Signal value.
*/
public final func alphaSignal() -> Signal<CGFloat, NoError> {
return self.rac_alpha.signal.map { $0 as! CGFloat }
}
/**
Sends changes when the view is hidden or visible.
- returns: Signal value.
*/
public final func hiddenSignal() -> Signal<Bool, NoError> {
return self.rac_hidden.signal.map { $0 as! Bool }
}
/**
Sends changes when the view's bounds changes.
- note: setting the frame will not trigger this signal to emit a value.
- returns: Signal value.
*/
public final func boundsSignal() -> Signal<CGRect, NoError> {
return self.rac_bounds.signal.map { ($0 as! NSValue).CGRectValue() }
}
/**
Sends changes when the view's frame changes.
- note: setting the bounds will not trigger this signal to emit a value.
- returns: Signal value.
*/
public final func frameSignal() -> Signal<CGRect, NoError> {
return self.rac_frame.signal.map { ($0 as! NSValue).CGRectValue() }
}
public final func frameProducer() -> SignalProducer<CGRect, NoError> {
return self.rac_frame.producer.map { ($0 as! NSValue).CGRectValue() }
}
public var rac_alpha: DynamicProperty {
return DynamicProperty(object: self, keyPath: "alpha")
}
public var rac_hidden: DynamicProperty {
return DynamicProperty(object: self, keyPath: "hidden")
}
/// - warning: When binding to this property, make sure to wrap your values in `NSValue`
public var rac_bounds: DynamicProperty {
return DynamicProperty(object: self, keyPath: "bounds")
}
/// - warning: When binding to this property, make sure to wrap your values in `NSValue`
public var rac_frame: DynamicProperty {
return DynamicProperty(object: self, keyPath: "frame")
}
}
extension UILabel {
/**
Emits a change when the label text changes.
- seealso: `optionalTextSignal`
- returns: Signal value.
*/
public final func textSignal() -> Signal<String, NoError> {
return self.rac_text.signal.ignoreNil().map { $0 as! String }
}
/**
Emits a change when the label text changes. Allows for nil values.
- returns: Signal value.
*/
public final func optionalTextSignal() -> Signal<String?, NoError> {
return self.rac_text.signal.map { $0 as? String }
}
public var rac_text: DynamicProperty {
return DynamicProperty(object: self, keyPath: "text")
}
}
extension UITextField {
/**
Sends the text field's value changes.
- returns: Signal value.
*/
public final func textSignal() -> Signal<String, NoError> {
return self.rac_textSignal().toSignal()
}
}
extension UIControl {
/**
Trigger signal that activates on the specified control events.
- parameter controlEvents: `UIControlEvents` value.
- returns: Activation signal.
*/
public final func signalForControlEvents(controlEvents: UIControlEvents) -> Signal<Void, NoError> {
return self.rac_signalForControlEvents(controlEvents)
.toTriggerSignal()
}
/**
Sends values on enable/disable.
- returns: Signal value.
*/
public final func enabledSignal() -> Signal<Bool, NoError> {
return self.rac_enabled.signal.map { $0 as! Bool }
}
public final func selectedSignal() -> Signal<Bool, NoError> {
return self.rac_selected.signal.map { $0 as! Bool }
}
public var rac_enabled: DynamicProperty {
return DynamicProperty(object: self, keyPath: "enabled")
}
public var rac_selected: DynamicProperty {
return DynamicProperty(object: self, keyPath: "selected")
}
}
extension UIPageControl {
/**
Sends a value whenever the current page changes.
- returns: Signal value.
*/
public final func currentPageSignal() -> Signal<Int, NoError> {
return self.rac_currentPage.signal.map { $0 as! Int }
}
/// -warning: when binding, wrap `Int` in `NSNumber`
public var rac_currentPage: DynamicProperty {
return DynamicProperty(object: self, keyPath: "currentPage")
}
}
extension UITableViewCell {
/**
Trigger signal that activates when the cell calls `prepareForReuse`.
- returns: Activiation signal.
*/
public final func prepareForReuseSignal() -> Signal<Void, NoError> {
return self.rac_prepareForReuseSignal.toTriggerSignal()
}
}
extension UICollectionReusableView {
/**
Trigger signal that activates when the cell calls `prepareForReuse`.
- returns: Activiation signal.
*/
public final func prepareForReuseSignal() -> Signal<Void, NoError> {
return self.rac_prepareForReuseSignal.toTriggerSignal()
}
}
extension UIScrollView {
/**
Emits changes when the content size changes.
- returns: Signal value.
*/
public final func contentSizeSignal() -> Signal<CGSize, NoError> {
return self.rac_contentSize.signal.map { ($0 as! NSValue).CGSizeValue() }
}
/**
Emits changes when the content offset changes.
- warning: If `scrollEnabled` is false, no values will be emitted.
- returns: Signal value.
*/
public final func contentOffsetSignal() -> Signal<CGPoint, NoError> {
return self.rac_contentOffset.signal.map {
return ($0 as! NSValue).CGPointValue()
}
}
/**
Emits changes when the scroll enabled changes.
- returns: Signal value.
*/
public final func scrollEnabledSignal() -> Signal<Bool, NoError> {
return self.rac_scrollEnabled.signal.map { $0 as! Bool }
}
public var rac_scrollEnabled: DynamicProperty {
return DynamicProperty(object: self, keyPath: "scrollEnabled")
}
/// - warning: When binding to this property, make sure to wrap your values in `NSValue`
public var rac_contentSize: DynamicProperty {
return DynamicProperty(object: self, keyPath: "contentSize")
}
/// - warning: When binding to this property, make sure to wrap your values in `NSValue`
public var rac_contentOffset: DynamicProperty {
return DynamicProperty(object: self, keyPath: "contentOffset")
}
}
extension UIGestureRecognizer {
/**
Emits values whenever there's a change in the gesture's state.
In order to get the appropriate type of gesture recognizer, map the value like:
`gesture.gestureSignal.map { $0 as! UIPanGestureRecognizer }`
- returns: Signal that emits `UIGestureRecognizer`.
*/
public final func gestureSignal() -> Signal<UIGestureRecognizer, NoError> {
return self.rac_gestureSignal().toSignal()
}
/**
Emits values when the gesture enables and disables.
- returns: Signal value.
*/
public final func enabledSignal() -> Signal<Bool, NoError> {
return self.rac_enabled.signal.map { $0 as! Bool }
}
public var rac_enabled: DynamicProperty {
return DynamicProperty(object: self, keyPath: "enabled")
}
}
extension UIViewController {
/**
Trigger signal that actives when the view controller disappeared.
- returns: Activation signal.
*/
public final func viewControllerDidDisappearSignal() -> Signal<Void, NoError> {
return self.rac_signalForSelector("viewDidDisappear:").toTriggerSignal()
}
}
extension UIImageView {
/**
Emits values whenever the image property changes.
- precondition: Image must never be set to nil.
- seealso: `optionalImageSignal`
- returns: Signal value.
*/
public final func imageSignal() -> Signal<UIImage, NoError> {
return self.rac_image.signal.ignoreNil().map { $0 as! UIImage }
}
/**
Emits values whenever the image property changes.
- returns: Signal value.
*/
public final func optionalImageSignal() -> Signal<UIImage?, NoError> {
return self.rac_image.signal.map { $0 as? UIImage }
}
public var rac_image: DynamicProperty {
return DynamicProperty(object: self, keyPath: "image")
}
}
//
// ReactiveExtensions.swift
// Created by Brendan Conron on 2/11/16.
//
import Foundation
import ReactiveCocoa
extension RACSignal {
/**
Converts a RACSignal to a Signal with the specified value types.
- warning: Signals that derive from a DynamicProperty should use `toTriggerSignal` since
dynamic properties have no type safety.
- returns: Signal value.
*/
public func toSignal<Value, Error: ErrorType>() -> Signal<Value, Error> {
return Signal { self.toSignalProducer().map { $0 as! Value }.mapError { $0 as! Error }.start($0) }
}
/**
Converts a RACSignal to a SignalProducer that never errors and sends nothing.
Useful for modeling function execution using `rac_signalForSelector`.
- returns: Signal value.
*/
public func toTriggerSignal() -> SignalProducer<Void, NoError> {
return self.toSignalProducer().map { _ in () }.assumeNoErrors()
}
/**
Converts a RACSignal to a Signal that never errors and sends nothing.
Useful as an activation signal such as a button press.
- returns: Signal value.
*/
public func toTriggerSignal() -> Signal<Void, NoError> {
return self.toSignalAssumingHot().map { _ in () }.assumeNoErrors()
}
/**
Converts a RACSignal to a "hot" Signal. These signals represent event streams
such as button presses or value changes on a control.
- returns: Signal value.
*/
public func toSignalAssumingHot() -> Signal<AnyObject?, NSError> {
return Signal { observer in
return self.toSignalProducer().start(observer)
}
}
}
extension SignalProducerType {
/**
Lifts the Signal `assumeNoErrors` method onto SignalProducers.
- returns: SignalProducer value.
*/
public final func assumeNoErrors() -> SignalProducer<Self.Value, NoError> {
return self.lift { $0.assumeNoErrors() }
}
}
extension SignalType {
/**
Assumes that no errors will be sent on this stream. Causes runtime exception if
an error is sent. Used in streams that presumably have no errors, such as buttons,
control values, etc.
- returns: Signal value.
*/
public final func assumeNoErrors() -> Signal<Self.Value, NoError> {
return self.mapError { error in
fatalError("Unexpected error found in signal that shouldn't error: \(error)")
()
}
}
}
extension SignalType where Value: Comparable {
public final func clamp(lower: Value, upper: Value) -> Signal<Self.Value, Self.Error> {
return self.clampLower(lower).clampGreater(upper)
}
public final func clampLower(lower: Value) -> Signal<Self.Value, Self.Error> {
return self.map { max($0, lower) }
}
public final func clampGreater(upper: Value) -> Signal<Self.Value, Self.Error> {
return self.map { min($0, upper) }
}
public final func clamp(lowerSignal: Signal<Value, Error>, greaterSignal: Signal<Value, Error>) -> Signal<Self.Value, Self.Error> {
return self.clampLower(lowerSignal).clampGreater(greaterSignal)
}
public final func clampLower(lowerSignal: Signal<Value, Error>) -> Signal<Self.Value, Self.Error> {
return self.combineLatestWith(lowerSignal)
.map { max($0.0, $0.1) }
}
public final func clampGreater(greaterSignal: Signal<Value, Error>) -> Signal<Self.Value, Self.Error> {
return self.combineLatestWith(greaterSignal)
.map { min($0.0, $0.1) }
}
}
extension SignalProducerType where Value: Comparable {
public final func clamp(lower: Value, upper: Value) -> SignalProducer<Self.Value, Self.Error> {
return self.lift { $0.clamp(lower, upper: upper) }
}
public final func clampLower(lower: Value) -> SignalProducer<Self.Value, Self.Error> {
return self.lift { $0.clampLower(lower) }
}
public final func clampGreater(upper: Value) -> SignalProducer<Self.Value, Self.Error> {
return self.lift { $0.clampGreater(upper) }
}
public final func clampLower(lowerProducer: SignalProducer<Value, Error>) -> SignalProducer<Self.Value, Self.Error> {
return self.combineLatestWith(lowerProducer)
.map { max($0.0, $0.1) }
}
public final func clampGreater(greaterProducer: SignalProducer<Value, Error>) -> SignalProducer<Self.Value, Self.Error> {
return self.combineLatestWith(greaterProducer)
.map { min($0.0, $0.1) }
}
}
//
// ReactiveView.swift
// Created by Brendan Conron on 2/12/16.
//
import Foundation
import UIKit
import ReactiveCocoa
public struct ReactiveView {
private weak var view: UIView?
private let willDealloc: SignalProducer<(), NoError>
private var animator: UIView? { return self.view }
public init(_ view: UIView) {
self.view = view
self.willDealloc = view
.rac_willDeallocSignal()
.toSignalProducer()
.map { _ in () }
.flatMapError {
fatalError("rac_willDeallocSignal failed with error: \($0)")
()
}
}
private func viewProperty<T>(setter: T -> ()) -> ViewProperty<T> {
return ViewProperty(willDealloc: self.willDealloc, setter: setter)
}
public var frame: ViewProperty<CGRect> {
return self.viewProperty { self.animator?.frame = $0 }
}
public var bounds: ViewProperty<CGRect> {
return viewProperty { self.animator?.bounds = $0 }
}
public var center: ViewProperty<CGPoint> {
return viewProperty { self.animator?.center = $0 }
}
public var backgroundColor: ViewProperty<UIColor> {
return viewProperty { self.animator?.backgroundColor = $0 }
}
public var transform: ViewProperty<CGAffineTransform> {
return viewProperty { self.animator?.transform = $0 }
}
public var alpha: ViewProperty<CGFloat> {
return viewProperty { value in
self.animator?.alpha = value
}
}
}
public struct ViewProperty<T> {
private let willDealloc: SignalProducer<(), NoError>
private let setter: T -> ()
}
public func <~ <T>(property: ViewProperty<T>, signal: Signal<T, NoError>) -> Disposable {
let disposable = CompositeDisposable()
let propertyDisposable = property.willDealloc.startWithCompleted {
disposable.dispose()
}
disposable.addDisposable(propertyDisposable)
let signalDisposable = signal.observe(Observer(next: property.setter, completed: {
disposable.dispose()
}))
disposable.addDisposable(signalDisposable)
return disposable
}
/// Binds a (potentially animated) signal producer to a view property.
public func <~ <T>(property: ViewProperty<T>, producer: SignalProducer<T, NoError>) -> Disposable {
var disposable: Disposable!
producer.startWithSignal { signal, signalDisposable in
property <~ signal
disposable = signalDisposable
property.willDealloc.startWithCompleted {
signalDisposable.dispose()
}
}
return disposable
}
/// Binds the view property to the latest values of `sourceProperty`.
public func <~ <T, P: PropertyType where P.Value == T>(destinationProperty: ViewProperty<T>, sourceProperty: P) -> Disposable {
return destinationProperty <~ sourceProperty.producer
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment