Skip to content

Commit f02fab5

Browse files
authored
Allow an alert to present another alert (#3309)
* Allow an alert to present another alert When we added support for vanilla SwiftUI modifiers, we lost the ability to present one alert after another because `nil` writes to the alert bindings unconditionally dismissed the feature, even if the feature was freshly presented. This fixes things by suppressing dismissal when the identity of a presented item has changed. Fix #3272. * wip
1 parent 181b9d2 commit f02fab5

File tree

14 files changed

+174
-21
lines changed

14 files changed

+174
-21
lines changed

Examples/Integration/Integration.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
CAF5802729A567BB0042FB62 /* LegacyPresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAF5802629A567BB0042FB62 /* LegacyPresentationTests.swift */; };
5656
DC140CC529E0BB2C006DF553 /* SwitchStoreTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC140CC429E0BB2C006DF553 /* SwitchStoreTestCase.swift */; };
5757
DC140CC729E0E8F3006DF553 /* SwitchStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC140CC629E0E8F3006DF553 /* SwitchStoreTests.swift */; };
58+
DC44CFC12C751C1E009F9FE4 /* MultipleAlertsTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */; };
5859
DC6268502AD1C85E00F2E2EF /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = DC62684F2AD1C85E00F2E2EF /* InlineSnapshotTesting */; };
5960
DC6268532AD1E06300F2E2EF /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = DC6268522AD1E06300F2E2EF /* InlineSnapshotTesting */; };
6061
DC6E2D942AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6E2D8D2AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift */; };
@@ -73,6 +74,7 @@
7374
DC6E2DAB2AD5C677005ACC26 /* ObservablePresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6E2DA42AD5C677005ACC26 /* ObservablePresentationTests.swift */; };
7475
DC808D6529E91FAA0072B4A9 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = DC808D6429E91FAA0072B4A9 /* ComposableArchitecture */; };
7576
DC92799B2A1E59D500B2031A /* PresentationItemTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC92799A2A1E59D500B2031A /* PresentationItemTestCase.swift */; };
77+
DCA6716B2C7CEC550086F359 /* MultipleAlertsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */; };
7678
DCFFB8E72A156488006AF839 /* BindingLocalTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFFB8E62A156488006AF839 /* BindingLocalTestCase.swift */; };
7779
DCFFB8E92A15792C006AF839 /* BindingLocalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFFB8E82A15792C006AF839 /* BindingLocalTests.swift */; };
7880
E9919D3E296E28C800C8716B /* EscapedWithViewStoreTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9919D3D296E28C800C8716B /* EscapedWithViewStoreTestCase.swift */; };
@@ -168,6 +170,7 @@
168170
CAF5802629A567BB0042FB62 /* LegacyPresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentationTests.swift; sourceTree = "<group>"; };
169171
DC140CC429E0BB2C006DF553 /* SwitchStoreTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchStoreTestCase.swift; sourceTree = "<group>"; };
170172
DC140CC629E0E8F3006DF553 /* SwitchStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchStoreTests.swift; sourceTree = "<group>"; };
173+
DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAlertsTestCase.swift; sourceTree = "<group>"; };
171174
DC6E2D8D2AD5C56F005ACC26 /* ObservableIdentifiedListTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableIdentifiedListTestCase.swift; sourceTree = "<group>"; };
172175
DC6E2D8E2AD5C56F005ACC26 /* ObservableBasicsTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableBasicsTestCase.swift; sourceTree = "<group>"; };
173176
DC6E2D8F2AD5C56F005ACC26 /* ObservableNavigationTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableNavigationTestCase.swift; sourceTree = "<group>"; };
@@ -183,6 +186,7 @@
183186
DC6E2DA32AD5C677005ACC26 /* ObservableBasicsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableBasicsTests.swift; sourceTree = "<group>"; };
184187
DC6E2DA42AD5C677005ACC26 /* ObservablePresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservablePresentationTests.swift; sourceTree = "<group>"; };
185188
DC92799A2A1E59D500B2031A /* PresentationItemTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationItemTestCase.swift; sourceTree = "<group>"; };
189+
DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleAlertsTests.swift; sourceTree = "<group>"; };
186190
DCFFB8E62A156488006AF839 /* BindingLocalTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingLocalTestCase.swift; sourceTree = "<group>"; };
187191
DCFFB8E82A15792C006AF839 /* BindingLocalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingLocalTests.swift; sourceTree = "<group>"; };
188192
E9919D3D296E28C800C8716B /* EscapedWithViewStoreTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EscapedWithViewStoreTestCase.swift; sourceTree = "<group>"; };
@@ -293,6 +297,7 @@
293297
DC6E2D8C2AD5C525005ACC26 /* iOS 17 */,
294298
CA8B2E932AC57518008272E0 /* Legacy */,
295299
CAA1CAFA296DEE79000665B1 /* Preview Content */,
300+
DC44CFC02C751BEA009F9FE4 /* Test Cases */,
296301
);
297302
path = Integration;
298303
sourceTree = "<group>";
@@ -313,6 +318,7 @@
313318
CAA6BEAC2ADADE4300FF83BC /* iOS 16+17 */,
314319
DC6E2D9D2AD5C64C005ACC26 /* iOS 17 */,
315320
CA8B2E9D2AC576CE008272E0 /* Legacy */,
321+
DCA671692C7CEC380086F359 /* Test Cases */,
316322
);
317323
path = IntegrationUITests;
318324
sourceTree = "<group>";
@@ -356,6 +362,14 @@
356362
path = TestCases;
357363
sourceTree = "<group>";
358364
};
365+
DC44CFC02C751BEA009F9FE4 /* Test Cases */ = {
366+
isa = PBXGroup;
367+
children = (
368+
DC44CFBF2C751BDA009F9FE4 /* MultipleAlertsTestCase.swift */,
369+
);
370+
path = "Test Cases";
371+
sourceTree = "<group>";
372+
};
359373
DC6E2D8B2AD5C512005ACC26 /* iOS 16 */ = {
360374
isa = PBXGroup;
361375
children = (
@@ -416,6 +430,14 @@
416430
path = "iOS 17";
417431
sourceTree = "<group>";
418432
};
433+
DCA671692C7CEC380086F359 /* Test Cases */ = {
434+
isa = PBXGroup;
435+
children = (
436+
DCA6716A2C7CEC4D0086F359 /* MultipleAlertsTests.swift */,
437+
);
438+
path = "Test Cases";
439+
sourceTree = "<group>";
440+
};
419441
/* End PBXGroup section */
420442

421443
/* Begin PBXHeadersBuildPhase section */
@@ -582,6 +604,7 @@
582604
CA4BA5E929E76A7F0004FF9D /* NavigationStackTestCase.swift in Sources */,
583605
E9919D42296E47A400C8716B /* BindingsAnimationsTestBench.swift in Sources */,
584606
CAE2E9232B23417000EE370B /* IfLetStoreTestCase.swift in Sources */,
607+
DC44CFC12C751C1E009F9FE4 /* MultipleAlertsTestCase.swift in Sources */,
585608
DCFFB8E72A156488006AF839 /* BindingLocalTestCase.swift in Sources */,
586609
DC6E2D992AD5C56F005ACC26 /* ObservableOptionalTestCase.swift in Sources */,
587610
CA7BDDA12ADB543400277984 /* NewContainsOldTestCase.swift in Sources */,
@@ -623,6 +646,7 @@
623646
CA8B2E9B2AC576CA008272E0 /* EnumTests.swift in Sources */,
624647
DC6E2DA62AD5C677005ACC26 /* ObservableIdentifiedListTests.swift in Sources */,
625648
CAF5802729A567BB0042FB62 /* LegacyPresentationTests.swift in Sources */,
649+
DCA6716B2C7CEC550086F359 /* MultipleAlertsTests.swift in Sources */,
626650
CA487B2C2A15185300F54A79 /* BaseIntegrationTests.swift in Sources */,
627651
CA8B2EA72AC584BE008272E0 /* SiblingTests.swift in Sources */,
628652
DC6E2DA72AD5C677005ACC26 /* ObservableNavigationTests.swift in Sources */,

Examples/Integration/Integration/IntegrationApp.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ final class IntegrationAppDelegate: NSObject, UIApplicationDelegate {
5656
configurationForConnecting connectingSceneSession: UISceneSession,
5757
options: UIScene.ConnectionOptions
5858
) -> UISceneConfiguration {
59-
UIView.setAnimationsEnabled(false)
59+
if ProcessInfo.processInfo.environment["UI_TEST"] != nil {
60+
UIView.setAnimationsEnabled(false)
61+
}
6062
Logger.shared.isEnabled = true
6163
IssueReporters.current.append(NotificationReporter())
6264
let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
@@ -185,9 +187,23 @@ struct ContentView: View {
185187
.navigationTitle(Text("iOS 16"))
186188
}
187189

190+
NavigationLink("Test cases") {
191+
List {
192+
ForEach(TestCase.Cases.allCases) { test in
193+
switch test {
194+
case .multipleAlerts:
195+
NavigationLink(test.rawValue) {
196+
MultipleAlertsTestCaseView()
197+
}
198+
}
199+
}
200+
}
201+
.navigationTitle(Text("Test cases"))
202+
}
203+
188204
NavigationLink("Legacy") {
189205
List {
190-
ForEach(TestCase.allCases) { test in
206+
ForEach(TestCase.Legacy.allCases) { test in
191207
switch test {
192208
case .escapedWithViewStore:
193209
NavigationLink(test.rawValue) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import ComposableArchitecture
2+
import SwiftUI
3+
4+
@Reducer
5+
private struct MultipleAlertsTestCase {
6+
@ObservableState
7+
struct State: Equatable {
8+
@Presents var alert: AlertState<Action.Alert>?
9+
}
10+
enum Action {
11+
case alert(PresentationAction<Alert>)
12+
case showAlertButtonTapped
13+
14+
@CasePathable
15+
enum Alert {
16+
case anotherButtonTapped
17+
}
18+
}
19+
var body: some ReducerOf<Self> {
20+
Reduce { state, action in
21+
switch action {
22+
case .alert(.presented(.anotherButtonTapped)):
23+
if let title = state.alert?.title {
24+
state.alert = AlertState {
25+
title + TextState("!")
26+
} actions: {
27+
ButtonState(action: .anotherButtonTapped) {
28+
TextState("Another!")
29+
}
30+
ButtonState(role: .cancel) {
31+
TextState("I'm done")
32+
}
33+
}
34+
}
35+
return .none
36+
37+
case .alert:
38+
return .none
39+
40+
case .showAlertButtonTapped:
41+
state.alert = AlertState {
42+
TextState("Hello")
43+
} actions: {
44+
ButtonState(action: .anotherButtonTapped) {
45+
TextState("Another!")
46+
}
47+
ButtonState(role: .cancel) {
48+
TextState("I'm done")
49+
}
50+
}
51+
return .none
52+
}
53+
}
54+
.ifLet(\.$alert, action: \.alert)
55+
._printChanges()
56+
}
57+
}
58+
59+
struct MultipleAlertsTestCaseView: View {
60+
@Perception.Bindable private var store = Store(initialState: MultipleAlertsTestCase.State()) {
61+
MultipleAlertsTestCase()
62+
}
63+
64+
var body: some View {
65+
WithPerceptionTracking {
66+
VStack {
67+
Button("Show alert") {
68+
store.send(.showAlertButtonTapped)
69+
}
70+
}
71+
.alert($store.scope(state: \.alert, action: \.alert))
72+
}
73+
}
74+
}

Examples/Integration/IntegrationUITests/Legacy/BindingLocalTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class BindingLocalTests: BaseIntegrationTests {
77
try XCTSkipIf(ProcessInfo.processInfo.environment["CI"] != nil)
88
try super.setUpWithError()
99
self.app.buttons["Legacy"].tap()
10-
app.collectionViews.buttons[TestCase.bindingLocal.rawValue].tap()
10+
app.collectionViews.buttons[TestCase.Legacy.bindingLocal.rawValue].tap()
1111
}
1212

1313
@MainActor

Examples/Integration/IntegrationUITests/Legacy/EscapedWithViewStoreTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class EscapedWithViewStoreTests: BaseIntegrationTests {
77
override func setUpWithError() throws {
88
try super.setUpWithError()
99
self.app.buttons["Legacy"].tap()
10-
app.collectionViews.buttons[TestCase.escapedWithViewStore.rawValue].tap()
10+
app.collectionViews.buttons[TestCase.Legacy.escapedWithViewStore.rawValue].tap()
1111
}
1212

1313
@MainActor

Examples/Integration/IntegrationUITests/Legacy/ForEachBindingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ final class ForEachBindingTests: BaseIntegrationTests {
66
override func setUpWithError() throws {
77
try super.setUpWithError()
88
self.app.buttons["Legacy"].tap()
9-
app.collectionViews.buttons[TestCase.forEachBinding.rawValue].tap()
9+
app.collectionViews.buttons[TestCase.Legacy.forEachBinding.rawValue].tap()
1010
}
1111

1212
@MainActor

Examples/Integration/IntegrationUITests/Legacy/IfLetStoreTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ final class IfLetStoreTests: BaseIntegrationTests {
66
override func setUpWithError() throws {
77
try super.setUpWithError()
88
self.app.buttons["Legacy"].tap()
9-
self.app.buttons[TestCase.ifLetStore.rawValue].tap()
9+
self.app.buttons[TestCase.Legacy.ifLetStore.rawValue].tap()
1010
}
1111

1212
@MainActor

Examples/Integration/IntegrationUITests/Legacy/LegacyNavigationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class LegacyNavigationTests: BaseIntegrationTests {
77
override func setUpWithError() throws {
88
try super.setUpWithError()
99
self.app.buttons["Legacy"].tap()
10-
self.app.buttons[TestCase.navigationStack.rawValue].tap()
10+
self.app.buttons[TestCase.Legacy.navigationStack.rawValue].tap()
1111
}
1212

1313
@MainActor

Examples/Integration/IntegrationUITests/Legacy/LegacyPresentationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class LegacyPresentationTests: BaseIntegrationTests {
77
override func setUpWithError() throws {
88
try super.setUpWithError()
99
self.app.buttons["Legacy"].tap()
10-
self.app.buttons[TestCase.presentation.rawValue].tap()
10+
self.app.buttons[TestCase.Legacy.presentation.rawValue].tap()
1111
}
1212

1313
@MainActor

Examples/Integration/IntegrationUITests/Legacy/SwitchStoreTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ final class SwitchStoreTests: BaseIntegrationTests {
77
override func setUpWithError() throws {
88
try super.setUpWithError()
99
app.buttons["Legacy"].tap()
10-
app.collectionViews.buttons[TestCase.switchStore.rawValue].tap()
10+
app.collectionViews.buttons[TestCase.Legacy.switchStore.rawValue].tap()
1111
}
1212

1313
@MainActor

0 commit comments

Comments
 (0)