SwiftUI发送数据到服务器后更新视图,规避状态更新错误
问题:CreateOfferView状态更新异常及错误分析
我通过JobViewModel的createJob函数向服务器发送POST请求,数据库中已成功创建关联任务,但点击“Create job”按钮后,CreateOfferView未按预期更新,还出现Modifying state during view update, this will cause undefined behavior错误。想确认CreateOfferView中的状态更新方式是否正确?注:已知viewStatus不应为String类型。
ViewModel代码
import SwiftUI struct JobRequestBody: Codable { let title: String let description: String let time: Date let location: String let userId: User } class JobViewModel: ObservableObject { // MARK: - PROPERTIES var userId: String var token: String @Published var jobs: [JobOffer]? // MARK: - FUNCTIONS init(userId: String, token: String) { self.userId = userId self.token = token } func createJob(title: String, description: String, time: Date, location: String, owner: User, completion: @escaping (Bool) -> Void) { guard let url = URL(string: "http://localhost:4000/api/jobs") else { return } let body = JobRequestBody(title: title, description: description, time: time, location: location, userId: owner) var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("Bearer " + token, forHTTPHeaderField: "Authorization") request.httpBody = try? JSONEncoder().encode(body) URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { completion(false) return } DispatchQueue.main.async { completion(true) } }.resume() } }
View代码
import SwiftUI struct CreateOfferView: View { // MARK: - PROPERTIES @State private var title = "" @State private var time = Date() @State private var location = "" @State private var description = "" @State private var viewStatus = "notCreated" @EnvironmentObject var userViewModel: UserViewModel @EnvironmentObject var jobViewModel: JobViewModel // MARK: - BODY var body: some View { if viewStatus == "notCreated" { createJobBody } else if viewStatus == "loading" { loadingBody } else { createdBody } } var loadingBody: some View { VStack { ProgressView() .scaleEffect(2) } .padding(25) } var createdBody: some View { VStack { Text("🎉") .font(.largeTitle) .padding(.bottom, 20) Text("Congrats! You have successfully created a new job.") } .padding(25) } var createJobBody: some View { NavigationView { VStack { SecondaryTextFieldView(placeholderSymbol: "pencil.circle.fill", placeholderText: "Title", content: $title) DatePicker(selection: $time, in: ...Date(), displayedComponents: .date) { Text("\(Image(systemName: "calendar.circle.fill")) Time") .foregroundColor(.gray) } .accentColor(.black) .padding(.top, 25) SecondaryTextFieldView(placeholderSymbol: "mappin.circle.fill", placeholderText: "Location", content: $location) MultiLineTextFieldView(placeholderSymbol: "line.3.horizontal.circle.fill", placeholderText: "Description", content: $description) Spacer() SecondaryButton(symbol: "plus.circle.fill", title: "Create job") { viewStatus = "loading" let user = userViewModel.authData?.loggedInUser if let owner = user { jobViewModel.createJob(title: title, description: description, time: time, location: location, owner: owner) { success in // Also tried not to wrap state update in DispatchQueue as I already do so in the ViewModel function DispatchQueue.main.async { viewStatus = "created" } } } } } .navigationTitle("Suggest a job") .padding(25) } } }
问题原因与修复方案
核心问题
你的状态更新逻辑存在两个关键问题:
- 视图更新周期冲突:点击按钮时直接同步修改
viewStatus = "loading",该操作发生在SwiftUI视图重绘的周期内,触发了状态修改与视图更新的并发冲突,导致报错。 - 状态管理不规范:用字符串管理视图状态,不仅容易出现拼写错误,也不符合SwiftUI类型安全的设计原则。
修复步骤
1. 用枚举替代字符串管理视图状态
先定义一个枚举规范状态类型,避免字符串的不确定性:
enum ViewStatus { case notCreated, loading, created }
然后修改View中的状态变量:
@State private var viewStatus: ViewStatus = .notCreated
视图分支判断也同步改为枚举匹配:
var body: some View { switch viewStatus { case .notCreated: createJobBody case .loading: loadingBody case .created: createdBody } }
2. 异步延迟状态更新
点击按钮时,将viewStatus的修改放入DispatchQueue.main.async中,确保状态修改发生在视图更新周期之外:
SecondaryButton(symbol: "plus.circle.fill", title: "Create job") { // 异步修改加载状态,避开视图更新周期 DispatchQueue.main.async { self.viewStatus = .loading } guard let owner = userViewModel.authData?.loggedInUser else { return } jobViewModel.createJob(title: title, description: description, time: time, location: location, owner: owner) { success in DispatchQueue.main.async { self.viewStatus = .created } } }
3. 可选优化:将状态移至ViewModel管理(推荐)
把任务创建的加载状态交给JobViewModel统一管理,让View只负责响应状态:
class JobViewModel: ObservableObject { // ... 原有属性 @Published var isCreatingJob = false func createJob(title: String, description: String, time: Date, location: String, owner: User, completion: @escaping (Bool) -> Void) { // 主线程更新加载状态 DispatchQueue.main.async { self.isCreatingJob = true } // ... 原有网络请求逻辑 URLSession.shared.dataTask(with: request) { [weak self] data, response, error in DispatchQueue.main.async { self?.isCreatingJob = false completion(error == nil && data != nil) } }.resume() } }
之后View可以直接观察jobViewModel.isCreatingJob,减少内部状态管理的复杂度。
额外注意事项
- 确保所有
@Published属性的修改都在主线程执行,你的ViewModel中已经在DispatchQueue.main.async中调用completion,这部分是正确的。 - 网络请求中建议增加HTTP状态码判断(比如判断response是否为2xx),避免仅通过data和error判断请求成功与否。
内容的提问来源于stack exchange,提问作者Sebastian
相关产品推荐
相关产品推荐

