diff --git a/Changelog.md b/Changelog.md index c02f32a3..48d03fe1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,18 @@ Current master - Nothing yet! +4.0 (In development) +-------------------- + +- Cleanup public API [#120](https://github.com/RxSwiftCommunity/Action/issues/120) + - Remove unneccessary properties + - `workFactory` + - `_enabledIf` + - Rename properties to match Swift API design [guidelines](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage) + - `enabled` ~> `isEnabled` + - `executing` ~> `isExecuting` + - Deprecate renamed properties + 3.5.0 ----- diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift index 7f5946d6..2fda0856 100644 --- a/Demo/ViewController.swift +++ b/Demo/ViewController.swift @@ -65,8 +65,8 @@ class ViewController: UIViewController { // Demo: observe the output of both actions, spin an activity indicator // while performing the work Observable.combineLatest( - button.rx.action!.executing, - self.navigationItem.rightBarButtonItem!.rx.action!.executing) { a,b in + button.rx.action!.isExecuting, + self.navigationItem.rightBarButtonItem!.rx.action!.isExecuting) { a,b in // we combine two boolean observable and output one boolean return a || b } @@ -90,7 +90,7 @@ class ViewController: UIViewController { } self.navigationItem.leftBarButtonItem?.rx.bind(to: sharedAction, input: .barButton) - sharedAction.executing.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in + sharedAction.isExecuting.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in if (executing) { self?.activityIndicator.startAnimating() } diff --git a/Readme.md b/Readme.md index 9bce03cf..64c06437 100644 --- a/Readme.md +++ b/Readme.md @@ -20,7 +20,7 @@ Usage You have to pass a `workFactory` that takes input and returns an `Observable`. This represents some work that needs to be accomplished. Whenever you call `execute()`, you pass in input that's fed to the work factory. The `Action` will subscribe to the observable and emit the `Next` events on its `elements` property. If the observable errors, the error is sent as a `Next` even on the `errors` property. Neat. -Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The `executing` property sends `true` and `false` values as `Next` events. +Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The `isExecuting` property sends `true` and `false` values as `Next` events. ```swift action: Action = Action(workFactory: { input in @@ -46,7 +46,7 @@ action: Action = Action(enabledIf: validEmailAddress, workFactory: Now `execute()` only does the work if the email address is valid. Super cool! -Note that `enabledIf` isn't the same as the `enabled` property. You pass in `enabledIf` and the action uses that, and its current executing state, to determine if it's currently enabled. +Note that `enabledIf` isn't the same as the `isEnabled` property. You pass in `enabledIf` and the action uses that, and its current executing state, to determine if it's currently enabled. What's _really_ cool is the `UIButton` extension. It accepts a `CocoaAction`, which is just `Action`. @@ -54,11 +54,11 @@ What's _really_ cool is the `UIButton` extension. It accepts a `CocoaAction`, wh button.rx.action = action ``` -Now when the button is pressed, the action is executed. The button's `enabled` state is bound to the action's `enabled` property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out [this code example of CocoaAction _in_ action](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift). +Now when the button is pressed, the action is executed. The button's `isEnabled` state is bound to the action's `isEnabled` property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out [this code example of CocoaAction _in_ action](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift). If you'd like to use `Action` to do a complex operation such as file download with download progress report (to update progress bar in the UI for example) you'd use `Action` instead of `CocoaAction`. Out of the box `CocoaAction` can't emit progress values, your own `Action` will do that. For details refer to [this article](http://www.sm-cloud.com/rxswift-action/). -If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bindTo` on each `UIButton` with a closure that returns correct input. +If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bind(to:)` on each `UIButton` with a closure that returns correct input. ```swift let button1 = UIButton() @@ -68,13 +68,13 @@ let action = Action { input in print(input) return .just(input) } -button1.rx.bindTo(action) {_ in return "Hello"} -button2.rx.bindTo(action) {_ in return "Goodbye"} +button1.rx.bind(to: action) {_ in return "Hello"} +button2.rx.bind(to: action) {_ in return "Goodbye"} ``` `button1` and `button2` are sharing the same `Action`, but they are feeding it with different input (`Hello` and `Goodbye` that will be printed for corresponding tap). -A more complex use case can be a single action related to a `UIViewController` that manages your navigation, error handling and loading state. With this approach, you can have as many `UIButton`s (or `UIBarButtonItem`s) as you want and subscribe to `executing`, `errors` and `elements` once and in a single common place. +A more complex use case can be a single action related to a `UIViewController` that manages your navigation, error handling and loading state. With this approach, you can have as many `UIButton`s (or `UIBarButtonItem`s) as you want and subscribe to `isExecuting`, `errors` and `elements` once and in a single common place. There's also a really cool extension on `UIAlertAction`, used by [`UIAlertController`](http://ashfurrow.com/blog/uialertviewcontroller-example/). One catch: because of the limitations of that class, you can't instantiate it with the normal initializer. Instead, call this class method: diff --git a/Sources/Action/Action.swift b/Sources/Action/Action.swift index c30bacb1..3e1cc292 100644 --- a/Sources/Action/Action.swift +++ b/Sources/Action/Action.swift @@ -22,9 +22,6 @@ When this excuted via execute() or inputs subject, it passes its parameter to th public final class Action { public typealias WorkFactory = (Input) -> Observable - public let _enabledIf: Observable - public let workFactory: WorkFactory - /// Inputs that triggers execution of action. /// This subject also includes inputs as aguments of execute(). /// All inputs are always appear in this subject even if the action is not enabled. @@ -40,7 +37,7 @@ public final class Action { public let elements: Observable /// Whether or not we're currently executing. - public let executing: Observable + public let isExecuting: Observable /// Observables returned by the workFactory. /// Useful for sending results back from work being completed @@ -50,7 +47,7 @@ public final class Action { /// Whether or not we're enabled. Note that this is a *computed* sequence /// property based on enabledIf initializer and if we're currently executing. /// Always observed on MainScheduler. - public let enabled: Observable + public let isEnabled: Observable private let disposeBag = DisposeBag() @@ -67,17 +64,14 @@ public final class Action { enabledIf: Observable = Observable.just(true), workFactory: @escaping WorkFactory) { - self._enabledIf = enabledIf - self.workFactory = workFactory - let enabledSubject = BehaviorSubject(value: false) - enabled = enabledSubject.asObservable() + isEnabled = enabledSubject.asObservable() let errorsSubject = PublishSubject() errors = errorsSubject.asObservable() executionObservables = inputs - .withLatestFrom(enabled) { input, enabled in (input, enabled) } + .withLatestFrom(isEnabled) { input, enabled in (input, enabled) } .flatMap { input, enabled -> Observable> in if enabled { return Observable.of(workFactory(input) @@ -93,7 +87,7 @@ public final class Action { elements = executionObservables .flatMap { $0.catchError { _ in Observable.empty() } } - executing = executionObservables.flatMap { + isExecuting = executionObservables.flatMap { execution -> Observable in let execution = execution .flatMap { _ in Observable.empty() } @@ -107,7 +101,7 @@ public final class Action { .share(replay: 1, scope: .forever) Observable - .combineLatest(executing, enabledIf) { !$0 && $1 } + .combineLatest(isExecuting, enabledIf) { !$0 && $1 } .bind(to: enabledSubject) .disposed(by: disposeBag) } @@ -135,3 +129,16 @@ public final class Action { return subject.asObservable() } } + +// MARK: Deprecated +extension Action { + @available(*, deprecated, renamed: "isExecuting") + public var executing: Observable { + return isExecuting + } + + @available(*, deprecated, renamed: "isEnabled") + public var enabled: Observable { + return isEnabled + } +} diff --git a/Sources/Action/CommonUI/Button+Action.swift b/Sources/Action/CommonUI/Button+Action.swift index 26af4054..cc1e282f 100644 --- a/Sources/Action/CommonUI/Button+Action.swift +++ b/Sources/Action/CommonUI/Button+Action.swift @@ -31,7 +31,7 @@ public extension Reactive where Base: Button { // Set up new bindings, if applicable. if let action = newValue { action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) diff --git a/Sources/Action/CommonUI/Control+Action.swift b/Sources/Action/CommonUI/Control+Action.swift index 39d27c83..15a7bc9e 100644 --- a/Sources/Action/CommonUI/Control+Action.swift +++ b/Sources/Action/CommonUI/Control+Action.swift @@ -26,7 +26,7 @@ public extension Reactive where Base: Control { // Bind the enabled state of the control to the enabled state of the action action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } diff --git a/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift b/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift index 9107bf7d..0cfe2678 100644 --- a/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift +++ b/Sources/Action/UIKitExtensions/UIAlertAction+Action.swift @@ -35,14 +35,14 @@ public extension Reactive where Base: UIAlertAction { // Set up new bindings, if applicable. if let action = newValue { action - .enabled - .bind(to: self.enabled) + .isEnabled + .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } } } - public var enabled: AnyObserver { + public var isEnabled: AnyObserver { return AnyObserver { [weak base] event in MainScheduler.ensureExecutingOnScheduler() diff --git a/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift b/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift index 63977f1e..7c3ef492 100644 --- a/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift +++ b/Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift @@ -26,7 +26,7 @@ public extension Reactive where Base: UIBarButtonItem { // Set up new bindings, if applicable. if let action = newValue { action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) @@ -47,7 +47,7 @@ public extension Reactive where Base: UIBarButtonItem { .disposed(by: self.base.actionDisposeBag) action - .enabled + .isEnabled .bind(to: self.isEnabled) .disposed(by: self.base.actionDisposeBag) } diff --git a/Tests/ActionTests/ActionTests.swift b/Tests/ActionTests/ActionTests.swift index e14f7942..73237f93 100644 --- a/Tests/ActionTests/ActionTests.swift +++ b/Tests/ActionTests/ActionTests.swift @@ -73,11 +73,11 @@ class ActionTests: QuickSpec { .bind(to: errors) .disposed(by: disposeBag) - action.enabled + action.isEnabled .bind(to: enabled) .disposed(by: disposeBag) - action.executing + action.isExecuting .bind(to: executing) .disposed(by: disposeBag) @@ -89,8 +89,8 @@ class ActionTests: QuickSpec { action.inputs.subscribe().disposed(by: disposeBag) action.elements.subscribe().disposed(by: disposeBag) action.errors.subscribe().disposed(by: disposeBag) - action.enabled.subscribe().disposed(by: disposeBag) - action.executing.subscribe().disposed(by: disposeBag) + action.isEnabled.subscribe().disposed(by: disposeBag) + action.isExecuting.subscribe().disposed(by: disposeBag) action.executionObservables.subscribe().disposed(by: disposeBag) }