Skip to content

Commit 2c2d94d

Browse files
committed
add new binders
1 parent 845c489 commit 2c2d94d

19 files changed

+737
-12
lines changed

Sources/CombineExtension/Binding/BindingSubscriber.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public protocol BindingSubscriber: Subscriber, Cancellable {
1515

1616
extension Publisher {
1717
@discardableResult
18-
static func bind<B: BindingSubscriber> (source: Self, subscriber: B) -> AnyCancellable
18+
static func bind<B: BindingSubscriber>(source: Self, subscriber: B) -> AnyCancellable
1919
where Output == B.Input, Failure == B.Failure
2020
{
2121
B.bind(subscriber: subscriber, source: source)
@@ -30,12 +30,3 @@ extension BindingSubscriber {
3030
Self.bind(subscriber: subscriber, source: source.map(Optional.some))
3131
}
3232
}
33-
34-
extension Publisher {
35-
@discardableResult
36-
static func bind<B: BindingSubscriber>(source: Self, subscriber: B) -> AnyCancellable
37-
where B.Input == Output?, B.Failure == Failure
38-
{
39-
B.bind(subscriber: subscriber, source: source)
40-
}
41-
}

Sources/CombineExtension/Binding/Publisher+Combine.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77

88
import Combine
99

10+
extension Publisher {
11+
@discardableResult
12+
static func bind<B: BindingSubscriber>(source: Self, subscriber: B) -> AnyCancellable
13+
where B.Input == Output?, B.Failure == Failure
14+
{
15+
B.bind(subscriber: subscriber, source: source)
16+
}
17+
}
18+
1019
extension Publisher {
1120
public func bind<B: BindingSubscriber>(to sink: B) -> AnyCancellable where B.Input == Output, B.Failure == Failure {
1221
B.bind(subscriber: sink, source: self)
@@ -16,3 +25,44 @@ extension Publisher {
1625
B.bind(subscriber: sink, source: self)
1726
}
1827
}
28+
29+
extension Publisher {
30+
public func bind(
31+
receiveCompletion: @escaping ((Subscribers.Completion<Failure>) -> Void),
32+
receiveValue: @escaping ((Output) -> Void)
33+
) -> AnyCancellable {
34+
let subscriber = Subscribers.Sink<Self.Output, Failure>(
35+
receiveCompletion: receiveCompletion, receiveValue: receiveValue
36+
)
37+
self.receive(subscriber: subscriber)
38+
return BlockCancellable {
39+
subscriber.cancel()
40+
}.eraseToAnyCancellable()
41+
}
42+
}
43+
44+
extension Publisher where Failure == Never {
45+
public func bind(
46+
receiveValue: @escaping ((Output) -> Void)
47+
) -> AnyCancellable {
48+
let subscriber = Subscribers.Sink<Self.Output, Never>(
49+
receiveCompletion: { _ in }, receiveValue: receiveValue
50+
)
51+
self.receive(subscriber: subscriber)
52+
return BlockCancellable {
53+
subscriber.cancel()
54+
}.eraseToAnyCancellable()
55+
}
56+
}
57+
58+
extension Publisher where Failure == Never {
59+
public func bind<Root>(
60+
object: Root, keyPath: ReferenceWritableKeyPath<Root, Output>
61+
) -> AnyCancellable {
62+
let subscriber = Subscribers.Assign(object: object, keyPath: keyPath)
63+
self.receive(subscriber: subscriber)
64+
return BlockCancellable {
65+
subscriber.cancel()
66+
}.eraseToAnyCancellable()
67+
}
68+
}

Sources/CombineExtension/Cancellable+Extension.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,10 @@ extension NSObject: CancellableContainerProvider {
163163
}
164164
}
165165

166+
extension Cancellable {
167+
func eraseToAnyCancellable() -> AnyCancellable {
168+
AnyCancellable(self)
169+
}
170+
}
171+
166172
#endif
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//
2+
// CombineControlNotification.swift
3+
// UICombineExtension
4+
//
5+
// Created by Amine Bensalah on 14/06/2020.
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
extension Publishers {
12+
struct CombineControlNotification: Publisher {
13+
public typealias Output = Notification
14+
public typealias Failure = Never
15+
16+
private let notification: NotificationCenter
17+
private let name: Notification.Name
18+
private var object: AnyObject?
19+
20+
public init(notification: NotificationCenter = .default, name: Notification.Name, object: AnyObject? = nil) {
21+
self.notification = notification
22+
self.name = name
23+
self.object = object
24+
}
25+
26+
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
27+
let subscription = SubscriptionNotification(subscriber: subscriber, notification: notification, name: name, object: object)
28+
subscriber.receive(subscription: subscription)
29+
}
30+
}
31+
32+
struct CombineControlNotificationKeyPath<Value>: Publisher {
33+
public typealias Output = Value
34+
public typealias Failure = Never
35+
36+
private let notification: NotificationCenter
37+
private let keyPath: KeyPath<NotificationCenter, Value>
38+
39+
public init(notification: NotificationCenter = .default, for keyPath: KeyPath<NotificationCenter, Value>) {
40+
self.notification = notification
41+
self.keyPath = keyPath
42+
}
43+
44+
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
45+
let subscription = SubscriptionNotification(subscriber: subscriber, notification: notification, keyPath: keyPath)
46+
subscriber.receive(subscription: subscription)
47+
}
48+
}
49+
50+
struct CombineControlNotificationKeyPathOption<Value>: Publisher {
51+
public typealias Output = Value
52+
public typealias Failure = Never
53+
54+
private let notification: NotificationCenter
55+
private let keyPath: KeyPath<NotificationCenter, Value>
56+
private let options: NSKeyValueObservingOptions
57+
58+
public init(notification: NotificationCenter = .default, for keyPath: KeyPath<NotificationCenter, Value>, options: NSKeyValueObservingOptions) {
59+
self.notification = notification
60+
self.keyPath = keyPath
61+
self.options = options
62+
}
63+
64+
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
65+
let subscription = SubscriptionNotification(subscriber: subscriber, notification: notification, keyPath: keyPath, options: options)
66+
subscriber.receive(subscription: subscription)
67+
}
68+
}
69+
}
70+
71+
extension Publishers.CombineControlNotification {
72+
private final class SubscriptionNotification<S: Subscriber>: Subscription where S.Input == Notification {
73+
private var subscriber: S?
74+
private let notification: NotificationCenter
75+
private var bag = Set<AnyCancellable>()
76+
77+
init(subscriber: S, notification: NotificationCenter, name: Notification.Name, object: AnyObject?) {
78+
self.subscriber = subscriber
79+
self.notification = notification
80+
notification.publisher(for: name, object: object)
81+
.sink {[weak self] notification in
82+
_ = self?.subscriber?.receive(notification)
83+
}
84+
.store(in: &bag)
85+
}
86+
87+
func request(_ demand: Subscribers.Demand) {
88+
89+
}
90+
91+
func cancel() {
92+
subscriber = nil
93+
}
94+
}
95+
}
96+
97+
extension Publishers.CombineControlNotificationKeyPath {
98+
private final class SubscriptionNotification<S: Subscriber, Value>: Subscription where S.Input == Value {
99+
private var subscriber: S?
100+
private let notification: NotificationCenter
101+
private var bag = Set<AnyCancellable>()
102+
103+
init(subscriber: S, notification: NotificationCenter, keyPath: KeyPath<NotificationCenter, Value>) {
104+
self.subscriber = subscriber
105+
self.notification = notification
106+
notification
107+
.publisher(for: keyPath)
108+
.sink {[weak self] value in
109+
_ = self?.subscriber?.receive(value)
110+
}
111+
.store(in: &bag)
112+
}
113+
114+
func request(_ demand: Subscribers.Demand) {
115+
116+
}
117+
118+
func cancel() {
119+
subscriber = nil
120+
}
121+
}
122+
}
123+
124+
extension Publishers.CombineControlNotificationKeyPathOption {
125+
private final class SubscriptionNotification<S: Subscriber, Value>: Subscription where S.Input == Value {
126+
private var subscriber: S?
127+
private let notification: NotificationCenter
128+
private var bag = Set<AnyCancellable>()
129+
130+
init(subscriber: S, notification: NotificationCenter, keyPath: KeyPath<NotificationCenter, Value>, options: NSKeyValueObservingOptions) {
131+
self.subscriber = subscriber
132+
self.notification = notification
133+
notification
134+
.publisher(for: keyPath, options: options)
135+
.sink {[weak self] value in
136+
_ = self?.subscriber?.receive(value)
137+
}
138+
.store(in: &bag)
139+
}
140+
141+
func request(_ demand: Subscribers.Demand) {
142+
}
143+
144+
func cancel() {
145+
subscriber = nil
146+
}
147+
}
148+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#if canImport(UIKit) && !os(watchOS)
2+
import CombineExtension
3+
import Combine
4+
import UIKit
5+
6+
extension CombineExtension where Base: UIActivityIndicatorView {
7+
var isAnimating: BindingSink<Base, Bool> {
8+
BindingSink(owner: base) { base, value in
9+
if value {
10+
base.startAnimating()
11+
} else {
12+
base.stopAnimating()
13+
}
14+
}
15+
}
16+
}
17+
18+
#endif
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#if canImport(UIKit) && !os(watchOS)
2+
import CombineExtension
3+
import Combine
4+
import UIKit
5+
6+
extension CombineExtension where Base: UIAlertAction {
7+
/// Bindable sink for `enabled` property.
8+
var isEnabled: BindingSink<Base, Bool> {
9+
BindingSink(owner: base) { $0.isEnabled = $1 }
10+
}
11+
}
12+
13+
#endif

Sources/UICombineExtension/Controls/UIBarButtonItem+Combine.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,13 @@ public extension CombineExtension where Base: UIBarButtonItem {
2929
var isEnabled: BindingSink<Base, Bool> {
3030
BindingSink(owner: base) { $0.isEnabled = $1 }
3131
}
32+
33+
var title: BindingSink<Base, String?> {
34+
BindingSink(owner: base) { $0.title = $1 }
35+
}
36+
37+
var image: BindingSink<Base, UIImage?> {
38+
BindingSink(owner: base) { $0.image = $1 }
39+
}
3240
}
3341
#endif

Sources/UICombineExtension/Controls/UIButton+Combine.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,34 @@ public extension CombineExtension where Base: UIButton {
1010
}
1111
}
1212

