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

SwiftUI中如何设置依赖FetchRequest结果的@State数组?

问题解决:SwiftUI + CoreData 中 DisclosureGroup 展开状态数组索引越界崩溃

问题原因

你当前用@State var isListExpanded: [Bool]存储展开状态,数组长度仅在ContentView初始化时根据当时的学生数量设置。当点击"Add"按钮新增学生后,students的数量增加,但isListExpanded数组长度未同步更新,导致ForEach遍历students.indices时,访问的索引超出数组范围,触发Index out of range错误。

此外,直接依赖数组索引绑定状态还有隐藏问题:CoreData的FetchedResults可能因排序、数据变更等改变元素顺序,此时索引对应的学生对象会错乱,导致展开状态和学生不匹配。

解决方案

改用字典存储每个学生的展开状态,利用学生对象的id(UUID)作为字典的key,这样新增学生时只需为新的id添加默认状态,无需维护数组长度,同时避免索引顺序变化带来的状态错乱问题。

修改后的完整代码

1. StudentsApp.swift(简化初始化逻辑)

import SwiftUI
import CoreData

@main
struct StudentsApp: App {
    @StateObject private var dataController = DataController()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, dataController.container.viewContext)
        }
    }
}

2. DataController.swift(无需修改)

import Foundation
import CoreData

class DataController: ObservableObject {
    let container = NSPersistentContainer(name: "Students")
    
    init() {
        container.loadPersistentStores { description, error in
            if let error = error {
                print("Core Data failed to load: \(error.localizedDescription)")
            }
        }
    }
}

3. ContentView.swift(核心修改)

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: []) var students: FetchedResults<Student>
    @State private var expandedStates: [UUID: Bool] = [:] // 用字典存储每个学生的展开状态

    var body: some View {
        VStack {
            List {
                // 直接遍历学生对象,而非索引,避免顺序变化导致的状态错乱
                ForEach(students) { student in
                    Section {
                        // 利用字典的默认值,确保新学生默认处于折叠状态
                        DisclosureGroup(isExpanded: $expandedStates[student.id!, default: false]) {
                            Text("Expanded")
                        } label: {
                            Text(student.name ?? "Unknown")
                        }
                    }
                }
            }
            Button("Add") {
                let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
                let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]
                let chosenFirstName = firstNames.randomElement()!
                let chosenLastName = lastNames.randomElement()!
                
                let student = Student(context: moc)
                student.id = UUID()
                student.name = "\(chosenFirstName) \(chosenLastName)"
                
                try? moc.save()
                // 可选:显式设置新学生的展开状态为false,省略也可,因为字典访问有default值
                expandedStates[student.id!] = false
            }
        }
    }
}

额外说明

  • 移除了StudentsApp中不必要的手动fetch操作,因为ContentView的@FetchRequest会自动监听CoreData上下文变化,实时更新学生列表。
  • 直接遍历students对象而非索引,符合SwiftUI的最佳实践,避免因数据顺序变化导致的UI与状态不匹配问题。
  • 字典的default: false确保新增学生时,即使未显式添加key,DisclosureGroup也能正常获取默认的折叠状态。

内容的提问来源于stack exchange,提问作者Dogahe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 13:47:37