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

SwiftUI添加客户工作后List View不更新问题求助

SwiftUI 添加工作记录后列表不刷新的问题解决

我用SwiftUI实现了为已注册客户添加工作记录的功能,但遇到一个问题:添加工作并关闭弹窗(sheet)后,WorkListView中的工作列表不会自动更新,必须退出该视图再重新进入才能看到最新的记录。我已经用了@State或@ObservedObject修饰相关变量,按理解应该会自动刷新视图,相关代码如下:

WorkListView 代码

struct WorkListView: View {
    @State var client: Client
    @ObservedObject var clientData = ClientData()
    @State private var showingAddWork = false
    @State private var additionalInfo: [String] = []
    
    
    let dateFormatterStart: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d, HH:mm"
        return formatter
    }()
    let dateFormatterEnd: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm"
        return formatter
    }()
    func calculateHoursAndMinutes(startDate: Date, endDate: Date) -> String {
        let interval = endDate.timeIntervalSince(startDate)
        let hours = Int(interval) / 3600
        let minutes = Int(interval) / 60 % 60
        return "\(hours) hour(s) and \(minutes) minute(s)"
    }
    var body: some View {
        
        
        List {
            Section {
                VStack(alignment: .leading) {
                    Text("Company")
                        .font(.footnote)
                        .foregroundColor(.secondary)
                    TextField("Company", text: $client.company)
                        .fontWeight(.semibold)
                        .autocorrectionDisabled(true)
                }
                
                VStack(alignment: .leading) {
                    Text("E-mail")
                        .font(.footnote)
                        .foregroundColor(.secondary)
                    TextField("E-mail", text: $client.eMail)
                        .fontWeight(.semibold)
                        .keyboardType(.emailAddress)
                }
                VStack(alignment: .leading) {
                    Text("Phone")
                        .font(.footnote)
                        .foregroundColor(.secondary)
                    TextField("Phone", text: $client.phone)
                        .fontWeight(.semibold)
                        .keyboardType(.numberPad)
                }
                VStack(alignment: .leading) {
                    Text("Price")
                        .font(.footnote)
                        .foregroundColor(.secondary)
                    TextField("Price", text: $client.price)
                }
                
            }
            
            Section(header: Text("Performed Work")) {
                ForEach(client.work.indices) { work in
                        VStack(alignment: .leading) {
                            Text("\(self.dateFormatterStart.string(from: self.client.work[work].startDate)) to \(self.dateFormatterEnd.string(from: self.client.work[work].endDate))\nDuration: \(self.calculateHoursAndMinutes(startDate: self.client.work[work].startDate, endDate: self.client.work[work].endDate))\n\(self.client.work[work].title)")
                            
                        }

                }
                
                .onDelete { indexSet in
                    guard let workIndex = indexSet.first else { return }
                    self.clientData.deleteWork(for: self.client, work: self.client.work[workIndex])
                }

            }
        }
        
        .navigationBarTitle(client.name)
        .navigationBarItems(trailing: Button(action: {
            self.showingAddWork = true
        }) {
            Image(systemName: "plus")
        })
        .sheet(isPresented: $showingAddWork) {
            AddWorkView(client: self.client, clientData: self.clientData, isPresented: $showingAddWork)
            
        }
    }
}

AddWorkView 代码

struct AddWorkView: View {
    @State var client: Client
    @ObservedObject var clientData = ClientData()
    @Binding var isPresented: Bool
    @State private var title = ""
    @State private var startDate = Date()
    @State private var endDate = Date()
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Work title", text: $title)
                DatePicker("Start date", selection: $startDate)
                    .environment(\.locale, Locale(identifier: "sv-SE"))
                DatePicker("End date", selection: $endDate)
                    .environment(\.locale, Locale(identifier: "sv-SE"))
                
                Button(action: {
                    let work = Work(title: self.title, startDate: self.startDate, endDate: self.endDate)
                    
                    self.clientData.addWork(for: self.client, work: work)
                    self.isPresented = false
                }) {
                    Text("Add work")
                }
            }
            .navigationBarTitle("Add work")
            .navigationBarItems(trailing: Button(action: {
                self.isPresented = false
            }) {
                Text("Cancel")
            })
        }
    }
        
}

补充:正常工作的客户添加代码

ContentView

struct ContentView: View {
    @ObservedObject var clientData = ClientData()
    @State private var showingAddClient = false
    @State private var showingAddWork = false
      
    var body: some View {
        NavigationView {
            List {
                ForEach(clientData.clients) { client in
                    NavigationLink(destination: WorkListView(client: client, clientData: self.clientData)) {
                        VStack(alignment: .leading) {
                            Text(client.name)
                                .font(.headline)
                            Text(client.company)
                                .foregroundColor(.secondary)
                        }
                    }
                }
                .onDelete { indices in
                    indices.forEach { self.clientData.clients.remove(at: $0) }
                }
            }
            .navigationBarTitle("Clients")
            .navigationBarItems(leading: EditButton(), trailing: Button(action: {
                self.showingAddClient.toggle()
            }) {
                Image(systemName: "plus")
            })
            .sheet(isPresented: $showingAddClient) {
                AddClientView(clientData: self.clientData, isPresented: self.$showingAddClient)
            }
        }
    }
}

