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

SwiftUI中修改子对象属性后退后视图不更新问题求助

问题:SwiftUI列表修改子对象属性后无法自动刷新

我已经将代码精简到最小复现版本:当修改数组中某个对象的属性时,数据已变更但SwiftUI视图无法可靠更新。

操作步骤:

  • 创建新Schedule
  • 点击该Schedule进入编辑页
  • 修改其名称
  • 返回列表页

此时列表视图不会更新,除非新增一个Schedule才会触发List刷新。请问如何实现名称修改后立即更新视图?


原代码

Views

import SwiftUI
import CoreData

struct ContentView: View {
    
    @ObservedObject var allSchedules:AllSchedules = AllSchedules.shared
 
    var body: some View {
        NavigationView {
            List {
                ForEach (allSchedules.schedules) { s in
                    NavigationLink(destination: EditView(schedule: s), label: {Text(s.name)} )
                }
                
                Button("Add a Schedule") { allSchedules.schedules.append(Schedule()) }
                
            }
        }
    }
}


struct EditView: View {
 
    @State var schedule:Schedule
 
    var body: some View {
        List {
            TextField("Change Name", text: $schedule.name, prompt: Text("Change Name"))
        }
    }
}

Classes

class AllSchedules:ObservableObject {

    @Published var schedules:Array<Schedule> = []
    
    static let shared = AllSchedules()
    
    private init() {}
    
}

class Schedule: ObservableObject, Identifiable {

    let id = UUID()
    
    @Published var name:String = "New Schedule"
    
}

项目模板代码(无此代码无法运行)

import CoreData

@main
struct Timer3App: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "Timer3")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

解决方案

核心问题:AllSchedules里的@Published var schedules只会在数组本身增删元素时发送更新通知,但数组内的Schedule对象是引用类型,其内部属性变化不会触发AllSchedules的更新信号,导致ContentView无法刷新。

下面提供两种实用解决方法:

方法一:将Schedule改为值类型(struct)

值类型的属性修改会生成新实例,数组能检测到元素变化,触发@Published更新,是最简洁的方案。

修改后的代码

Classes部分

class AllSchedules: ObservableObject {
    @Published var schedules: Array<Schedule> = []
    
    static let shared = AllSchedules()
    
    private init() {}
}

struct Schedule: Identifiable {
    let id = UUID()
    var name: String = "New Schedule"
}

Views部分

调整EditView使用@Binding传递值类型,ContentView的ForEach绑定数组元素:

struct ContentView: View {
    @ObservedObject var allSchedules: AllSchedules = AllSchedules.shared
 
    var body: some View {
        NavigationView {
            List {
                // 使用$绑定数组元素
                ForEach ($allSchedules.schedules) { $s in
                    NavigationLink(destination: EditView(schedule: $s), label: {Text(s.name)} )
                }
                
                Button("Add a Schedule") { 
                    allSchedules.schedules.append(Schedule()) 
                }
            }
        }
    }
}

struct EditView: View {
    // 使用@Binding接收值类型
    @Binding var schedule: Schedule
 
    var body: some View {
        List {
            TextField("Change Name", text: $schedule.name, prompt: Text("Change Name"))
        }
    }
}

方法二:手动监听子对象变化(保留类类型)

如果必须用引用类型的Schedule,可以在AllSchedules中监听每个Schedule的属性变化,手动触发父对象的更新。

修改后的代码

Classes部分

import Combine

class AllSchedules: ObservableObject {
    @Published var schedules: Array<Schedule> = []
    
    static let shared = AllSchedules()
    
    private init() {}
    
    // 封装添加方法,同时监听子对象变化
    func addSchedule() {
        let newSchedule = Schedule()
        // 监听子对象的更新信号,触发当前对象的刷新
        let cancellable = newSchedule.objectWillChange.sink { [weak self] _ in
            self?.objectWillChange.send()
        }
        // 保存监听对象,防止被提前释放
        newSchedule.cancellable = cancellable
        schedules.append(newSchedule)
    }
}

class Schedule: ObservableObject, Identifiable {
    let id = UUID()
    @Published var name: String = "New Schedule"
    // 保存监听的生命周期对象
    var cancellable: AnyCancellable?
}

Views部分

修改添加按钮调用封装的方法:

Button("Add a Schedule") { allSchedules.addSchedule() }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 13:35:18