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

iOS 16下SwiftUI的onAppear()失效导致崩溃问题咨询

iOS 16 下 SwiftUI onAppear 失效导致数组越界崩溃的解决方案

问题核心

升级iOS16后,onAppear执行时机晚于视图渲染,导致selectedParcoursIndexArray还没完成填充就被Picker的Binding访问,直接触发数组越界崩溃。另外你当前的数组填充逻辑也存在问题——初始值设为[0],后续用append会导致数组长度比groupeManager.groupeList.count多1,逻辑上不匹配。

具体修复步骤

  1. 修正 GroupeManager 的状态管理与初始化逻辑
    不要用@State存储GroupeManager,如果它是遵循ObservableObject的类,改用@StateObject来保证生命周期一致性;如果不是,也应该在视图初始化时先创建空实例,再在.task(iOS15+推荐,触发时机比onAppear更可靠)中加载数据。

    修改示例:

    // 假设 GroupeManager 遵循 ObservableObject
    @StateObject var groupeManager: GroupeManager
    
    // 视图初始化时传入空构造(如果支持)
    init(courseId: String?) {
        _groupeManager = StateObject(wrappedValue: GroupeManager(courseId: courseId))
    }
    
  2. 同步数组长度,消除越界风险
    把selectedParcoursIndexArray的填充逻辑改成直接生成对应长度的数组,替换初始值,确保和groupeManager.groupeList.count完全匹配:

    .task {
        // 若需要重新加载数据,这里更新groupeManager(建议用专门的加载方法而非直接重新初始化)
        groupeManager.reloadData(courseId: objCourse.id!)
        // 直接生成对应长度的默认数组
        selectedParcoursIndexArray = Array(repeating: 0, count: groupeManager.groupeList.count)
    }
    
  3. 给 Binding 加安全访问兜底
    即使数组长度匹配,也建议在Binding的get/set方法中加入索引检查,避免极端情况崩溃:

    Picker(selection: Binding(
        get: { 
            // 索引有效才返回对应值,否则返回默认0
            self.selectedParcoursIndexArray.indices.contains(num) ? self.selectedParcoursIndexArray[num] : 0 
        },
        set: { (newValue) in
            // 索引有效才赋值
            if self.selectedParcoursIndexArray.indices.contains(num) {
                self.selectedParcoursIndexArray[num] = newValue
                peutSauvegarder = true
            }
        }), 
    label: HStack{
        Image(systemName: "person.fill")
        Text("\(groupeManager.groupeList[num].nomGroupe)")
    }.foregroundColor(selectedParcoursIndexArray.indices.contains(num) && selectedParcoursIndexArray[num] != 0 ? .orange : Color("ColorDarkLight"))
    ) {
        if enCourse.count != 0 {
            ...
        }
    }
    
  4. 延迟渲染依赖数据的视图
    在Form外层加条件判断,确保groupeManager.groupeList有数据后再渲染,彻底避免初期空数组的问题:

    if !groupeManager.groupeList.isEmpty {
        Form{
            // 原Form内容
        }
    } else {
        // 显示加载提示
        ProgressView("加载中...")
    }
    

关键修改后的代码片段

struct DepartsSimultanesView: View {
    @EnvironmentObject var objCourse : CourseActuelle
    @EnvironmentObject var zindex : Zindex
    @EnvironmentObject var listActuelle : ListActuelle
    @State var selectedParcoursIndexArray : [Int] = []
    @State var peutSauvegarder = false
    @StateObject var groupeManager: GroupeManager
    var listeParcoursRestants : [String] = []
    @State private var action: Int? = 0
    
    init(courseId: String?) {
        _groupeManager = StateObject(wrappedValue: GroupeManager(courseId: courseId))
    }
    
    var body: some View {
        VStack{
            HStack{
                ...
            }
            HStack{
                ...
            }
            
            // 数据加载完成后再渲染Form
            if !groupeManager.groupeList.isEmpty {
                Form{
                    Section(header: Text("Coureurs/Choix parcours")){
                        ForEach(0 ..< groupeManager.groupeList.count, id:\.self){ num in
                            let detailManager = DetailManager(groupeId: groupeManager.groupeList[num].id, courseId: objCourse.id!)
                            let listeParcoursRestants = listRestants(detailManager: detailManager)
                            let enCourse = detailManager.enCourseList.filter { $0.groupeId == groupeManager.groupeList[num].id }
                            
                            Picker(selection: Binding(
                                get: { 
                                    self.selectedParcoursIndexArray.indices.contains(num) ? self.selectedParcoursIndexArray[num] : 0 
                                },
                                set: { (newValue) in
                                    if self.selectedParcoursIndexArray.indices.contains(num) {
                                        self.selectedParcoursIndexArray[num] = newValue
                                        peutSauvegarder = true
                                    }
                                }), 
                            label: HStack{
                                Image(systemName: "person.fill")
                                Text("\(groupeManager.groupeList[num].nomGroupe)")
                            }.foregroundColor(selectedParcoursIndexArray.indices.contains(num) && selectedParcoursIndexArray[num] != 0 ? .orange : Color("ColorDarkLight"))
                            ) {
                                if enCourse.count != 0 {
                                    ...
                                }
                            }
                            .navigationBarHidden(true)
                            .disabled(listeParcoursRestants.count == 1 || enCourse.count != 0)
                        }
                    }
                }
            } else {
                ProgressView("加载中...")
            }
        }
        .task {
            groupeManager.reloadData(courseId: objCourse.id!)
            selectedParcoursIndexArray = Array(repeating: 0, count: groupeManager.groupeList.count)
        }
        .navigationBarHidden(true)
        NavigationLink(destination: CestPartiView().environmentObject(objCourse), tag: 1, selection: $action){}
    }
}

额外说明

iOS16对SwiftUI视图的渲染优先级和生命周期做了调整,onAppear可能会在视图部分渲染后才触发,所以依赖数据初始化的逻辑尽量用.task或者提前处理。另外,永远不要假设数组索引一定有效,加安全检查是避免崩溃的好习惯。

内容的提问来源于stack exchange,提问作者Clément Tengip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 10:26:04