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

SwiftUI带Section的List无法实现删除与移动行功能的解决方案咨询

Fixing Delete & Move for Sectioned Lists in SwiftUI

You’re spot-on about the root issue: since Category is a value type (struct), its standard methods can’t modify its own properties by default, and your original approach to calling deleteListItem/moveListItem directly won’t trigger the view updates needed from your ObservableObject. Let’s fix this step by step:

Step 1: Make Category Methods Mutating

First, update the Category struct to allow modifying its items array by marking the methods as mutating:

struct Category: Identifiable {
    var id = UUID()
    var title: String
    // Note: I'd recommend removing the `number` property—using `id` to identify categories is far more reliable (indexes shift when items are deleted/moved)
    var number: Int
    var items: [ChecklistItem]
    
    mutating func deleteListItem(whichElement: IndexSet) {
        items.remove(atOffsets: whichElement)
    }
    
    mutating func moveListItem(whichElement: IndexSet, destination: Int) {
        items.move(fromOffsets: whichElement, toOffset: destination)
    }
    
    // Add a mutating method to toggle item check state too
    mutating func toggleItemChecked(itemId: UUID) {
        guard let index = items.firstIndex(where: { $0.id == itemId }) else { return }
        items[index].isChecked.toggle()
    }
}

Step 2: Add Data Modification Logic to Your ObservableObject

Since Category is a value type, modifying it requires replacing the entire instance in your Checklist array to trigger @Published updates. Add these methods to your Checklist class:

class Checklist: ObservableObject {
    @Published var items = [Category]()
    
    // Delete an item from a specific category
    func deleteItem(in categoryId: UUID, offsets: IndexSet) {
        guard let categoryIndex = items.firstIndex(where: { $0.id == categoryId }) else { return }
        var updatedCategory = items[categoryIndex]
        updatedCategory.deleteListItem(whichElement: offsets)
        items[categoryIndex] = updatedCategory
    }
    
    // Move an item within a specific category
    func moveItem(in categoryId: UUID, offsets: IndexSet, destination: Int) {
        guard let categoryIndex = items.firstIndex(where: { $0.id == categoryId }) else { return }
        var updatedCategory = items[categoryIndex]
        updatedCategory.moveListItem(whichElement: offsets, destination: destination)
        items[categoryIndex] = updatedCategory
    }
    
    // Toggle check state for an item
    func toggleItemChecked(in categoryId: UUID, itemId: UUID) {
        guard let categoryIndex = items.firstIndex(where: { $0.id == categoryId }) else { return }
        var updatedCategory = items[categoryIndex]
        updatedCategory.toggleItemChecked(itemId: itemId)
        items[categoryIndex] = updatedCategory
    }
}

Step 3: Update the View to Use the New Methods

Modify your ChecklistView to call the Checklist class methods instead of directly accessing Category methods, and fix the tap gesture logic:

struct ChecklistView: View {
    @EnvironmentObject var checklist: Checklist
    @State var newChecklistItemViewIsVisible = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(checklist.items) { category in
                    Section(header: Text(category.title)) {
                        ForEach(category.items) { item in
                            HStack {
                                Text(item.name)
                                Spacer()
                                Text(item.isChecked ? "✅" : "")
                            }
                            .background(Color.white)
                            .onTapGesture {
                                checklist.toggleItemChecked(in: category.id, itemId: item.id)
                            }
                        }
                        .onDelete { offsets in
                            checklist.deleteItem(in: category.id, offsets: offsets)
                        }
                        .onMove { offsets, destination in
                            checklist.moveItem(in: category.id, offsets: offsets, destination: destination)
                        }
                    }
                }
            }
            .navigationBarItems(
                leading: Button(action: { newChecklistItemViewIsVisible = true }) {
                    HStack {
                        Image(systemName: "plus.circle.fill")
                        Text("Add")
                    }
                },
                trailing: EditButton()
            )
            .navigationBarTitle("List")
        }
        .sheet(isPresented: $newChecklistItemViewIsVisible) {
            NewChecklistItemView(checklist: checklist)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ChecklistView()
            .environmentObject(Checklist()) // Don't forget to inject the environment object for previews
    }
}

Key Notes

  • Using id instead of number to identify categories avoids bugs when the order of categories changes (e.g., if you add category reordering later).
  • By modifying the Category instance and replacing it in the Checklist array, you ensure @Published triggers a view refresh, so your UI stays in sync with the data.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 16:32:28