Skip to content

Commit 85f2eeb

Browse files
committed
Add support for rx_action in UIBarButtonItem
1 parent 76a3dca commit 85f2eeb

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed

Action.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ Pod::Spec.new do |s|
2323
s.dependency "RxSwift", '~> 2.0.0-beta'
2424
s.dependency "RxCocoa", '~> 2.0.0-beta'
2525

26-
s.watchos.exclude_files = "UIButton+Rx.swift"
27-
s.osx.exclude_files = "UIButton+Rx.swift"
26+
s.watchos.exclude_files = "UIButton+Rx.swift", "UIBarButtonItem+Action.swift"
27+
s.osx.exclude_files = "UIButton+Rx.swift", "UIBarButtonItem+Action.swift"
2828
end

UIBarButtonItem+Action.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import UIKit
2+
import RxSwift
3+
import RxCocoa
4+
import ObjectiveC
5+
6+
public extension UIBarButtonItem {
7+
8+
/// Binds enabled state of action to button, and subscribes to rx_tap to execute action.
9+
/// These subscriptions are managed in a private, inaccessible dispose bag. To cancel
10+
/// them, set the rx_action to nil or another action.
11+
public var rx_action: CocoaAction? {
12+
get {
13+
var action: CocoaAction?
14+
doLocked {
15+
action = objc_getAssociatedObject(self, &AssociatedKeys.Action) as? Action
16+
}
17+
return action
18+
}
19+
20+
set {
21+
doLocked {
22+
// Store new value.
23+
objc_setAssociatedObject(self, &AssociatedKeys.Action, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
24+
25+
// This effectively disposes of any existing subscriptions.
26+
self.resetActionDisposeBag()
27+
28+
// Set up new bindings, if applicable.
29+
if let action = newValue {
30+
action
31+
.enabled
32+
.bindTo(self.rx_enabled)
33+
.addDisposableTo(self.actionDisposeBag)
34+
35+
// Technically, this file is only included on tv/iOS platforms,
36+
// so this optional will never be nil. But let's be safe 😉
37+
let lookupControlEvent: ControlEvent<Void>?
38+
39+
#if os(tvOS)
40+
lookupControlEvent = self.rx_primaryAction
41+
#elseif os(iOS)
42+
lookupControlEvent = self.rx_tap
43+
#endif
44+
45+
guard let controlEvent = lookupControlEvent else {
46+
return
47+
}
48+
49+
controlEvent
50+
.subscribeNext { _ -> Void in
51+
action.execute()
52+
}
53+
.addDisposableTo(self.actionDisposeBag)
54+
}
55+
}
56+
}
57+
}
58+
}
59+
60+
// Note: Actions performed in this extension are _not_ locked
61+
// So be careful!
62+
private extension UIBarButtonItem {
63+
private struct AssociatedKeys {
64+
static var Action = "rx_action"
65+
static var DisposeBag = "rx_disposeBag"
66+
}
67+
68+
// A dispose bag to be used exclusively for the instance's rx_action.
69+
private var actionDisposeBag: DisposeBag {
70+
var disposeBag: DisposeBag
71+
72+
if let lookup = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBag) as? DisposeBag {
73+
disposeBag = lookup
74+
} else {
75+
disposeBag = DisposeBag()
76+
objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, disposeBag, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
77+
}
78+
79+
return disposeBag
80+
}
81+
82+
// Resets the actionDisposeBag to nil, disposeing of any subscriptions within it.
83+
private func resetActionDisposeBag() {
84+
objc_setAssociatedObject(self, &AssociatedKeys.DisposeBag, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
85+
}
86+
87+
// Uses objc_sync on self to perform a locked operation.
88+
private func doLocked(closure: () -> Void) {
89+
objc_sync_enter(self); defer { objc_sync_exit(self) }
90+
closure()
91+
}
92+
}

0 commit comments

Comments
 (0)