SwiftUI视图间数组传递异常:非空数组被误判为nil
问题分析与解决方案
核心问题
你的代码存在两个关键问题导致数组无法正确传递:
- 状态更新的竞态条件:在设置
contentSelectedSet和hasSelectedSet后立即触发isQuizViewShowing = true,但SwiftUI的状态更新是异步批量处理的,此时全屏封面的内容已经被计算,导致contentSelectedSet仍为nil。 - 错误的
@State使用:DebugQuizView中selectedSet被标记为@State,但它是从父视图传入的外部数据,@State仅用于视图内部管理的状态,会导致数据无法正确同步。
修复步骤
1. 让Question结构体遵循Identifiable协议
这是使用fullScreenCover(item:)修饰器的前提,确保我们可以直接通过数组的非空状态触发弹窗:
struct Question: Identifiable { let id = UUID() // 添加唯一标识 let text: String let options: [String] let correctAnswer: String let imageName: UIImage? let originalOptions: [String] }
2. 重构ContentView
移除冗余的isQuizViewShowing和hasSelectedSet,直接用contentSelectedSet的非空状态控制弹窗:
struct ContentView: View { @State var contentSelectedSet: [Question]? var body: some View { TabView { MenuView(onQuizButtonTapped: { menuSelectedSet in contentSelectedSet = menuSelectedSet print("The content set is \(String(describing: contentSelectedSet))") print("The selected set is \(menuSelectedSet)") }) .tabItem { Image(systemName: "book.fill") Text("Learn") } PracticeView() .tabItem { Image(systemName: "chart.bar.fill") Text("Practice") } SettingsView() .tabItem { Image(systemName: "gearshape.fill") Text("Settings") } } // 直接通过contentSelectedSet的非空状态触发弹窗 .fullScreenCover(item: $contentSelectedSet) { selectedSet in DebugQuizView( isPresented: Binding( get: { contentSelectedSet != nil }, set: { if !$0 { contentSelectedSet = nil } } ), selectedSet: selectedSet ) } } }
3. 修正DebugQuizView的状态声明
将@State var selectedSet改为常量let,并添加数组越界安全检查:
struct DebugQuizView: View { @Binding var isPresented: Bool let selectedSet: [Question] // 改为常量,接收父视图传入的数据 var body: some View { VStack { // 安全访问数组元素,避免崩溃 if selectedSet.count > 1 { Text(selectedSet[1].text) .foregroundColor(Color.green) } else { Text("题目数量不足") } // 添加关闭按钮 Button("关闭") { isPresented = false } .padding() } } }
替代方案(不修改Question协议)
如果不想让Question遵循Identifiable,可以使用异步延迟触发弹窗来避免竞态条件:
// 在MenuView的回调中修改: MenuView(onQuizButtonTapped: { menuSelectedSet in contentSelectedSet = menuSelectedSet print("The content set is \(String(describing: contentSelectedSet))") print("The selected set is \(menuSelectedSet)") // 延迟触发,等待状态更新完成 DispatchQueue.main.async { isQuizViewShowing = true } })
内容的提问来源于stack exchange,提问作者Flynn
相关产品推荐
相关产品推荐

