Skip to content

Commit 4449bc2

Browse files
authored
Add TextState (for AlertState, ActionSheetState, etc.) (#359)
* Use SwiftUI.Text with {Alert,ActionSheet}State Fix #293, #318. * Use public interface * Availability * TextState * Note * Simplify * Update LocalizedStringTests.swift * Fix warnings * Fix docs
1 parent 6773c2e commit 4449bc2

27 files changed

+509
-173
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ let alertAndSheetReducer = Reducer<
4444
switch action {
4545
case .actionSheetButtonTapped:
4646
state.actionSheet = .init(
47-
title: "Action sheet",
48-
message: "This is an action sheet.",
47+
title: .init("Action sheet"),
48+
message: .init("This is an action sheet."),
4949
buttons: [
5050
.cancel(),
51-
.default("Increment", send: .incrementButtonTapped),
52-
.default("Decrement", send: .decrementButtonTapped),
51+
.default(.init("Increment"), send: .incrementButtonTapped),
52+
.default(.init("Decrement"), send: .decrementButtonTapped),
5353
]
5454
)
5555
return .none
@@ -63,10 +63,10 @@ let alertAndSheetReducer = Reducer<
6363

6464
case .alertButtonTapped:
6565
state.alert = .init(
66-
title: "Alert!",
67-
message: "This is an alert",
66+
title: .init("Alert!"),
67+
message: .init("This is an alert"),
6868
primaryButton: .cancel(),
69-
secondaryButton: .default("Increment", send: .incrementButtonTapped)
69+
secondaryButton: .default(.init("Increment"), send: .incrementButtonTapped)
7070
)
7171
return .none
7272

@@ -78,12 +78,12 @@ let alertAndSheetReducer = Reducer<
7878
return .none
7979

8080
case .decrementButtonTapped:
81-
state.alert = .init(title: "Decremented!")
81+
state.alert = .init(title: .init("Decremented!"))
8282
state.count -= 1
8383
return .none
8484

8585
case .incrementButtonTapped:
86-
state.alert = .init(title: "Incremented!")
86+
state.alert = .init(title: .init("Incremented!"))
8787
state.count += 1
8888
return .none
8989
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ let sharedStateCounterReducer = Reducer<
107107
case .isPrimeButtonTapped:
108108
state.alert = .init(
109109
title: isPrime(state.count)
110-
? "👍 The number \(state.count) is prime!"
111-
: "👎 The number \(state.count) is not prime :("
110+
? .init("👍 The number \(state.count) is prime!")
111+
: .init("👎 The number \(state.count) is not prime :(")
112112
)
113113
return .none
114114
}

Examples/CaseStudies/SwiftUICaseStudies/02-Effects-WebSocket.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ let webSocketReducer = Reducer<WebSocketState, WebSocketAction, WebSocketEnviron
109109

110110
case let .sendResponse(error):
111111
if error != nil {
112-
state.alert = .init(title: "Could not send socket message. Try again.")
112+
state.alert = .init(title: .init("Could not send socket message. Try again."))
113113
}
114114
return .none
115115

@@ -121,7 +121,7 @@ let webSocketReducer = Reducer<WebSocketState, WebSocketAction, WebSocketEnviron
121121
let .webSocket(.didCompleteWithError(error)):
122122
state.connectivityState = .disconnected
123123
if error != nil {
124-
state.alert = .init(title: "Disconnected from socket for some reason. Try again.")
124+
state.alert = .init(title: .init("Disconnected from socket for some reason. Try again."))
125125
}
126126
return .cancel(id: WebSocketId())
127127

Examples/CaseStudies/SwiftUICaseStudies/03-Effects-SystemEnvironment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ let multipleDependenciesReducer = Reducer<
5050
.eraseToEffect()
5151

5252
case .alertDelayReceived:
53-
state.alert = .init(title: "Here's an alert after a delay!")
53+
state.alert = .init(title: .init("Here's an alert after a delay!"))
5454
return .none
5555

5656
case .alertDismissed:

Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads/DownloadComponent.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,19 @@ extension Reducer {
118118
}
119119

120120
private let deleteAlert = AlertState(
121-
title: "Do you want to delete this map from your offline storage?",
122-
primaryButton: .destructive("Delete", send: .deleteButtonTapped),
121+
title: .init("Do you want to delete this map from your offline storage?"),
122+
primaryButton: .destructive(.init("Delete"), send: .deleteButtonTapped),
123123
secondaryButton: nevermindButton
124124
)
125125

126126
private let cancelAlert = AlertState(
127-
title: "Do you want to cancel downloading this map?",
128-
primaryButton: .destructive("Cancel", send: .cancelButtonTapped),
127+
title: .init("Do you want to cancel downloading this map?"),
128+
primaryButton: .destructive(.init("Cancel"), send: .cancelButtonTapped),
129129
secondaryButton: nevermindButton
130130
)
131131

132132
let nevermindButton = AlertState<DownloadComponentAction.AlertAction>.Button
133-
.default("Nevermind", send: .nevermindButtonTapped)
133+
.default(.init("Nevermind"), send: .nevermindButtonTapped)
134134

135135
struct DownloadComponent<ID: Equatable>: View {
136136
let store: Store<DownloadComponentState<ID>, DownloadComponentAction>

Examples/CaseStudies/SwiftUICaseStudies/04-HigherOrderReducers-ReusableFavoriting.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ extension Reducer {
7878
.cancellable(id: FavoriteCancelId(id: state.id), cancelInFlight: true)
7979

8080
case let .response(.failure(error)):
81-
state.alert = .init(title: .init(error.localizedDescription))
81+
state.alert = .init(title: TextState(error.localizedDescription))
8282
return .none
8383

8484
case let .response(.success(isFavorite)):

Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-AlertsAndActionSheetsTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ class AlertsAndActionSheetsTests: XCTestCase {
1616
store.assert(
1717
.send(.alertButtonTapped) {
1818
$0.alert = .init(
19-
title: "Alert!",
20-
message: "This is an alert",
19+
title: .init("Alert!"),
20+
message: .init("This is an alert"),
2121
primaryButton: .cancel(),
22-
secondaryButton: .default("Increment", send: .incrementButtonTapped)
22+
secondaryButton: .default(.init("Increment"), send: .incrementButtonTapped)
2323
)
2424
},
2525
.send(.incrementButtonTapped) {
26-
$0.alert = .init(title: "Incremented!")
26+
$0.alert = .init(title: .init("Incremented!"))
2727
$0.count = 1
2828
},
2929
.send(.alertDismissed) {
@@ -42,17 +42,17 @@ class AlertsAndActionSheetsTests: XCTestCase {
4242
store.assert(
4343
.send(.actionSheetButtonTapped) {
4444
$0.actionSheet = .init(
45-
title: "Action sheet",
46-
message: "This is an action sheet.",
45+
title: .init("Action sheet"),
46+
message: .init("This is an action sheet."),
4747
buttons: [
4848
.cancel(),
49-
.default("Increment", send: .incrementButtonTapped),
50-
.default("Decrement", send: .decrementButtonTapped),
49+
.default(.init("Increment"), send: .incrementButtonTapped),
50+
.default(.init("Decrement"), send: .decrementButtonTapped),
5151
]
5252
)
5353
},
5454
.send(.incrementButtonTapped) {
55-
$0.alert = .init(title: "Incremented!")
55+
$0.alert = .init(title: .init("Incremented!"))
5656
$0.count = 1
5757
},
5858
.send(.actionSheetDismissed) {

Examples/CaseStudies/SwiftUICaseStudiesTests/01-GettingStarted-SharedStateTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class SharedStateTests: XCTestCase {
8080
store.assert(
8181
.send(.isPrimeButtonTapped) {
8282
$0.alert = .init(
83-
title: "👍 The number \($0.count) is prime!"
83+
title: .init("👍 The number \($0.count) is prime!")
8484
)
8585
},
8686
.send(.alertDismissed) {
@@ -100,7 +100,7 @@ class SharedStateTests: XCTestCase {
100100
store.assert(
101101
.send(.isPrimeButtonTapped) {
102102
$0.alert = .init(
103-
title: "👎 The number \($0.count) is not prime :("
103+
title: .init("👎 The number \($0.count) is not prime :(")
104104
)
105105
},
106106
.send(.alertDismissed) {

Examples/CaseStudies/SwiftUICaseStudiesTests/02-Effects-WebSocketTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class WebSocketTests: XCTestCase {
9696
$0.messageToSend = ""
9797
},
9898
.receive(.sendResponse(NSError(domain: "", code: 1))) {
99-
$0.alert = .init(title: "Could not send socket message. Try again.")
99+
$0.alert = .init(title: .init("Could not send socket message. Try again."))
100100
},
101101

102102
// Disconnect from the socket

Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-ReusableFavoritingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ReusableComponentsFavoritingTests: XCTestCase {
6666
.episode(index: 2, action: .favorite(.response(.failure(FavoriteError(error: error)))))
6767
) {
6868
$0.episodes[2].alert = .init(
69-
title: "The operation couldn’t be completed. (co.pointfree error -1.)"
69+
title: .init("The operation couldn’t be completed. (co.pointfree error -1.)")
7070
)
7171
},
7272

Examples/CaseStudies/SwiftUICaseStudiesTests/04-HigherOrderReducers-ReusableOfflineDownloadsTests.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ class ReusableComponentsDownloadComponentTests: XCTestCase {
118118

119119
.send(.buttonTapped) {
120120
$0.alert = .init(
121-
title: "Do you want to cancel downloading this map?",
122-
primaryButton: .destructive("Cancel", send: .cancelButtonTapped),
123-
secondaryButton: .default("Nevermind", send: .nevermindButtonTapped)
121+
title: .init("Do you want to cancel downloading this map?"),
122+
primaryButton: .destructive(.init("Cancel"), send: .cancelButtonTapped),
123+
secondaryButton: .default(.init("Nevermind"), send: .nevermindButtonTapped)
124124
)
125125
},
126126

@@ -157,9 +157,9 @@ class ReusableComponentsDownloadComponentTests: XCTestCase {
157157

158158
.send(.buttonTapped) {
159159
$0.alert = .init(
160-
title: "Do you want to cancel downloading this map?",
161-
primaryButton: .destructive("Cancel", send: .cancelButtonTapped),
162-
secondaryButton: .default("Nevermind", send: .nevermindButtonTapped)
160+
title: .init("Do you want to cancel downloading this map?"),
161+
primaryButton: .destructive(.init("Cancel"), send: .cancelButtonTapped),
162+
secondaryButton: .default(.init("Nevermind"), send: .nevermindButtonTapped)
163163
)
164164
},
165165

@@ -193,9 +193,9 @@ class ReusableComponentsDownloadComponentTests: XCTestCase {
193193
store.assert(
194194
.send(.buttonTapped) {
195195
$0.alert = .init(
196-
title: "Do you want to delete this map from your offline storage?",
197-
primaryButton: .destructive("Delete", send: .deleteButtonTapped),
198-
secondaryButton: .default("Nevermind", send: .nevermindButtonTapped)
196+
title: .init("Do you want to delete this map from your offline storage?"),
197+
primaryButton: .destructive(.init("Delete"), send: .deleteButtonTapped),
198+
secondaryButton: .default(.init("Nevermind"), send: .nevermindButtonTapped)
199199
)
200200
},
201201

Examples/SpeechRecognition/SpeechRecognition/SpeechRecognition.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, e
3838

3939
case .speech(.failure(.couldntConfigureAudioSession)),
4040
.speech(.failure(.couldntStartAudioEngine)):
41-
state.alert = .init(title: "Problem with audio device. Please try again.")
41+
state.alert = .init(title: .init("Problem with audio device. Please try again."))
4242
return .none
4343

4444
case .recordButtonTapped:
@@ -66,7 +66,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, e
6666
}
6767

6868
case let .speech(.failure(error)):
69-
state.alert = .init(title: "An error occured while transcribing. Please try again.")
69+
state.alert = .init(title: .init("An error occured while transcribing. Please try again."))
7070
return environment.speechClient.finishTask(SpeechRecognitionId())
7171
.fireAndForget()
7272

@@ -76,18 +76,21 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, e
7676

7777
switch status {
7878
case .notDetermined:
79-
state.alert = .init(title: "Try again.")
79+
state.alert = .init(title: .init("Try again."))
8080
return .none
8181

8282
case .denied:
8383
state.alert = .init(
84-
title: """
84+
title: .init(
85+
"""
8586
You denied access to speech recognition. This app needs access to transcribe your speech.
86-
""")
87+
"""
88+
)
89+
)
8790
return .none
8891

8992
case .restricted:
90-
state.alert = .init(title: "Your device does not allow speech recognition.")
93+
state.alert = .init(title: .init("Your device does not allow speech recognition."))
9194
return .none
9295

9396
case .authorized:

Examples/SpeechRecognition/SpeechRecognitionTests/SpeechRecognitionTests.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ class SpeechRecognitionTests: XCTestCase {
2727
.do { self.scheduler.advance() },
2828
.receive(.speechRecognizerAuthorizationStatusResponse(.denied)) {
2929
$0.alert = .init(
30-
title: """
30+
title: .init(
31+
"""
3132
You denied access to speech recognition. This app needs access to transcribe your speech.
3233
"""
34+
)
3335
)
3436
$0.isRecording = false
3537
$0.speechRecognizerAuthorizationStatus = .denied
@@ -55,7 +57,7 @@ class SpeechRecognitionTests: XCTestCase {
5557
},
5658
.do { self.scheduler.advance() },
5759
.receive(.speechRecognizerAuthorizationStatusResponse(.restricted)) {
58-
$0.alert = .init(title: "Your device does not allow speech recognition.")
60+
$0.alert = .init(title: .init("Your device does not allow speech recognition."))
5961
$0.isRecording = false
6062
$0.speechRecognizerAuthorizationStatus = .restricted
6163
}
@@ -139,7 +141,7 @@ class SpeechRecognitionTests: XCTestCase {
139141

140142
.do { self.recognitionTaskSubject.send(completion: .failure(.couldntConfigureAudioSession)) },
141143
.receive(.speech(.failure(.couldntConfigureAudioSession))) {
142-
$0.alert = .init(title: "Problem with audio device. Please try again.")
144+
$0.alert = .init(title: .init("Problem with audio device. Please try again."))
143145
},
144146

145147
.do { self.recognitionTaskSubject.send(completion: .finished) }
@@ -171,7 +173,7 @@ class SpeechRecognitionTests: XCTestCase {
171173

172174
.do { self.recognitionTaskSubject.send(completion: .failure(.couldntStartAudioEngine)) },
173175
.receive(.speech(.failure(.couldntStartAudioEngine))) {
174-
$0.alert = .init(title: "Problem with audio device. Please try again.")
176+
$0.alert = .init(title: .init("Problem with audio device. Please try again."))
175177
},
176178

177179
.do { self.recognitionTaskSubject.send(completion: .finished) }

Examples/TicTacToe/Sources/Core/LoginCore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public let loginReducer =
7272
return .none
7373

7474
case let .loginResponse(.failure(error)):
75-
state.alert = .init(title: .init(error.localizedDescription))
75+
state.alert = .init(title: TextState(error.localizedDescription))
7676
state.isLoginRequestInFlight = false
7777
return .none
7878

Examples/TicTacToe/Sources/Core/TwoFactorCore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public let twoFactorReducer = Reducer<TwoFactorState, TwoFactorAction, TwoFactor
6363
.cancellable(id: TwoFactorTearDownToken())
6464

6565
case let .twoFactorResponse(.failure(error)):
66-
state.alert = .init(title: .init(error.localizedDescription))
66+
state.alert = .init(title: TextState(error.localizedDescription))
6767
state.isTwoFactorRequestInFlight = false
6868
return .none
6969

Examples/TicTacToe/Sources/Views-UIKit/LoginViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class LoginViewController: UIViewController {
137137
guard let alert = alert else { return }
138138

139139
let alertController = UIAlertController(
140-
title: alert.title.formatted(), message: nil, preferredStyle: .alert)
140+
title: String(state: alert.title), message: nil, preferredStyle: .alert)
141141
alertController.addAction(
142142
UIAlertAction(
143143
title: "Ok", style: .default,

Examples/TicTacToe/Sources/Views-UIKit/TwoFactorViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public final class TwoFactorViewController: UIViewController {
9393
guard let alert = alert else { return }
9494

9595
let alertController = UIAlertController(
96-
title: alert.title.formatted(), message: nil, preferredStyle: .alert)
96+
title: String(state: alert.title), message: nil, preferredStyle: .alert)
9797
alertController.addAction(
9898
UIAlertAction(
9999
title: "Ok", style: .default,

Examples/TicTacToe/Tests/LoginSwiftUITests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ class LoginSwiftUITests: XCTestCase {
119119
self.scheduler.advance()
120120
},
121121
.receive(.loginResponse(.failure(.invalidUserPassword))) {
122-
$0.alert = .init(title: .init(AuthenticationError.invalidUserPassword.localizedDescription))
122+
$0.alert = .init(
123+
title: TextState(AuthenticationError.invalidUserPassword.localizedDescription)
124+
)
123125
$0.isActivityIndicatorVisible = false
124126
$0.isFormDisabled = false
125127
},

Examples/TicTacToe/Tests/TwoFactorSwiftUITests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ class TwoFactorSwiftUITests: XCTestCase {
8888
self.scheduler.advance()
8989
},
9090
.receive(.twoFactorResponse(.failure(.invalidTwoFactor))) {
91-
$0.alert = .init(title: .init(AuthenticationError.invalidTwoFactor.localizedDescription))
91+
$0.alert = .init(
92+
title: TextState(AuthenticationError.invalidTwoFactor.localizedDescription)
93+
)
9294
$0.isActivityIndicatorVisible = false
9395
$0.isFormDisabled = false
9496
},

0 commit comments

Comments
 (0)