13+
public extension CombineExtension where Base: UIButton {
14+
/// Reactive wrapper for `setTitle(_:for:)`
15+
func title(for controlState: UIControl.State = []) -> BindingSink<Base, String?> {
16+
return BindingSink(owner: base) { button, title -> Void in
17+
button.setTitle(title, for: controlState)
18+
}
19+
}
20+
21+
/// Reactive wrapper for `setImage(_:for:)`
22+
func image(for controlState: UIControl.State = []) -> BindingSink<Base, UIImage?> {
23+
return BindingSink(owner: base) { button, image -> Void in
24+
button.setImage(image, for: controlState)
25+
}
26+
}
27+
28+
/// Reactive wrapper for `setBackgroundImage(_:for:)`
29+
func backgroundImage(for controlState: UIControl.State = []) -> BindingSink<Base, UIImage?> {
30+
return BindingSink(owner: base) { button, image -> Void in
31+
button.setBackgroundImage(image, for: controlState)
32+
}
33+
}
34+
35+
/// Reactive wrapper for `setAttributedTitle(_:controlState:)`
36+
func attributedTitle(for controlState: UIControl.State = []) -> BindingSink<Base, NSAttributedString?> {
37+
return BindingSink(owner: base) { button, attributedTitle -> Void in
38+
button.setAttributedTitle(attributedTitle, for: controlState)
39+
}
40+
}
41+
}
42+
1343
#endif

