Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 0e7a3d8

Browse files
authored
feat: TextField keypath publisher
2 parents 88f9938 + ce0452d commit 0e7a3d8

File tree

6 files changed

+203
-2
lines changed

6 files changed

+203
-2
lines changed

Sources/GoodCombineExtensions/UIKit/UIControlPublisher.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,50 @@ public extension GRActive where Base: UIControl {
4545

4646
}
4747

48+
// MARK: - Keypath Publisher
49+
50+
@available(iOS 13.0, *)
51+
public extension Combine.Publishers {
52+
53+
/// A Control Property is a publisher that emits the value at the provided keypath
54+
/// whenever the specific control events are triggered. It also emits the keypath's
55+
/// initial value upon subscription.
56+
struct ControlProperty<Control: UIControl, Value>: Publisher {
57+
58+
public typealias Output = Value
59+
public typealias Failure = Never
60+
61+
private let control: Control
62+
private let controlEvents: Control.Event
63+
private let keyPath: KeyPath<Control, Value>
64+
65+
/// Initialize a publisher that emits the value at the specified keypath
66+
/// whenever any of the provided Control Events trigger.
67+
///
68+
/// - parameter control: UI Control.
69+
/// - parameter events: Control Events.
70+
/// - parameter keyPath: A Key Path from the UI Control to the requested value.
71+
public init(
72+
control: Control,
73+
events: Control.Event,
74+
keyPath: KeyPath<Control, Value>
75+
) {
76+
self.control = control
77+
self.controlEvents = events
78+
self.keyPath = keyPath
79+
}
80+
81+
public func receive<S: Subscriber>(subscriber: S) where S.Failure == Failure, S.Input == Output {
82+
let subscription = UIControlKeyPathSubscription(
83+
subscriber: subscriber,
84+
control: control,
85+
event: controlEvents,
86+
keyPath: keyPath
87+
)
88+
89+
subscriber.receive(subscription: subscription)
90+
}
91+
92+
}
93+
94+
}

Sources/GoodCombineExtensions/UIKit/UIControlSubscriber.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,49 @@ final public class UIControlSubscription<SubscriberType: Subscriber, Control: UI
3737
_ = subscriber?.receive(control)
3838
}
3939
}
40+
41+
// swiftlint:disable line_length
42+
@available(iOS 13.0, *)
43+
public final class UIControlKeyPathSubscription<S: Subscriber, Control: UIControl, Value>: Combine.Subscription where S.Input == Value {
44+
// swiftlint:enable line_length
45+
46+
private var subscriber: S?
47+
weak private var control: Control?
48+
let keyPath: KeyPath<Control, Value>
49+
private var didEmitInitial = false
50+
private let event: Control.Event
51+
52+
init(subscriber: S, control: Control, event: Control.Event, keyPath: KeyPath<Control, Value>) {
53+
self.subscriber = subscriber
54+
self.control = control
55+
self.keyPath = keyPath
56+
self.event = event
57+
control.addTarget(self, action: #selector(handleEvent), for: event)
58+
}
59+
60+
public func request(_ demand: Subscribers.Demand) {
61+
// Emit initial value upon first demand request
62+
if !didEmitInitial,
63+
demand > .none,
64+
let control = control,
65+
let subscriber = subscriber {
66+
_ = subscriber.receive(control[keyPath: keyPath])
67+
didEmitInitial = true
68+
}
69+
70+
// We don't care about the demand at this point.
71+
// As far as we're concerned - UIControl events are endless until the control is deallocated.
72+
}
73+
74+
public func cancel() {
75+
control?.removeTarget(self, action: #selector(handleEvent), for: event)
76+
subscriber = nil
77+
}
78+
79+
@objc private func handleEvent() {
80+
guard let control = control else { return }
81+
82+
_ = subscriber?.receive(control[keyPath: keyPath])
83+
}
84+
85+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// UISwitchCombineExtension.swift
3+
//
4+
//
5+
// Created by Andrej Jasso on 11/03/2022.
6+
//
7+
8+
import Combine
9+
import GRCompatible
10+
import UIKit
11+
12+
public extension GRActive where Base: UISwitch {
13+
14+
var isOnPublisher: AnyPublisher<Bool, Never> {
15+
Publishers.ControlProperty(control: base, events: .valueChanged, keyPath: \.isOn)
16+
.eraseToAnyPublisher()
17+
}
18+
19+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// UITextFieldPublisher.swift
3+
//
4+
//
5+
// Created by Andrej Jasso on 10/03/2022.
6+
//
7+
8+
import Combine
9+
import GRCompatible
10+
import UIKit
11+
12+
@available(iOS 13.0, *)
13+
public struct UITextFieldPublisher<TextField: UITextField>: Publisher {
14+
15+
public typealias Output = TextField
16+
public typealias Failure = Never
17+
18+
weak var textfield: TextField?
19+
let event: UITextField.Event
20+
21+
init(control: TextField, event: UIControl.Event) {
22+
self.textfield = control
23+
self.event = event
24+
}
25+
26+
public func receive<S>(subscriber: S) where S: Subscriber, Never == S.Failure, TextField == S.Input {
27+
let subscription = UITextFieldSubscription(
28+
subscriber: subscriber,
29+
textfield: textfield,
30+
event: event
31+
)
32+
subscriber.receive(subscription: subscription)
33+
}
34+
35+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// UITextFieldSubscriber.swift
3+
//
4+
//
5+
// Created by Andrej Jasso on 11/03/2022.
6+
//
7+
8+
import Combine
9+
import GRCompatible
10+
import UIKit
11+
12+
// swiftlint:disable line_length
13+
@available(iOS 13.0, *)
14+
final public class UITextFieldSubscription<SubscriberType: Subscriber, TextField: UITextField>: Subscription where SubscriberType.Input == TextField {
15+
// swiftlint:enable line_length
16+
17+
private var subscriber: SubscriberType?
18+
private weak var textfield: TextField?
19+
20+
init(subscriber: SubscriberType, textfield: TextField?, event: UITextField.Event) {
21+
self.subscriber = subscriber
22+
self.textfield = textfield
23+
24+
textfield?.addTarget(self, action: #selector(eventHandler), for: event)
25+
}
26+
27+
public func request(_ demand: Subscribers.Demand) {}
28+
29+
public func cancel() {
30+
subscriber = nil
31+
}
32+
33+
@objc private func eventHandler() {
34+
guard let textfield = textfield else { return }
35+
_ = subscriber?.receive(textfield)
36+
}
37+
}
38+
39+
public extension GRActive where Base: UITextField {
40+
41+
func eventPublisher(for event: UIControl.Event) -> UITextFieldPublisher<UITextField> {
42+
UITextFieldPublisher(control: base, event: event)
43+
}
44+
45+
}
46+
47+
public extension GRActive where Base: UITextField {
48+
49+
var textPublisher: AnyPublisher<String?, Never> {
50+
Publishers.ControlProperty(control: base, events: [.valueChanged, .allEditingEvents], keyPath: \.text)
51+
.eraseToAnyPublisher()
52+
}
53+
54+
}

Sources/GoodExtensions/Foundation/DateExtentions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
2-
// DateExtensions.swift
3-
// GoodExtensions
2+
// Date.swift
3+
//
44
//
55
// Created by Andrej Jasso on 08/06/2021.
66
// Copyright © 2020 GoodRequest. All rights reserved.

0 commit comments

Comments
 (0)