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

SwiftUI删除按钮报错:视图更新内禁止发布变更的原因与修复

问题描述

从数据库获取买家列表后,点击删除按钮出现两个问题:

  1. 弹窗显示错误的买家名称
  2. 控制台抛出报错:'Publishing changes from within view updates is not allowed, this will cause undefined behavior.'

相关代码如下:

BuyersView 代码

struct BuyersView: View {
    @StateObject var viewModel = BuyersViewModel()

    @State var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
                VStack{
                    CategoryView(categories: viewModel.fases, currentSelection: $viewModel.currentFase)
                    HStack{
                        Spacer()
                        NavigationLink("Add buyer", value: 0)
                            .padding()
                    }
                    List(viewModel.buyers){ buyer in
                            HStack{
                                NavigationLink(value: buyer) {
                                    VStack(alignment: .leading){
                                        Text(buyer.name ?? "name")
                                            .font(.title3)
                                        Text("Fase : \(buyer.fase ?? "fase")")
                                    }
                                }
                                Spacer()
                                DeleteButton(showingAlert: $viewModel.showingAlert, buyer: buyer, vm: viewModel, deleteUser: {viewModel.deleteBuyer(id: buyer.id, currentfase: viewModel.currentFase)})
                        }

                    }.onChange(of: viewModel.currentFase) { _, newValue in
                        viewModel.resetBuyers()
                    }
                }
                .navigationDestination(for: Int.self) { _ in
                    AddBuyerView(path: $path, fases: viewModel.fases, onBuyerAdded: { viewModel.resetBuyers()})
                }.navigationDestination(for: BuyerData.self) { buyer in
                    BuyerDetailView(buyer: buyer)
                }
        }
    }
}

BuyersViewModel 代码

import Foundation

class BuyersViewModel: ObservableObject {
    
    @Published var buyers : [BuyerData] = []
    @Published var showingAlert = false
    @Published var currentFase : Category = .init(title: "All Buyers")
    
    let fases: [Category] = [
        .init(title: "All Buyers"),
        .init(title: "Nieuw"),
        .init(title: "Email contact"),
        .init(title: "Gesproken"),
        .init(title: "Gepland"),
        .init(title: "Bezocht"),
        .init(title: "Bod"),
        .init(title: "Sale"),
        .init(title: "Zoekt niet meer")
    ]
    
    
    init() {
        buyers = getAllBuyers(currentfase: currentFase)
        }
    
    func deleteBuyer(id: Int, currentfase: Category) {
        let db = DBHelper()
        db.deleteBuyer(id: id)
        if currentfase.title == "All Buyers"{
            buyers = db.AllBuyers()
        }
        else{
            buyers = db.AllB(fase: currentfase.title)
        }
        db.close()
    }
    
    func getAllBuyers(currentfase: Category) -> [BuyerData]{
        let db = DBHelper()
        var allbuyers: [BuyerData]
        if currentfase.title == "All Buyers"{
            allbuyers = db.AllBuyers()
        }
        else{
            allbuyers = db.AllB(fase: currentfase.title)
        }
        db.close()
        return allbuyers
    }
    
    func resetBuyers(){
        buyers = getAllBuyers(currentfase: currentFase)
    }
    
}

DeleteButton 代码

struct DeleteButton: View {
    @Binding var showingAlert : Bool
    var buyer : BuyerData?
    var seller: SellerData?
    @StateObject var vm : BuyersViewModel
    var deleteUser: () -> Void
    
    var messageText : String {
        (seller == nil ? buyer?.name : seller?.name) ?? "this user"
    }
    var titleText : String {
        buyer == nil ? "Seller" : "Buyer"
    }

    var body: some View {
        Button("delete") {
            vm.showingAlert = true
        }
        .buttonStyle(.borderless)
        .foregroundColor(.red)
        .alert("Deleting \(titleText)", isPresented: $showingAlert) {
            Button(role: .destructive) {
                deleteUser()
                vm.showingAlert = false
            } label: {
                Text("Delete")
            }
            Button(role: .cancel) {
                vm.showingAlert = false
            } label: {
                Text("Cancel")
            }
        } message: {
            Text("Are you sure you want to delete \(messageText) \n this action cannot be undone")
        }
    }
}
问题原因
  1. 弹窗名称错误:所有DeleteButton共用ViewModel里的同一个showingAlert变量,当多个按钮渲染完成后,点击任意按钮都会触发最后一个绑定的弹窗内容,导致显示错误的买家名称。
  2. 控制台报错:deleteBuyer方法中直接修改@Published的buyers数组,而这个操作是在Alert的按钮回调中执行的——此时正处于SwiftUI的视图更新周期内,直接发布ObservableObject的变更会触发框架的限制报错。
修复方案

1. 让每个DeleteButton独立管理弹窗状态

把DeleteButton的弹窗状态从ViewModel的全局变量改为自己的@State变量,每个按钮单独控制弹窗,确保弹窗内容和当前绑定的买家匹配:

修改后的DeleteButton:

struct DeleteButton: View {
    var buyer : BuyerData?
    var seller: SellerData?
    var deleteUser: () -> Void
    
    // 每个按钮独立管理弹窗状态
    @State private var showingAlert = false
    
    var messageText : String {
        (seller == nil ? buyer?.name : seller?.name) ?? "this user"
    }
    var titleText : String {
        buyer == nil ? "Seller" : "Buyer"
    }

    var body: some View {
        Button("delete") {
            showingAlert = true
        }
        .buttonStyle(.borderless)
        .foregroundColor(.red)
        .alert("Deleting \(titleText)", isPresented: $showingAlert) {
            Button(role: .destructive) {
                deleteUser()
                showingAlert = false
            } label: {
                Text("Delete")
            }
            Button(role: .cancel) {
                showingAlert = false
            } label: {
                Text("Cancel")
            }
        } message: {
            Text("Are you sure you want to delete \(messageText) \n this action cannot be undone")
        }
    }
}

同时修改BuyersView中调用DeleteButton的代码,移除不必要的参数:

// 原代码
DeleteButton(showingAlert: $viewModel.showingAlert, buyer: buyer, vm: viewModel, deleteUser: {viewModel.deleteBuyer(id: buyer.id, currentfase: viewModel.currentFase)})

// 修改后
DeleteButton(buyer: buyer, deleteUser: {viewModel.deleteBuyer(id: buyer.id, currentfase: viewModel.currentFase)})

2. 异步更新ViewModel的Published变量

在deleteBuyer方法中,用DispatchQueue.main.async包装修改buyers数组的代码,确保变更操作在视图更新周期外执行,规避框架限制:

修改后的BuyersViewModel的deleteBuyer方法:

func deleteBuyer(id: Int, currentfase: Category) {
    let db = DBHelper()
    db.deleteBuyer(id: id)
    
    // 异步更新buyers数组,避免在视图更新周期内发布变更
    DispatchQueue.main.async {
        if currentfase.title == "All Buyers"{
            self.buyers = db.AllBuyers()
        }
        else{
            self.buyers = db.AllB(fase: currentfase.title)
        }
        db.close()
    }
}

最后移除ViewModel中不再使用的showingAlert变量:

class BuyersViewModel: ObservableObject {
    
    @Published var buyers : [BuyerData] = []
    // 移除不再使用的变量
    // @Published var showingAlert = false
    @Published var currentFase : Category = .init(title: "All Buyers")
    
    // ... 其他代码保持不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 23:19:50