Skip to content

Commit fce17d7

Browse files
committed
UIControl extension to bind events to blocks (iOS 14+)
1 parent 386daad commit fce17d7

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import UIKit
2+
3+
@available(iOS 14.0, *)
4+
public extension UIControl {
5+
6+
/// Adds `action` block to `self` to run for specified `event`.
7+
///
8+
/// - Parameters:
9+
/// - event: UIControl.Event to react on
10+
/// - action: block to run for `event`
11+
/// - Returns: self
12+
func on(_ event: UIControl.Event, do action: @escaping () -> Void) -> Self {
13+
addAction(
14+
UIAction { _ in
15+
action()
16+
},
17+
for: event
18+
)
19+
return self
20+
}
21+
22+
/// Adds async `action` block to `self` to run for specified `event`.
23+
///
24+
/// - Parameters:
25+
/// - event: UIControl.Event to react on
26+
/// - action: async block to run for `event`
27+
/// - Returns: self
28+
///
29+
/// Action will be started concurrently in cooperative thread pool. It would not inherit parent's actor.
30+
/// If you need to run block's code with specific actor/queue you should take care of it by yourself.
31+
/// E.g. if you'll need to run some code on MainActor you should wrap it with `MainActor.run` or annotate function called from block with @MainActor.
32+
///
33+
/// NB: Adding @MainActor annotation to `action` block will be not enough!
34+
///
35+
/// # Example:
36+
///
37+
/// // MyViewController
38+
/// override func viewDidLoad() {
39+
/// // ...
40+
/// addSubview(
41+
/// UI.VStack(
42+
/// UIButton()
43+
/// .apply { $0.setTitle("First button", for: .normal) }
44+
/// .on(.touchUpInside, do: viewModel.firstButtonTapped),
45+
/// UIButton()
46+
/// .apply { $0.setTitle("Second button", for: .normal) }
47+
/// .on(.touchUpInside) { [unowned self] in await viewModel.secondButtonTapped() }
48+
/// ),
49+
/// padding: 20
50+
/// )
51+
/// // ...
52+
/// }
53+
///
54+
/// // MyViewModel
55+
/// @MainActor
56+
/// func firstButtonTapped() async {
57+
/// // code from this funciton will run on MainActor
58+
/// }
59+
///
60+
/// func secondButtonTapped() async {
61+
/// // following code will NOT run on MainActor
62+
/// doSomethingSync()
63+
/// await doSomethingAsync()
64+
///
65+
/// await MainActor.run {
66+
/// updateUI() // syncronous func will run on MainActor
67+
/// }
68+
/// }
69+
///
70+
func on(_ event: UIControl.Event, do action: @escaping () async -> Void) -> Self {
71+
addAction(
72+
UIAction { _ in
73+
Task {
74+
// NB: action does not run on MainActor!
75+
await action()
76+
}
77+
},
78+
for: event
79+
)
80+
return self
81+
}
82+
83+
}

0 commit comments

Comments
 (0)