Skip to content

Commit d539362

Browse files
stephencelismluisbrown
authored andcommitted
Fix iOS 14 alert/confirmationDialog runtime crash (#931)
* Fix iOS 14 alert/confirmationDialog runtime crash * wip * wip
1 parent b4e5ff5 commit d539362

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

Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift

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

266306
@available(iOS 13, *)

0 commit comments

Comments
 (0)