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

SwiftUI列表侧滑删除随机选中元素问题求助

SwiftUI侧滑删除功能异常:随机删除列表元素

我的SwiftUI应用中,用户列表的侧滑删除(swipe to delete)功能存在问题:执行删除操作时,数组里会随机删除一个元素。一开始以为是索引赋值错误,但调试后发现索引函数运行正常,问题应该出在行的选中逻辑上。我尝试给ForEach添加id: \.self,还试过将确认对话框和待删除用户绑定,但都没法正确删除目标用户,相关代码如下:

初始代码

import SwiftUI

struct AdminUserTableView: View {
    @State var userViewModel = UserViewModel.shared
    
    @State var deleteAdminUserMessage = ""
    @State var deleteAdminAlert = false
    @State var deleteConfirm = false
    
    @State var getUsersMessage = ""
    @State var getUsersAlert = false
    
    var body: some View {
        Text("")
            .navigationTitle("Admin Users")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    NavigationLink
                    {
                        AddUserView()
                    } label: {
                        Text("+")
                            .font(.largeTitle)
                    }
                }
            }
        
        List {
            ForEach(userViewModel.adminUsers, id: \.self) { user in
                AdminUserCellView(user: user)
                    .swipeActions {
                        Button("Delete") {
                            deleteConfirm = true
                        }
                        .tint(.red)
                    }
                    .confirmationDialog("Are you sure you want to delete \(user.email)?", isPresented: $deleteConfirm, titleVisibility: .visible) {
                        Button("Delete", role: .destructive) {
                            
                            Task {
                                do {
//Delete user from server
                                    var httpStatus = try await NetworkController.shared.deleteAdminUser(user: user)
                                    
                                    let index = UserViewModel.shared.adminUsers.firstIndex(where: { $0.id == user.id })!
//Delete user from array.
                                    userViewModel.removeUser(at: index)
                                } catch {
                                    deleteAdminUserMessage = "Unable to delete user. Please try again."
                                    deleteAdminAlert = true
                                }
                            }
                        }
                        
                        Button("Cancel", role: .cancel) {}
                    }
            }

        }
        .alert(deleteAdminUserMessage, isPresented: $deleteAdminAlert) {
                    Button("OK", role: .cancel) { }
                }
        .onAppear {
            Task {
                do {
                    UserViewModel.shared.adminUsers = try await NetworkController.shared.getAdminUsers()
                } catch {
                    getUsersMessage = "Failed to get admin users from server. Go back, then try again."
                    getUsersAlert = true
                }
            }
        }
    }
}

UserViewModel代码

import Foundation

@Observable class UserViewModel {
    static var shared = UserViewModel()
    
    var adminUsers: [User] = []
    var residentUsers: [ResidentUser] = []
    
    func addAdminUser(_ user: User) {
        adminUsers.append(user)
    }
    
    func removeUser(at index: Int) {
        adminUsers.remove(at: index)
    }
}

修改后的尝试代码

import SwiftUI

struct AdminUserTableView: View {
    @State private var userViewModel = UserViewModel()
    
    @State var deleteAdminUserMessage = ""
    @State var deleteAdminAlert = false
//    @State var deleteConfirm = false
    @State var userToDelete: User? = nil
    
    @State var getUsersMessage = ""
    @State var getUsersAlert = false
    
    var body: some View {
        Text("")
            .navigationTitle("Admin Users")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    NavigationLink
                    {
                        AddUserView()
                    } label: {
                        Text("+")
                            .font(.largeTitle)
                    }
                }
            }
        
        List {
            ForEach(userViewModel.adminUsers, id: \.self) { user in
                AdminUserCellView(user: user)
                    .swipeActions {
                        Button("Delete") {
                            userToDelete = user
                        }
                        .tint(.red)
                    }
                    .confirmationDialog("Are you sure you want to delete \(userToDelete?.email ?? "")?", isPresented: Binding<Bool>(
                                                get: { userToDelete != nil },
                                                set: { if !$0 { userToDelete = nil } }
                                            ), titleVisibility: .visible) {
                        Button("Delete", role: .destructive) {
                            
                            if let userToDelete = userToDelete {
                                Task {
                                    do {
                                        var httpStatus = try await NetworkController.shared.deleteAdminUser(user: user)
                                        
                                        if httpStatus.statusCode == 200 {
                                            let index = userViewModel.adminUsers.firstIndex(where: { $0.id == user.id })!
                                            userViewModel.removeUser(at: index)
                                        }
                                        
                                    } catch {
                                        deleteAdminUserMessage = "Unable to delete user. Please try again."
                                        deleteAdminAlert = true
                                    }
                                }
                            }
                        }
                        
                        Button("Cancel", role: .cancel) {}
                    }
            }

        }
        .alert(deleteAdminUserMessage, isPresented: $deleteAdminAlert) {
                    Button("OK", role: .cancel) { }
                }
        .onAppear {
            Task {
                do {
                    userViewModel.adminUsers = try await NetworkController.shared.getAdminUsers()
                } catch {
                    getUsersMessage = "Failed to get admin users from server. Go back, then try again."
                    getUsersAlert = true
                }
            }
        }
    }
}

