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

SwiftUI中LazyVGrid滑动删除出现错误项删除问题

问题根源与修复方案

核心问题分析

  1. ForEach依赖索引作为唯一标识:删除操作会改变数组索引,SwiftUI的视图复用机制会错误地将旧视图绑定到新数据,导致UI显示错乱。
  2. 子ViewModel无唯一标识:SnagItemViewModel未实现Identifiable,ForEach无法区分不同的列表项。
  3. 状态修饰符使用错误:
    • SnagItemRowView中用@StateObject接收父视图传入的ViewModel,会导致视图持有旧的实例,无法响应数据更新。
    • reference用@State修饰,删除后视图复用时会保留旧的序号值,导致显示错误。
  4. 删除方法限制单一索引:原方法只删除第一个索引,无法正确处理批量删除场景。

修复步骤

1. 给数据模型添加唯一标识

修改SnagItem,让它遵循Identifiable协议,自动生成唯一ID:

struct SnagItem: Codable, Hashable, Identifiable {
    let id = UUID()
    private(set) var reference: Int = 0
    private(set) var details: String = ""
    private(set) var isCompleted: Bool = false
    
    enum CodingKeys: String, CodingKey, CaseIterable {
        case reference = "Reference"
        case details = "Details"
        case isCompleted = "IsCompleted"
    }
}

2. 让子ViewModel遵循Identifiable

复用SnagItem的ID作为SnagItemViewModel的唯一标识:

class SnagItemViewModel: ObservableObject, Identifiable {
    let id: UUID
    private let snagItem: SnagItem
    
    @Published var details: String = ""
    @Published var isCompleted: Bool = false
    
    init(withSnagItem snagItem: SnagItem) {
        self.id = snagItem.id
        self.snagItem = snagItem
        details = snagItem.details
        isCompleted = snagItem.isCompleted
    }
}

3. 修正容器视图的ForEach实现

直接遍历Identifiable数组,通过ID计算当前项的序号:

struct SnagItemsView: View {
    @StateObject var snagItemsViewModel: SnagItemsViewModel
    
    var body: some View {
        ForEach($snagItemsViewModel.snagItems) { $snagItemVM in
            let reference = snagItemsViewModel.snagItems.firstIndex(where: { $0.id == snagItemVM.id })! + 1
            SnagItemRowView(reference: reference,
                            snagItemViewModel: snagItemVM)
        }
        .onDelete(perform: delete(at:))
    }
    
    func delete(at indexSet: IndexSet) {
        withAnimation {
            snagItemsViewModel.snagItems.remove(atOffsets: indexSet)
        }
    }
}

注:如果需要避免强制解包,可以添加默认值,或者在SnagItemsViewModel中维护序号逻辑。

4. 修正行视图的状态修饰符

将@State改为普通常量,@StateObject改为@ObservedObject(iOS17+可替换为@Bindable):

struct SnagItemRowView: View {
    let reference: Int
    @ObservedObject var snagItemViewModel: SnagItemViewModel
    
    let gridItemColumnConfig = [
        GridItem(.fixed(Constants.Frame.minGridItemWidth)),
        GridItem(.flexible()),
        GridItem(.fixed(Constants.Frame.minGridItemWidth))
    ]
    
    var body: some View {
        LazyVGrid(columns: gridItemColumnConfig, spacing: 8) {
            ForEach(SnagItem.CodingKeys.allCases, id: \.self) { snagItemCategory in
                switch snagItemCategory {
                case .reference:
                    Text("\(reference)")
                case .details:
                    TextField(snagItemViewModel.localizedText(for: .enterValue),
                              text: $snagItemViewModel.details,
                              axis: .vertical)
                case .isCompleted:
                    Toggle("", isOn: $snagItemViewModel.isCompleted)
                        .labelsHidden()
                }
            }
        }
    }
}

5. 简化删除方法

支持批量删除,直接调用数组的remove(atOffsets:):

class SnagItemsViewModel: ObservableObject {
    @Published var snagItems: [SnagItemViewModel] = [SnagItemViewModel(withSnagItem: SnagItem())]
    
    func addSnagItem() {
        snagItems.append(SnagItemViewModel(withSnagItem: SnagItem()))
    }
    
    func deleteSnagItemRecord(at indexSet: IndexSet) {
        snagItems.remove(atOffsets: indexSet)
    }
}

修复原理

  • 唯一ID让SwiftUI能精准追踪每个列表项,删除后不会因为索引变化导致视图复用错误。
  • 修正状态修饰符后,行视图会正确响应ViewModel的更新,序号也会随数组结构变化重新计算。
  • 直接遍历Identifiable数组,彻底避免了索引依赖带来的UI与数据不匹配问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:27:04