Skip to content

Commit 9c162f4

Browse files
authored
Fix iOS 14 alert/confirmationDialog runtime crash (#931)
* Fix iOS 14 alert/confirmationDialog runtime crash * wip * wip
1 parent fe97ad6 commit 9c162f4

File tree

2 files changed

+117
-44
lines changed

2 files changed

+117
-44
lines changed

Sources/ComposableArchitecture/SwiftUI/Alert.swift

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -208,30 +208,63 @@ extension View {
208208
/// - dismissal: An action to send when the alert is dismissed through non-user actions, such
209209
/// as when an alert is automatically dismissed by the system. Use this action to `nil` out
210210
/// the associated alert state.
211-
public func alert<Action>(
211+
@ViewBuilder public func alert<Action>(
212212
_ store: Store<AlertState<Action>?, Action>,
213213
dismiss: Action
214214
) -> some View {
215-
WithViewStore(store, removeDuplicates: { $0?.id == $1?.id }) { viewStore in
216-
#if compiler(>=5.5)
217-
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
218-
self.alert(
219-
(viewStore.state?.title).map { Text($0) } ?? Text(""),
220-
isPresented: viewStore.binding(send: dismiss).isPresent(),
221-
presenting: viewStore.state,
222-
actions: { $0.toSwiftUIActions(send: viewStore.send) },
223-
message: { $0.message.map { Text($0) } }
215+
#if compiler(>=5.5)
216+
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
217+
self.modifier(
218+
NewAlertModifier(
219+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
220+
dismiss: dismiss
224221
)
225-
} else {
226-
self.alert(item: viewStore.binding(send: dismiss)) { state in
227-
state.toSwiftUIAlert(send: viewStore.send)
228-
}
229-
}
230-
#else
231-
self.alert(item: viewStore.binding(send: dismiss)) { state in
232-
state.toSwiftUIAlert(send: viewStore.send)
233-
}
234-
#endif
222+
)
223+
} else {
224+
self.modifier(
225+
OldAlertModifier(
226+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
227+
dismiss: dismiss
228+
)
229+
)
230+
}
231+
#else
232+
self.modifier(
233+
OldAlertModifier(
234+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
235+
dismiss: dismiss
236+
)
237+
)
238+
#endif
239+
}
240+
}
241+
242+
#if compiler(>=5.5)
243+
// NB: Workaround for iOS 14 runtime crashes during iOS 15 availability checks.
244+
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
245+
private struct NewAlertModifier<Action>: ViewModifier {
246+
@ObservedObject var viewStore: ViewStore<AlertState<Action>?, Action>
247+
let dismiss: Action
248+
249+
func body(content: Content) -> some View {
250+
content.alert(
251+
(viewStore.state?.title).map { Text($0) } ?? Text(""),
252+
isPresented: viewStore.binding(send: dismiss).isPresent(),
253+
presenting: viewStore.state,
254+
actions: { $0.toSwiftUIActions(send: viewStore.send) },
255+
message: { $0.message.map { Text($0) } }
256+
)
257+
}
258+
}
259+
#endif
260+
261+
private struct OldAlertModifier<Action>: ViewModifier {
262+
@ObservedObject var viewStore: ViewStore<AlertState<Action>?, Action>
263+
let dismiss: Action
264+
265+
func body(content: Content) -> some View {
266+
content.alert(item: viewStore.binding(send: dismiss)) { state in
267+
state.toSwiftUIAlert(send: viewStore.send)
235268
}
236269
}
237270
}

Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -230,36 +230,76 @@ extension View {
230230
@available(macOS 12, *)
231231
@available(tvOS 13, *)
232232
@available(watchOS 6, *)
233-
public func confirmationDialog<Action>(
233+
@ViewBuilder public func confirmationDialog<Action>(
234234
_ store: Store<ConfirmationDialogState<Action>?, Action>,
235235
dismiss: Action
236236
) -> some View {
237-
238-
WithViewStore(store, removeDuplicates: { $0?.id == $1?.id }) { viewStore in
239-
#if compiler(>=5.5)
240-
if #available(iOS 15, tvOS 15, watchOS 8, *) {
241-
self.confirmationDialog(
242-
(viewStore.state?.title).map { Text($0) } ?? Text(""),
243-
isPresented: viewStore.binding(send: dismiss).isPresent(),
244-
titleVisibility: viewStore.state?.titleVisibility.toSwiftUI ?? .automatic,
245-
presenting: viewStore.state,
246-
actions: { $0.toSwiftUIActions(send: viewStore.send) },
247-
message: { $0.message.map { Text($0) } }
237+
#if compiler(>=5.5)
238+
if #available(iOS 15, tvOS 15, watchOS 8, *) {
239+
self.modifier(
240+
NewConfirmationDialogModifier(
241+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
242+
dismiss: dismiss
248243
)
249-
} else {
250-
#if !os(macOS)
251-
self.actionSheet(item: viewStore.binding(send: dismiss)) { state in
252-
state.toSwiftUIActionSheet(send: viewStore.send)
253-
}
254-
#endif
255-
}
256-
#elseif !os(macOS)
257-
self.actionSheet(item: viewStore.binding(send: dismiss)) { state in
258-
state.toSwiftUIActionSheet(send: viewStore.send)
259-
}
260-
#endif
244+
)
245+
} else {
246+
#if !os(macOS)
247+
self.modifier(
248+
OldConfirmationDialogModifier(
249+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
250+
dismiss: dismiss
251+
)
252+
)
253+
#endif
254+
}
255+
#elseif !os(macOS)
256+
self.modifier(
257+
OldConfirmationDialogModifier(
258+
viewStore: ViewStore(store, removeDuplicates: { $0?.id == $1?.id }),
259+
dismiss: dismiss
260+
)
261+
)
262+
#endif
263+
}
264+
}
265+
266+
#if compiler(>=5.5)
267+
// NB: Workaround for iOS 14 runtime crashes during iOS 15 availability checks.
268+
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
269+
private struct NewConfirmationDialogModifier<Action>: ViewModifier {
270+
@ObservedObject var viewStore: ViewStore<ConfirmationDialogState<Action>?, Action>
271+
let dismiss: Action
272+
273+
func body(content: Content) -> some View {
274+
content.confirmationDialog(
275+
(viewStore.state?.title).map { Text($0) } ?? Text(""),
276+
isPresented: viewStore.binding(send: dismiss).isPresent(),
277+
titleVisibility: viewStore.state?.titleVisibility.toSwiftUI ?? .automatic,
278+
presenting: viewStore.state,
279+
actions: { $0.toSwiftUIActions(send: viewStore.send) },
280+
message: { $0.message.map { Text($0) } }
281+
)
261282
}
262283
}
284+
#endif
285+
286+
@available(iOS 13, *)
287+
@available(macOS 12, *)
288+
@available(tvOS 13, *)
289+
@available(watchOS 6, *)
290+
private struct OldConfirmationDialogModifier<Action>: ViewModifier {
291+
@ObservedObject var viewStore: ViewStore<ConfirmationDialogState<Action>?, Action>
292+
let dismiss: Action
293+
294+
func body(content: Content) -> some View {
295+
#if !os(macOS)
296+
return content.actionSheet(item: viewStore.binding(send: dismiss)) { state in
297+
state.toSwiftUIActionSheet(send: viewStore.send)
298+
}
299+
#else
300+
return EmptyView()
301+
#endif
302+
}
263303
}
264304

265305
@available(iOS 13, *)

0 commit comments

Comments
 (0)