Skip to content

Commit 393e8ff

Browse files
authored
Introduce Binding<Bool>.init<V>(_: Binding<V?>) (pointfreeco#160)
* Introduce `Binding<Bool>.init<V>(_: Binding<V?>)` We currently have an `isPresent()` method for transforming bindings, but it seems to be an outlier compared to most binding transformations that come with SwiftUI, which use initializers. While updating docs I noticed the core module wasn't really organized at all, so took a quick pass. * wip * wip
1 parent dacc399 commit 393e8ff

18 files changed

+255
-45
lines changed

Examples/CaseStudies/09-CustomComponents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ extension View {
9898
where Content: View {
9999
modifier(
100100
BottomMenuModifier(
101-
isActive: item.isPresent(),
101+
isActive: Binding(item),
102102
content: { Binding(unwrapping: item).map(content) }
103103
)
104104
)

Sources/SwiftUINavigation/Internal/Deprecations.swift

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
) -> some View
2020
where Content: View {
2121
self.fullScreenCover(
22-
isPresented: value.isPresent(),
22+
isPresented: Binding(value),
2323
onDismiss: onDismiss
2424
) {
2525
Binding(unwrapping: value).map(content)
@@ -41,12 +41,12 @@
4141
if requiresBindWorkaround {
4242
self.modifier(
4343
_NavigationDestinationBindWorkaround(
44-
isPresented: value.isPresent(),
44+
isPresented: Binding(value),
4545
destination: Binding(unwrapping: value).map(destination)
4646
)
4747
)
4848
} else {
49-
self.navigationDestination(isPresented: value.isPresent()) {
49+
self.navigationDestination(isPresented: Binding(value)) {
5050
Binding(unwrapping: value).map(destination)
5151
}
5252
}
@@ -92,7 +92,7 @@
9292
@ViewBuilder content: @escaping (Binding<Value>) -> Content
9393
) -> some View {
9494
self.popover(
95-
isPresented: value.isPresent(),
95+
isPresented: Binding(value),
9696
attachmentAnchor: attachmentAnchor,
9797
arrowEdge: arrowEdge
9898
) {
@@ -112,7 +112,7 @@
112112
@ViewBuilder content: @escaping (Binding<Value>) -> Content
113113
) -> some View
114114
where Content: View {
115-
self.sheet(isPresented: value.isPresent(), onDismiss: onDismiss) {
115+
self.sheet(isPresented: Binding(value), onDismiss: onDismiss) {
116116
Binding(unwrapping: value).map(content)
117117
}
118118
}
@@ -132,7 +132,7 @@
132132
) where Destination == WrappedDestination? {
133133
self.init(
134134
destination: Binding(unwrapping: value).map(destination),
135-
isActive: value.isPresent().didSet(onNavigate),
135+
isActive: Binding(value).didSet(onNavigate),
136136
label: label
137137
)
138138
}
@@ -164,7 +164,7 @@
164164
) -> some View {
165165
self.confirmationDialog(
166166
value.wrappedValue.map(title) ?? Text(verbatim: ""),
167-
isPresented: value.isPresent(),
167+
isPresented: Binding(value),
168168
titleVisibility: titleVisibility,
169169
presenting: value.wrappedValue,
170170
actions: actions,
@@ -184,7 +184,7 @@
184184
) -> some View {
185185
alert(
186186
(value.wrappedValue?.title).map(Text.init) ?? Text(verbatim: ""),
187-
isPresented: value.isPresent(),
187+
isPresented: Binding(value),
188188
presenting: value.wrappedValue,
189189
actions: {
190190
ForEach($0.buttons) {
@@ -196,13 +196,13 @@
196196
}
197197

198198
@available(*, deprecated, renamed: "alert(_:action:)")
199-
public func alert<Value>(
199+
public func alert<Value: Sendable>(
200200
unwrapping value: Binding<AlertState<Value>?>,
201-
action handler: @escaping (Value?) async -> Void = { (_: Never?) async in }
201+
action handler: @escaping @Sendable (Value?) async -> Void = { (_: Never?) async in }
202202
) -> some View {
203203
alert(
204204
(value.wrappedValue?.title).map(Text.init) ?? Text(verbatim: ""),
205-
isPresented: value.isPresent(),
205+
isPresented: Binding(value),
206206
presenting: value.wrappedValue,
207207
actions: {
208208
ForEach($0.buttons) {
@@ -220,7 +220,7 @@
220220
) -> some View {
221221
confirmationDialog(
222222
value.wrappedValue.flatMap { Text($0.title) } ?? Text(verbatim: ""),
223-
isPresented: value.isPresent(),
223+
isPresented: Binding(value),
224224
titleVisibility: value.wrappedValue.map { .init($0.titleVisibility) } ?? .automatic,
225225
presenting: value.wrappedValue,
226226
actions: {
@@ -233,13 +233,13 @@
233233
}
234234

235235
@available(*, deprecated, renamed: "confirmationDialog(_:action:)")
236-
public func confirmationDialog<Value>(
236+
public func confirmationDialog<Value: Sendable>(
237237
unwrapping value: Binding<ConfirmationDialogState<Value>?>,
238-
action handler: @escaping (Value?) async -> Void = { (_: Never?) async in }
238+
action handler: @escaping @Sendable (Value?) async -> Void = { (_: Never?) async in }
239239
) -> some View {
240240
confirmationDialog(
241241
value.wrappedValue.flatMap { Text($0.title) } ?? Text(verbatim: ""),
242-
isPresented: value.isPresent(),
242+
isPresented: Binding(value),
243243
titleVisibility: value.wrappedValue.map { .init($0.titleVisibility) } ?? .automatic,
244244
presenting: value.wrappedValue,
245245
actions: {
@@ -292,10 +292,10 @@
292292
message:
293293
"Chain a '@CasePathable' enum binding into a case directly instead of specifying a case path."
294294
)
295-
public func alert<Enum, Value>(
295+
public func alert<Enum, Value: Sendable>(
296296
unwrapping enum: Binding<Enum?>,
297297
case casePath: AnyCasePath<Enum, AlertState<Value>>,
298-
action handler: @escaping (Value?) async -> Void = { (_: Never?) async in }
298+
action handler: @escaping @Sendable (Value?) async -> Void = { (_: Never?) async in }
299299
) -> some View {
300300
alert(`enum`.case(casePath), action: handler)
301301
}
@@ -343,10 +343,10 @@
343343
message:
344344
"Chain a '@CasePathable' enum binding into a case directly instead of specifying a case path."
345345
)
346-
public func confirmationDialog<Enum, Value>(
346+
public func confirmationDialog<Enum, Value: Sendable>(
347347
unwrapping enum: Binding<Enum?>,
348348
case casePath: AnyCasePath<Enum, ConfirmationDialogState<Value>>,
349-
action handler: @escaping (Value?) async -> Void = { (_: Never?) async in }
349+
action handler: @escaping @Sendable (Value?) async -> Void = { (_: Never?) async in }
350350
) -> some View {
351351
confirmationDialog(
352352
`enum`.case(casePath),
@@ -521,7 +521,7 @@
521521
)
522522
public func isPresent<Enum, Case>(_ casePath: AnyCasePath<Enum, Case>) -> Binding<Bool>
523523
where Value == Enum? {
524-
self.case(casePath).isPresent()
524+
.init(self.case(casePath))
525525
}
526526
}
527527

@@ -767,7 +767,7 @@
767767
message:
768768
"Switch over a '@CasePathable' enum and derive bindings from each case using '$enum.case.map { $case in … }', instead."
769769
)
770-
public struct CaseLet<Enum, Case, Content>: View
770+
public struct CaseLet<Enum, Case, Content>: Sendable, View
771771
where Content: View {
772772
@EnvironmentObject private var `enum`: BindingObject<Enum>
773773
public let casePath: AnyCasePath<Enum, Case>
@@ -1847,9 +1847,9 @@
18471847
message:
18481848
"'View.alert' now passes an optional action to its handler to allow you to handle action-less dismissals."
18491849
)
1850-
public func alert<Value>(
1850+
public func alert<Value: Sendable>(
18511851
unwrapping value: Binding<AlertState<Value>?>,
1852-
action handler: @escaping (Value) async -> Void = { (_: Void) async in }
1852+
action handler: @escaping @Sendable (Value) async -> Void = { (_: Void) async in }
18531853
) -> some View {
18541854
alert(value) { (value: Value?) in
18551855
if let value = value {
@@ -1865,10 +1865,10 @@
18651865
message:
18661866
"'View.alert' now passes an optional action to its handler to allow you to handle action-less dismissals."
18671867
)
1868-
public func alert<Enum, Value>(
1868+
public func alert<Enum, Value: Sendable>(
18691869
unwrapping enum: Binding<Enum?>,
18701870
case casePath: CasePath<Enum, AlertState<Value>>,
1871-
action handler: @escaping (Value) async -> Void = { (_: Void) async in }
1871+
action handler: @escaping @Sendable (Value) async -> Void = { (_: Void) async in }
18721872
) -> some View {
18731873
alert(unwrapping: `enum`, case: casePath) { (value: Value?) async in
18741874
if let value = value {
@@ -1884,9 +1884,9 @@
18841884
message:
18851885
"'View.alert' now passes an optional action to its handler to allow you to handle action-less dismissals."
18861886
)
1887-
public func confirmationDialog<Value>(
1887+
public func confirmationDialog<Value: Sendable>(
18881888
unwrapping value: Binding<ConfirmationDialogState<Value>?>,
1889-
action handler: @escaping (Value) async -> Void = { (_: Void) async in }
1889+
action handler: @escaping @Sendable (Value) async -> Void = { (_: Void) async in }
18901890
) -> some View {
18911891
confirmationDialog(unwrapping: value) { (value: Value?) in
18921892
if let value = value {
@@ -1902,10 +1902,10 @@
19021902
message:
19031903
"'View.alert' now passes an optional action to its handler to allow you to handle action-less dismissals."
19041904
)
1905-
public func confirmationDialog<Enum, Value>(
1905+
public func confirmationDialog<Enum, Value: Sendable>(
19061906
unwrapping enum: Binding<Enum?>,
19071907
case casePath: CasePath<Enum, ConfirmationDialogState<Value>>,
1908-
action handler: @escaping (Value) async -> Void = { (_: Void) async in }
1908+
action handler: @escaping @Sendable (Value) async -> Void = { (_: Void) async in }
19091909
) -> some View {
19101910
confirmationDialog(unwrapping: `enum`, case: casePath) { (value: Value?) async in
19111911
if let value = value {
@@ -1941,7 +1941,7 @@
19411941
) where Destination == WrappedDestination? {
19421942
self.init(
19431943
destination: Binding(unwrapping: value).map(destination),
1944-
isActive: value.isPresent().didSet(onNavigate),
1944+
isActive: Binding(value).didSet(onNavigate),
19451945
label: label
19461946
)
19471947
}

Sources/SwiftUINavigation/NavigationLink.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
) where Destination == WrappedDestination? {
6363
self.init(
6464
destination: Binding(unwrapping: item).map(destination),
65-
isActive: item.isPresent().didSet(onNavigate),
65+
isActive: Binding(item).didSet(onNavigate),
6666
label: label
6767
)
6868
}

Sources/SwiftUINavigationCore/Alert.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
) -> some View {
6868
alert(
6969
item.wrappedValue.map(title) ?? Text(verbatim: ""),
70-
isPresented: item.isPresent(),
70+
isPresented: Binding(item),
7171
presenting: item.wrappedValue,
7272
actions: actions,
7373
message: message
@@ -132,7 +132,7 @@
132132
) -> some View {
133133
alert(
134134
item.wrappedValue.map(title) ?? Text(verbatim: ""),
135-
isPresented: item.isPresent(),
135+
isPresented: Binding(item),
136136
presenting: item.wrappedValue,
137137
actions: actions
138138
)

Sources/SwiftUINavigationCore/AlertState.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,18 @@
219219

220220
// MARK: - SwiftUI bridging
221221

222+
@available(
223+
iOS, introduced: 13, deprecated: 100000, message: "use 'View.alert(_:action:)' instead."
224+
)
225+
@available(
226+
macOS, introduced: 10.15, deprecated: 100000, message: "use 'View.alert(_:action:)' instead."
227+
)
228+
@available(
229+
tvOS, introduced: 13, deprecated: 100000, message: "use 'View.alert(_:action:)' instead."
230+
)
231+
@available(
232+
watchOS, introduced: 6, deprecated: 100000, message: "use 'View.alert(_:action:)' instead."
233+
)
222234
extension Alert {
223235
/// Creates an alert from alert state.
224236
///

Sources/SwiftUINavigationCore/Binding.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
import SwiftUI
33

44
extension Binding {
5-
/// Creates a binding by projecting the current optional value to a boolean describing if it's
6-
/// non-`nil`.
5+
/// Creates a binding by projecting the base optional value to a Boolean value.
76
///
87
/// Writing `false` to the binding will `nil` out the base value. Writing `true` does nothing.
98
///
10-
/// - Returns: A binding to a boolean. Returns `true` if non-`nil`, otherwise `false`.
11-
public func isPresent<Wrapped>() -> Binding<Bool>
12-
where Value == Wrapped? {
13-
self._isPresent
9+
/// - Parameter base: A value to project to a Boolean value.
10+
public init<V>(_ base: Binding<V?>) where Value == Bool {
11+
self = base._isPresent
1412
}
1513
}
1614

@@ -23,5 +21,4 @@
2321
}
2422
}
2523
}
26-
2724
#endif

Sources/SwiftUINavigationCore/ConfirmationDialog.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
) -> some View {
7171
confirmationDialog(
7272
item.wrappedValue.map(title) ?? Text(verbatim: ""),
73-
isPresented: item.isPresent(),
73+
isPresented: Binding(item),
7474
titleVisibility: titleVisibility,
7575
presenting: item.wrappedValue,
7676
actions: actions,
@@ -140,7 +140,7 @@
140140
) -> some View {
141141
confirmationDialog(
142142
item.wrappedValue.map(title) ?? Text(verbatim: ""),
143-
isPresented: item.isPresent(),
143+
isPresented: Binding(item),
144144
titleVisibility: titleVisibility,
145145
presenting: item.wrappedValue,
146146
actions: actions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# ``SwiftUINavigationCore/AlertState``
2+
3+
## Topics
4+
5+
### Creating alerts
6+
7+
- ``init(title:actions:message:)``
8+
9+
### Reading alert data
10+
11+
- ``id``
12+
- ``title``
13+
- ``message``
14+
- ``buttons``
15+
16+
### Transforming alerts
17+
18+
- ``map(_:)``
19+
20+
### Deprecations
21+
22+
- <doc:AlertStateDeprecations>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Deprecations
2+
3+
Review unsupported SwiftUI Navigation APIs and their replacements.
4+
5+
## Overview
6+
7+
Avoid using deprecated APIs in your app. Select a method to see the replacement that you should use
8+
instead.
9+
10+
## Topics
11+
12+
### Creating alerts
13+
14+
- ``AlertState/init(title:message:primaryButton:secondaryButton:)``
15+
- ``AlertState/init(title:message:dismissButton:)``
16+
- ``AlertState/init(title:message:buttons:)``
17+
18+
### Supporting types
19+
20+
- ``AlertState/Button``
21+
- ``AlertState/ButtonAction``
22+
- ``AlertState/ButtonRole``
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ``SwiftUINavigationCore/ButtonState``
2+
3+
## Topics
4+
5+
### Creating buttons
6+
7+
- ``init(role:action:label:)-99wi3``
8+
- ``init(role:action:label:)-2ixoi``
9+
- ``ButtonStateRole``
10+
- ``ButtonStateAction``
11+
12+
### Composing buttons
13+
14+
- ``ButtonStateBuilder``
15+
16+
### Reading button data
17+
18+
- ``id``
19+
- ``role-swift.property``
20+
- ``action``
21+
- ``label``
22+
23+
### Performing actions
24+
25+
- ``withAction(_:)-56ifj``
26+
- ``withAction(_:)-71nj4``
27+
28+
### Transforming buttons
29+
30+
- ``SwiftUI/Button``
31+
- ``SwiftUI/ButtonRole``
32+
- ``map(_:)``
33+
34+
### Deprecations
35+
36+
- <doc:ButtonStateDeprecations>

0 commit comments

Comments
 (0)