Sources/UICombineExtension/Controls/UIDatePicker+Combine.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import UIKit
1212

1313
public extension CombineExtension where Base: UIDatePicker {
1414
/// A publisher emitting date changes from this date picker.
15-
var date: AnyPublisher<Date, Never> {
15+
var value: AnyPublisher<Date, Never> {
1616
Publishers.ControlProperty(control: base,
1717
events: .defaultValueEvents,
1818
keyPath: \.date)
@@ -26,5 +26,9 @@ public extension CombineExtension where Base: UIDatePicker {
2626
keyPath: \.countDownDuration)
2727
.eraseToAnyPublisher()
2828
}
29+
30+
var date: BindingSink<Base, Date> {
31+
BindingSink(owner: base) { $0.date = $1 }
32+
}
2933
}
3034
#endif
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#if canImport(UIKit) && !os(watchOS)
2+
import CombineExtension
3+
import Combine
4+
import UIKit
5+
6+
extension CombineExtension where Base: UIImageView {
7+
var image: BindingSink<Base, UIImage?> {
8+
BindingSink(owner: base) { $0.image = $1 }
9+
}
10+
11+
var data: BindingSink<Base, Data> {
12+
BindingSink(owner: base) { $0.image = UIImage(data: $1) }
13+
}
14+
}
15+
16+
#endif

0 commit comments

Comments
 (0)