AddClientView

struct AddClientView: View {
    @ObservedObject var clientData = ClientData()
    @Binding var isPresented: Bool
    @State private var name = ""
    @State private var company = ""
    @State private var eMail = ""
    @State private var phone = ""
    @State private var price = ""
    @State private var additionalInfo = ""
    
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Client name", text: $name)
                    .autocorrectionDisabled(true)
                TextField("Comapny", text: $company)
                    .autocorrectionDisabled(true)
                TextField("E-mail", text: $eMail)
                    .keyboardType(.emailAddress)
                TextField("Phone", text: $phone)
                    .keyboardType(.numberPad)
                TextField("Price", text: $price)
                    .keyboardType(.numberPad)
                Button(action: {
                    let client = Client(name: self.name, company: self.company, eMail: self.eMail, phone: self.phone, price: self.price)
                    
                    do {
                        try self.clientData.addClient(client)
                        self.isPresented = false
                    } catch {
                        // Handle the error
                    }
                }) {
                    Text("Add client")
                }
            }
            .navigationBarTitle("Add client")
            .navigationBarItems(trailing: Button(action: {
                self.isPresented = false
            }) {
                Text("Cancel")
            })
            
        }
    }
}

Client 类代码

class Client: ObservableObject, Codable, Hashable, Identifiable, Equatable {
    var id = UUID()
    @Published var name: String
    @Published var company: String
    @Published  var eMail: String
    @Published  var phone: String
    @Published  var price: String
    @Published  var work: [Work]
    
    init(name: String, company: String, eMail: String, phone: String, price: String) {
        self.name = name
        self.company = company
        self.eMail = eMail
        self.phone = phone
        self.price = price
        self.work = []
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        company = try container.decode(String.self, forKey: .company)
        eMail = try container.decode(String.self, forKey: .eMail)
        phone = try container.decode(String.self, forKey: .phone)
        price = try container.decode(String.self, forKey: .price)
        work = try container.decode([Work].self, forKey: .work)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(company, forKey: .company)
        try container.encode(eMail, forKey: .eMail)
        try container.encode(phone, forKey: .phone)
        try container.encode(price, forKey: .price)
        try container.encode(work, forKey: .work)
    }
    
    enum CodingKeys: String, CodingKey {
        case name
        case company
        case eMail
        case phone
        case price
        case work
    }
    
    static func == (lhs: Client, rhs: Client) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

问题根源

  1. client修饰符错误:WorkListView中用@State var client: Client,但Client是继承自ObservableObject的引用类型,@State仅适用于值类型,无法监听ObservableObject内部@Published属性(比如work数组)的变化。
  2. AddWorkView创建独立数据源实例:AddWorkView中直接初始化@ObservedObject var clientData = ClientData(),操作的是全新的数据源,而非WorkListView传递的共享实例,导致原数据未被修改。
  3. ForEach遍历方式不够可靠:用ForEach(client.work.indices)遍历,虽然能工作,但配合ObservableObject的数组更新,直接遍历数组元素(需Work实现Identifiable)的方式刷新更稳定。

修复步骤

1. 修改WorkListView中client的修饰符

将@State var client: Client改为@ObservedObject var client: Client,同时移除clientData的本地初始化,确保接收从ContentView传递的实例:

struct WorkListView: View {
    @ObservedObject var client: Client
    @ObservedObject var clientData: ClientData // 不再本地初始化
    @State private var showingAddWork = false
    @State private var additionalInfo: [String] = []
    // ... 其余代码不变
}

2. 修复AddWorkView的数据源传递

AddWorkView不应新建ClientData实例,而是接收外部传入的共享实例,同时将client改为@ObservedObject监听变化:

struct AddWorkView: View {
    @ObservedObject var client: Client
    @ObservedObject var clientData: ClientData // 接收外部实例
    @Binding var isPresented: Bool
    @State private var title = ""
    @State private var startDate = Date()
    @State private var endDate = Date()
    
    // ... 其余代码不变
}

3. 让Work实现Identifiable(可选但推荐)

给Work添加Identifiable协议,简化ForEach遍历:

struct Work: Identifiable, Codable {
    var id = UUID()
    var title: String
    var startDate: Date
    var endDate: Date
}

修改WorkListView中的ForEach:

ForEach(client.work) { work in
    VStack(alignment: .leading) {
        Text("\(dateFormatterStart.string(from: work.startDate)) to \(dateFormatterEnd.string(from: work.endDate))\nDuration: \(calculateHoursAndMinutes(startDate: work.startDate, endDate: work.endDate))\n\(work.title)")
    }
}

4. 确保ClientData的addWork方法正确修改数据源

确认addWork方法修改的是clientData.clients数组中的对应实例:

class ClientData: ObservableObject {
    @Published var clients: [Client] = []
    
    func addWork(for client: Client, work: Work) {
        if let index = clients.firstIndex(where: { $0.id == client.id }) {
            clients[index].work.append(work)
        }
    }
}

完成上述修改后,添加工作记录后WorkListView会自动刷新,显示最新的记录。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 13:51:30