问题根源及修复方案

核心问题

  1. 状态共享冲突:所有列表项共用同一个deleteConfirm或userToDelete状态,导致多个删除触发时状态混淆,SwiftUI无法准确匹配待删除项。
  2. 列表项识别错误:使用id: \.self依赖User的Equatable实现,若未正确实现或用户数据存在重复标识,会导致SwiftUI无法精准区分列表项。
  3. ViewModel实例不一致:初始代码混合使用单例和局部@State实例,导致数据不同步。

具体修复步骤

1. 让User遵循Identifiable协议

确保SwiftUI能精准识别每个列表项,避免手动指定id: \.self的潜在问题:

struct User: Identifiable, Codable {
    let id: String // 或UUID等唯一标识类型
    let email: String
    // 其他用户属性
}
2. 为每个列表项单独管理删除状态

用用户ID存储待删除标记,避免全局状态冲突:

struct AdminUserTableView: View {
    @State private var userViewModel = UserViewModel()
    @State private var deleteAlertMessage = ""
    @State private var showDeleteAlert = false
    @State private showGetUsersAlert = false
    @State private getUsersErrorMessage = ""
    @State private pendingDeleteId: User.ID? // 存储待删除用户的唯一ID
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(userViewModel.adminUsers) { user in
                    AdminUserCellView(user: user)
                        .swipeActions {
                            Button("删除", role: .destructive) {
                                pendingDeleteId = user.id
                            }
                        }
                        .confirmationDialog(
                            "确定要删除\(user.email)吗?",
                            isPresented: Binding(
                                get: { pendingDeleteId == user.id },
                                set: { if !$0 { pendingDeleteId = nil } }
                            ),
                            titleVisibility: .visible
                        ) {
                            Button("删除", role: .destructive) {
                                guard let index = userViewModel.adminUsers.firstIndex(where: { $0.id == user.id }) else { return }
                                Task {
                                    do {
                                        let httpStatus = try await NetworkController.shared.deleteAdminUser(user: user)
                                        if httpStatus.statusCode == 200 {
                                            userViewModel.removeUser(at: index)
                                        } else {
                                            deleteAlertMessage = "删除失败,请重试"
                                            showDeleteAlert = true
                                        }
                                    } catch {
                                        deleteAlertMessage = "无法删除用户,请重试"
                                        showDeleteAlert = true
                                    }
                                }
                                pendingDeleteId = nil
                            }
                            Button("取消", role: .cancel) {
                                pendingDeleteId = nil
                            }
                        }
                }
            }
            .navigationTitle("Admin Users")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    NavigationLink(destination: AddUserView()) {
                        Text("+")
                            .font(.largeTitle)
                    }
                }
            }
            .alert(deleteAlertMessage, isPresented: $showDeleteAlert) {
                Button("OK", role: .cancel) {}
            }
            .alert(getUsersErrorMessage, isPresented: $showGetUsersAlert) {
                Button("OK", role: .cancel) {}
            }
            .onAppear {
                Task {
                    do {
                        userViewModel.adminUsers = try await NetworkController.shared.getAdminUsers()
                    } catch {
                        getUsersErrorMessage = "获取管理员用户失败,请返回后重试"
                        showGetUsersAlert = true
                    }
                }
            }
        }
    }
}
3. 统一ViewModel实例使用

要么全局使用单例,要么局部持有并通过环境传递,避免混合使用导致的数据不同步问题。

额外注意事项

  • 网络请求成功后再更新本地数据,确保本地与服务器数据一致。
  • 若坚持使用id: \.self,必须确保User正确实现Equatable协议,且所有属性的比较逻辑符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:35:56