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

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)
        }
    }
}

问题原因与修复方案

核心问题

你的状态更新逻辑存在两个关键问题:

  1. 视图更新周期冲突:点击按钮时直接同步修改viewStatus = "loading",该操作发生在SwiftUI视图重绘的周期内,触发了状态修改与视图更新的并发冲突,导致报错。
  2. 状态管理不规范:用字符串管理视图状态,不仅容易出现拼写错误,也不符合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:45:22