SwiftUI中使用ObservableObject结合Binding导致Sheet崩溃问题
点击主视图按钮弹出第一个Sheet,关闭第一个Sheet后需弹出第二个Sheet,但使用ObservableObject配合Binding时,弹出第二个Sheet后视图崩溃。
相关代码
Root:
class Benef: ObservableObject { @Published var val: String = "" } @main struct TestApp: App { @StateObject var beenf = Benef() var body: some Scene { WindowGroup { ContentView(testt: $beenf.val) } } }
ContentView:
enum Test: Identifiable { case one case two var id: String { switch self { case .one : return "One" case .two : return "Two" } } } struct ContentView: View { @Environment(\.dismiss) private var dismiss @State private var showingFirst: Test? @State private var showingSecond: Test? @Binding var testt: String var body: some View { VStack { Button("Show First Sheet") { showingFirst = .one } } .sheet(item: $showingFirst) { tt in FirstSheetiew { showingSecond = .two } } .sheet(item: $showingSecond) { tt in VStack { Text("Second Sheet") Button("Dismiss") { dismiss() } } } } } struct FirstSheetiew: View { @Environment(\.dismiss) private var dismiss let action: () -> Void var body: some View { VStack { Text("First Sheet") Button("Dismiss") { action() dismiss() } } } }
崩溃错误信息
应用尝试以模态方式展示视图控制器 <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView>: 0x12a01b600>,但该视图控制器已被 <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x12a814e00> 展示。
解决方案
崩溃原因
在第一个Sheet的dismiss()执行前就设置了showingSecond = .two,此时第一个Sheet的 dismiss 流程尚未完成,主视图同时触发两个Sheet的展示操作,导致视图控制器层级冲突。
修复方法
方法一:利用Sheet的onDismiss回调(推荐)
将第二个Sheet的触发逻辑移到第一个Sheet的onDismiss回调中,确保第一个Sheet完全关闭后再展示第二个:
修改ContentView中第一个Sheet的代码:
.sheet(item: $showingFirst, onDismiss: { // 第一个Sheet关闭后自动弹出第二个 showingSecond = .two }) { tt in // 移除action参数,FirstSheet只需处理自身关闭 FirstSheetiew() }
同时简化FirstSheetiew:
struct FirstSheetiew: View { @Environment(\.dismiss) private var dismiss var body: some View { VStack { Text("First Sheet") Button("Dismiss") { dismiss() } } } }
方法二:延迟触发第二个Sheet
如果需要在第一个Sheet的按钮点击时执行额外逻辑,可通过DispatchQueue.main.async延迟触发第二个Sheet的展示,确保dismiss流程完成:
修改FirstSheetiew的按钮动作:
Button("Dismiss") { dismiss() // 延迟执行,等待dismiss流程结束 DispatchQueue.main.async { action() } }
额外优化
第二个Sheet中可单独声明自身的dismiss环境变量,避免依赖主视图的dismiss,逻辑更清晰:
.sheet(item: $showingSecond) { tt in VStack { Text("Second Sheet") Button("Dismiss") { @Environment(\.dismiss) var sheetDismiss sheetDismiss() } } }
内容的提问来源于stack exchange,提问作者OuSS

