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) } }
问题根源
- client修饰符错误:WorkListView中用
@State var client: Client,但Client是继承自ObservableObject的引用类型,@State仅适用于值类型,无法监听ObservableObject内部@Published属性(比如work数组)的变化。 - AddWorkView创建独立数据源实例:AddWorkView中直接初始化
@ObservedObject var clientData = ClientData(),操作的是全新的数据源,而非WorkListView传递的共享实例,导致原数据未被修改。 - 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
相关产品推荐
相关产品推荐

