You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI视图间数组传递异常:非空数组被误判为nil

问题分析与解决方案

核心问题

你的代码存在两个关键问题导致数组无法正确传递:

  1. 状态更新的竞态条件:在设置contentSelectedSet和hasSelectedSet后立即触发isQuizViewShowing = true,但SwiftUI的状态更新是异步批量处理的,此时全屏封面的内容已经被计算,导致contentSelectedSet仍为nil。
  2. 错误的@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:07:00