CocoaAction.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import Foundation
  2. /// Wraps an Action for use by a GUI control (such as `NSControl` or
  3. /// `UIControl`), with KVO, or with Cocoa Bindings.
  4. public final class CocoaAction: NSObject {
  5. /// The selector that a caller should invoke upon a CocoaAction in order to
  6. /// execute it.
  7. public static let selector: Selector = #selector(CocoaAction.execute(_:))
  8. /// Whether the action is enabled.
  9. ///
  10. /// This property will only change on the main thread, and will generate a
  11. /// KVO notification for every change.
  12. public private(set) var enabled: Bool = false
  13. /// Whether the action is executing.
  14. ///
  15. /// This property will only change on the main thread, and will generate a
  16. /// KVO notification for every change.
  17. public private(set) var executing: Bool = false
  18. private let _execute: AnyObject? -> Void
  19. private let disposable = CompositeDisposable()
  20. /// Initializes a Cocoa action that will invoke the given Action by
  21. /// transforming the object given to execute().
  22. ///
  23. /// - note: You must cast the passed in object to the control type you need
  24. /// since there is no way to know where this cocoa action will be
  25. /// added as a target.
  26. ///
  27. /// - parameters:
  28. /// - action: Executable action.
  29. /// - inputTransform: Closure that accepts the UI control performing the
  30. /// action and returns a value (e.g.
  31. /// `(UISwitch) -> (Bool)` to reflect whether a provided
  32. /// switch is currently on.
  33. public init<Input, Output, Error>(_ action: Action<Input, Output, Error>, _ inputTransform: AnyObject? -> Input) {
  34. _execute = { input in
  35. let producer = action.apply(inputTransform(input))
  36. producer.start()
  37. }
  38. super.init()
  39. disposable += action.enabled.producer
  40. .observeOn(UIScheduler())
  41. .startWithNext { [weak self] value in
  42. self?.willChangeValueForKey("enabled")
  43. self?.enabled = value
  44. self?.didChangeValueForKey("enabled")
  45. }
  46. disposable += action.executing.producer
  47. .observeOn(UIScheduler())
  48. .startWithNext { [weak self] value in
  49. self?.willChangeValueForKey("executing")
  50. self?.executing = value
  51. self?.didChangeValueForKey("executing")
  52. }
  53. }
  54. /// Initializes a Cocoa action that will invoke the given Action by always
  55. /// providing the given input.
  56. ///
  57. /// - parameters:
  58. /// - action: Executable action.
  59. /// - input: A value given as input to the action.
  60. public convenience init<Input, Output, Error>(_ action: Action<Input, Output, Error>, input: Input) {
  61. self.init(action, { _ in input })
  62. }
  63. deinit {
  64. disposable.dispose()
  65. }
  66. /// Attempts to execute the underlying action with the given input, subject
  67. /// to the behavior described by the initializer that was used.
  68. ///
  69. /// - parameters:
  70. /// - input: A value for the action passed during initialization.
  71. @IBAction public func execute(input: AnyObject?) {
  72. _execute(input)
  73. }
  74. public override class func automaticallyNotifiesObserversForKey(key: String) -> Bool {
  75. return false
  76. }
  77. }
  78. extension Action {
  79. /// A UI bindable `CocoaAction`.
  80. ///
  81. /// - warning: The default behavior force casts the `AnyObject?` input to
  82. /// match the action's `Input` type. This makes it unsafe for use
  83. /// when the action is parameterized for something like `Void`
  84. /// input. In those cases, explicitly assign a value to this
  85. /// property that transforms the input to suit your needs.
  86. public var unsafeCocoaAction: CocoaAction {
  87. return CocoaAction(self) { $0 as! Input }
  88. }
  89. }