Skip to content

Commit c4a299d

Browse files
committed
Update AlertState to use new APIs (#794)
* Update AlertState to use new APIs * fix * Feedback * wip * Fix * wip * wip * wip * wip
1 parent c3842e5 commit c4a299d

File tree

13 files changed

+1074
-896
lines changed

13 files changed

+1074
-896
lines changed

Examples/CaseStudies/CaseStudies.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
CA3E4C5B24B4FA0E00447C0B /* 04-HigherOrderReducers-Lifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3E4C5A24B4FA0E00447C0B /* 04-HigherOrderReducers-Lifecycle.swift */; };
1818
CA410EE0247A15FE00E41798 /* 02-Effects-WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA410EDF247A15FE00E41798 /* 02-Effects-WebSocket.swift */; };
1919
CA410EE2247C73B400E41798 /* 02-Effects-WebSocketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA410EE1247C73B400E41798 /* 02-Effects-WebSocketTests.swift */; };
20-
CA50BE6024A8F46500FE7DBA /* 01-GettingStarted-AlertsAndActionSheetsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndActionSheetsTests.swift */; };
20+
CA50BE6024A8F46500FE7DBA /* 01-GettingStarted-AlertsAndConfirmationDialogsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndConfirmationDialogsTests.swift */; };
2121
CA5ECF92267A79F0002067FF /* FactClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA5ECF91267A79F0002067FF /* FactClient.swift */; };
2222
CA6AC2642451135C00C71CB3 /* ReusableComponents-Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6AC2602451135C00C71CB3 /* ReusableComponents-Download.swift */; };
2323
CA6AC2652451135C00C71CB3 /* CircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6AC2612451135C00C71CB3 /* CircularProgressView.swift */; };
@@ -32,7 +32,7 @@
3232
CAA9ADCC2446615B0003A984 /* 02-Effects-LongLivingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA9ADCB2446615B0003A984 /* 02-Effects-LongLivingTests.swift */; };
3333
CABC4F3926AEE00C00D5FA2C /* 02-Effects-Refreshable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABC4F3826AEE00C00D5FA2C /* 02-Effects-Refreshable.swift */; };
3434
CABC4F3B26AEE20200D5FA2C /* 02-Effects-RefreshableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABC4F3A26AEE20200D5FA2C /* 02-Effects-RefreshableTests.swift */; };
35-
CAE962FD24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */; };
35+
CAE962FD24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndConfirmationDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndConfirmationDialogs.swift */; };
3636
CAF069D024ACC5AF00A1AAEF /* 00-Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */; };
3737
CAF88E7324B8E26D00539345 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF88E7224B8E26D00539345 /* AppDelegate.swift */; };
3838
CAF88E7524B8E26D00539345 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF88E7424B8E26D00539345 /* RootView.swift */; };
@@ -160,7 +160,7 @@
160160
CA3E4C5A24B4FA0E00447C0B /* 04-HigherOrderReducers-Lifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "04-HigherOrderReducers-Lifecycle.swift"; sourceTree = "<group>"; };
161161
CA410EDF247A15FE00E41798 /* 02-Effects-WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-WebSocket.swift"; sourceTree = "<group>"; };
162162
CA410EE1247C73B400E41798 /* 02-Effects-WebSocketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-WebSocketTests.swift"; sourceTree = "<group>"; };
163-
CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndActionSheetsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AlertsAndActionSheetsTests.swift"; sourceTree = "<group>"; };
163+
CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndConfirmationDialogsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AlertsAndConfirmationDialogsTests.swift"; sourceTree = "<group>"; };
164164
CA5ECF91267A79F0002067FF /* FactClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FactClient.swift; sourceTree = "<group>"; };
165165
CA6AC2602451135C00C71CB3 /* ReusableComponents-Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReusableComponents-Download.swift"; sourceTree = "<group>"; };
166166
CA6AC2612451135C00C71CB3 /* CircularProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = "<group>"; };
@@ -175,7 +175,7 @@
175175
CAA9ADCB2446615B0003A984 /* 02-Effects-LongLivingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-LongLivingTests.swift"; sourceTree = "<group>"; };
176176
CABC4F3826AEE00C00D5FA2C /* 02-Effects-Refreshable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-Refreshable.swift"; sourceTree = "<group>"; };
177177
CABC4F3A26AEE20200D5FA2C /* 02-Effects-RefreshableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "02-Effects-RefreshableTests.swift"; sourceTree = "<group>"; };
178-
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AlertsAndActionSheets.swift"; sourceTree = "<group>"; };
178+
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndConfirmationDialogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "01-GettingStarted-AlertsAndConfirmationDialogs.swift"; sourceTree = "<group>"; };
179179
CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "00-Core.swift"; sourceTree = "<group>"; };
180180
CAF88E7024B8E26D00539345 /* tvOSCaseStudies.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSCaseStudies.app; sourceTree = BUILT_PRODUCTS_DIR; };
181181
CAF88E7224B8E26D00539345 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -393,7 +393,7 @@
393393
DC89C42424460F96006900B9 /* Info.plist */,
394394
CAF069CF24ACC5AF00A1AAEF /* 00-Core.swift */,
395395
DC89C41A24460F95006900B9 /* 00-RootView.swift */,
396-
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift */,
396+
CAE962FC24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndConfirmationDialogs.swift */,
397397
DC88D8A5245341EC0077F427 /* 01-GettingStarted-Animations.swift */,
398398
CA25E5D124463AD700DA666A /* 01-GettingStarted-Bindings-Basics.swift */,
399399
DC5B505025C86EBC000D8DFD /* 01-GettingStarted-Bindings-Forms.swift */,
@@ -432,7 +432,7 @@
432432
DC89C42C24460F96006900B9 /* SwiftUICaseStudiesTests */ = {
433433
isa = PBXGroup;
434434
children = (
435-
CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndActionSheetsTests.swift */,
435+
CA50BE5F24A8F46500FE7DBA /* 01-GettingStarted-AlertsAndConfirmationDialogsTests.swift */,
436436
CA34170724A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift */,
437437
DC27215525BF84FC00D9C8DB /* 01-GettingStarted-BindingBasicsTests.swift */,
438438
4F5AC11E24ECC7E4009DC50B /* 01-GettingStarted-SharedStateTests.swift */,
@@ -763,7 +763,7 @@
763763
DCC68EDD2447A5B00037F998 /* 01-GettingStarted-OptionalState.swift in Sources */,
764764
CABC4F3926AEE00C00D5FA2C /* 02-Effects-Refreshable.swift in Sources */,
765765
DCC68EAB244666AF0037F998 /* 03-Navigation-Sheet-PresentAndLoad.swift in Sources */,
766-
CAE962FD24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndActionSheets.swift in Sources */,
766+
CAE962FD24A7F7BE00EFC025 /* 01-GettingStarted-AlertsAndConfirmationDialogs.swift in Sources */,
767767
CA25E5D224463AD700DA666A /* 01-GettingStarted-Bindings-Basics.swift in Sources */,
768768
DC88D8A6245341EC0077F427 /* 01-GettingStarted-Animations.swift in Sources */,
769769
DC89C44D244621A5006900B9 /* 03-Navigation-NavigateAndLoad.swift in Sources */,
@@ -791,7 +791,7 @@
791791
CA34170824A4E89500FAF950 /* 01-GettingStarted-AnimationsTests.swift in Sources */,
792792
CA0C0C4724B89BEC00CBDD8A /* 04-HigherOrderReducers-LifecycleTests.swift in Sources */,
793793
DC07231724465D1E003A8B65 /* 02-Effects-TimersTests.swift in Sources */,
794-
CA50BE6024A8F46500FE7DBA /* 01-GettingStarted-AlertsAndActionSheetsTests.swift in Sources */,
794+
CA50BE6024A8F46500FE7DBA /* 01-GettingStarted-AlertsAndConfirmationDialogsTests.swift in Sources */,
795795
CAA9ADC424465AB00003A984 /* 02-Effects-BasicsTests.swift in Sources */,
796796
4F5AC11F24ECC7E4009DC50B /* 01-GettingStarted-SharedStateTests.swift in Sources */,
797797
CAA9ADCC2446615B0003A984 /* 02-Effects-LongLivingTests.swift in Sources */,

Examples/CaseStudies/SwiftUICaseStudies/00-Core.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import UIKit
44
import XCTestDynamicOverlay
55

66
struct RootState {
7-
var alertAndActionSheet = AlertAndSheetState()
7+
var alertAndConfirmationDialog = AlertAndConfirmationDialogState()
88
var animation = AnimationsState()
99
var bindingBasics = BindingBasicsState()
1010
#if compiler(>=5.4)
@@ -40,7 +40,7 @@ struct RootState {
4040
}
4141

4242
enum RootAction {
43-
case alertAndActionSheet(AlertAndSheetAction)
43+
case alertAndConfirmationDialog(AlertAndConfirmationDialogAction)
4444
case animation(AnimationsAction)
4545
case bindingBasics(BindingBasicsAction)
4646
#if compiler(>=5.4)
@@ -110,10 +110,10 @@ let rootReducer = Reducer<RootState, RootAction, RootEnvironment>.combine(
110110
return .none
111111
}
112112
},
113-
alertAndSheetReducer
113+
alertAndConfirmationDialogReducer
114114
.pullback(
115-
state: \.alertAndActionSheet,
116-
action: /RootAction.alertAndActionSheet,
115+
state: \.alertAndConfirmationDialog,
116+
action: /RootAction.alertAndConfirmationDialog,
117117
environment: { _ in .init() }
118118
),
119119
animationsReducer

Examples/CaseStudies/SwiftUICaseStudies/00-RootView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ struct RootView: View {
7474
)
7575

7676
NavigationLink(
77-
"Alerts and Action Sheets",
78-
destination: AlertAndSheetView(
77+
"Alerts and Confirmation Dialogs",
78+
destination: AlertAndConfirmationDialogView(
7979
store: self.store.scope(
80-
state: \.alertAndActionSheet,
81-
action: RootAction.alertAndActionSheet
80+
state: \.alertAndConfirmationDialog,
81+
action: RootAction.alertAndConfirmationDialog
8282
)
8383
)
8484
)

Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-AlertsAndActionSheets.swift renamed to Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-AlertsAndConfirmationDialogs.swift

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,50 @@ import ComposableArchitecture
22
import SwiftUI
33

44
private let readMe = """
5-
This demonstrates how to best handle alerts and action sheets in the Composable Architecture.
5+
This demonstrates how to best handle alerts and confirmation dialogs in the Composable \
6+
Architecture.
67
78
Because the library demands that all data flow through the application in a single direction, we \
89
cannot leverage SwiftUI's two-way bindings because they can make changes to state without going \
910
through a reducer. This means we can't directly use the standard API to display alerts and sheets.
1011
11-
However, the library comes with two types, `AlertState` and `ActionSheetState`, which can be \
12-
constructed from reducers and control whether or not an alert or action sheet is displayed. \
13-
Further, it automatically handles sending actions when you tap their buttons, which allows you \
14-
to properly handle their functionality in the reducer rather than in two-way bindings and action \
15-
closures.
12+
However, the library comes with two types, `AlertState` and `ConfirmationDialogState`, which can \
13+
be constructed from reducers and control whether or not an alert or confirmation dialog is \
14+
displayed. Further, it automatically handles sending actions when you tap their buttons, which \
15+
allows you to properly handle their functionality in the reducer rather than in two-way bindings \
16+
and action closures.
1617
1718
The benefit of doing this is that you can get full test coverage on how a user interacts with \
18-
alerts and action sheets in your application
19+
alerts and dialogs in your application
1920
"""
2021

21-
struct AlertAndSheetState: Equatable {
22-
var actionSheet: ActionSheetState<AlertAndSheetAction>?
23-
var alert: AlertState<AlertAndSheetAction>?
22+
struct AlertAndConfirmationDialogState: Equatable {
23+
var alert: AlertState<AlertAndConfirmationDialogAction>?
24+
var confirmationDialog: ConfirmationDialogState<AlertAndConfirmationDialogAction>?
2425
var count = 0
2526
}
2627

27-
enum AlertAndSheetAction: Equatable {
28-
case actionSheetButtonTapped
29-
case actionSheetDismissed
28+
enum AlertAndConfirmationDialogAction: Equatable {
3029
case alertButtonTapped
3130
case alertDismissed
31+
case confirmationDialogButtonTapped
32+
case confirmationDialogDismissed
3233
case decrementButtonTapped
3334
case incrementButtonTapped
3435
}
3536

36-
struct AlertAndSheetEnvironment {}
37+
struct AlertAndConfirmationDialogEnvironment {}
3738

38-
let alertAndSheetReducer = Reducer<
39-
AlertAndSheetState, AlertAndSheetAction, AlertAndSheetEnvironment
39+
let alertAndConfirmationDialogReducer = Reducer<
40+
AlertAndConfirmationDialogState, AlertAndConfirmationDialogAction, AlertAndConfirmationDialogEnvironment
4041
> { state, action, _ in
4142

4243
switch action {
43-
case .actionSheetButtonTapped:
44-
state.actionSheet = .init(
45-
title: .init("Action sheet"),
46-
message: .init("This is an action sheet."),
47-
buttons: [
48-
.cancel(),
49-
.default(.init("Increment"), action: .send(.incrementButtonTapped)),
50-
.default(.init("Decrement"), action: .send(.decrementButtonTapped)),
51-
]
52-
)
53-
return .none
54-
55-
case .actionSheetDismissed:
56-
state.actionSheet = nil
57-
return .none
58-
5944
case .alertButtonTapped:
6045
state.alert = .init(
6146
title: .init("Alert!"),
6247
message: .init("This is an alert"),
63-
primaryButton: .cancel(),
48+
primaryButton: .cancel(.init("Cancel")),
6449
secondaryButton: .default(.init("Increment"), action: .send(.incrementButtonTapped))
6550
)
6651
return .none
@@ -69,6 +54,22 @@ let alertAndSheetReducer = Reducer<
6954
state.alert = nil
7055
return .none
7156

57+
case .confirmationDialogButtonTapped:
58+
state.confirmationDialog = .init(
59+
title: .init("Confirmation dialog"),
60+
message: .init("This is a confirmation dialog."),
61+
buttons: [
62+
.cancel(.init("Cancel")),
63+
.default(.init("Increment"), action: .send(.incrementButtonTapped)),
64+
.default(.init("Decrement"), action: .send(.decrementButtonTapped)),
65+
]
66+
)
67+
return .none
68+
69+
case .confirmationDialogDismissed:
70+
state.confirmationDialog = nil
71+
return .none
72+
7273
case .decrementButtonTapped:
7374
state.alert = .init(title: .init("Decremented!"))
7475
state.count -= 1
@@ -82,8 +83,8 @@ let alertAndSheetReducer = Reducer<
8283
}
8384
.debug()
8485

85-
struct AlertAndSheetView: View {
86-
let store: Store<AlertAndSheetState, AlertAndSheetAction>
86+
struct AlertAndConfirmationDialogView: View {
87+
let store: Store<AlertAndConfirmationDialogState, AlertAndConfirmationDialogAction>
8788

8889
var body: some View {
8990
WithViewStore(self.store) { viewStore in
@@ -97,25 +98,25 @@ struct AlertAndSheetView: View {
9798
dismiss: .alertDismissed
9899
)
99100

100-
Button("Action sheet") { viewStore.send(.actionSheetButtonTapped) }
101-
.actionSheet(
102-
self.store.scope(state: \.actionSheet),
103-
dismiss: .actionSheetDismissed
101+
Button("Confirmation Dialog") { viewStore.send(.confirmationDialogButtonTapped) }
102+
.confirmationDialog(
103+
self.store.scope(state: \.confirmationDialog),
104+
dismiss: .confirmationDialogDismissed
104105
)
105106
}
106107
}
107108
}
108-
.navigationBarTitle("Alerts & Action Sheets")
109+
.navigationBarTitle("Alerts & Confirmation Dialogs")
109110
}
110111
}
111112

112-
struct AlertAndSheet_Previews: PreviewProvider {
113+
struct AlertAndConfirmationDialog_Previews: PreviewProvider {
113114
static var previews: some View {
114115
NavigationView {
115-
AlertAndSheetView(
116+
AlertAndConfirmationDialogView(
116117
store: .init(
117118
initialState: .init(),
118-
reducer: alertAndSheetReducer,
119+
reducer: alertAndConfirmationDialogReducer,
119120
environment: .init()
120121
)
121122
)

Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-Animations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ let animationsReducer = Reducer<AnimationsState, AnimationsAction, AnimationsEnv
8484
.init("Reset"),
8585
action: .send(.resetConfirmationButtonTapped, animation: .default)
8686
),
87-
secondaryButton: .cancel()
87+
secondaryButton: .cancel(.init("Cancel"))
8888
)
8989
return .none
9090

Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-AlertsAndActionSheetsTests.swift renamed to Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-AlertsAndConfirmationDialogsTests.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ import XCTest
55

66
@testable import SwiftUICaseStudies
77

8-
class AlertsAndActionSheetsTests: XCTestCase {
8+
class AlertsAndConfirmationDialogsTests: XCTestCase {
99
func testAlert() {
1010
let store = TestStore(
11-
initialState: AlertAndSheetState(),
12-
reducer: alertAndSheetReducer,
13-
environment: AlertAndSheetEnvironment()
11+
initialState: AlertAndConfirmationDialogState(),
12+
reducer: alertAndConfirmationDialogReducer,
13+
environment: AlertAndConfirmationDialogEnvironment()
1414
)
1515

1616
store.send(.alertButtonTapped) {
1717
$0.alert = .init(
1818
title: .init("Alert!"),
1919
message: .init("This is an alert"),
20-
primaryButton: .cancel(),
20+
primaryButton: .cancel(.init("Cancel")),
2121
secondaryButton: .default(.init("Increment"), action: .send(.incrementButtonTapped))
2222
)
2323
}
@@ -30,19 +30,19 @@ class AlertsAndActionSheetsTests: XCTestCase {
3030
}
3131
}
3232

33-
func testActionSheet() {
33+
func testConfirmationDialog() {
3434
let store = TestStore(
35-
initialState: AlertAndSheetState(),
36-
reducer: alertAndSheetReducer,
37-
environment: AlertAndSheetEnvironment()
35+
initialState: AlertAndConfirmationDialogState(),
36+
reducer: alertAndConfirmationDialogReducer,
37+
environment: AlertAndConfirmationDialogEnvironment()
3838
)
3939

40-
store.send(.actionSheetButtonTapped) {
41-
$0.actionSheet = .init(
42-
title: .init("Action sheet"),
43-
message: .init("This is an action sheet."),
40+
store.send(.confirmationDialogButtonTapped) {
41+
$0.confirmationDialog = .init(
42+
title: .init("Confirmation dialog"),
43+
message: .init("This is a confirmation dialog."),
4444
buttons: [
45-
.cancel(),
45+
.cancel(.init("Cancel")),
4646
.default(.init("Increment"), action: .send(.incrementButtonTapped)),
4747
.default(.init("Decrement"), action: .send(.decrementButtonTapped)),
4848
]
@@ -52,8 +52,8 @@ class AlertsAndActionSheetsTests: XCTestCase {
5252
$0.alert = .init(title: .init("Incremented!"))
5353
$0.count = 1
5454
}
55-
store.send(.actionSheetDismissed) {
56-
$0.actionSheet = nil
55+
store.send(.confirmationDialogDismissed) {
56+
$0.confirmationDialog = nil
5757
}
5858
}
5959
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import SwiftUI
2+
3+
@available(iOS 13, macOS 10.15, macCatalyst 13, tvOS 13, watchOS 6, *)
4+
extension Binding {
5+
func isPresent<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
6+
.init(
7+
get: { self.wrappedValue != nil },
8+
set: { isPresent, transaction in
9+
guard !isPresent else { return }
10+
self.transaction(transaction).wrappedValue = nil
11+
}
12+
)
13+
}
14+
}

0 commit comments

Comments
 (0)