SwiftUI带Section的List无法实现删除与移动行功能的解决方案咨询
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
idinstead ofnumberto identify categories avoids bugs when the order of categories changes (e.g., if you add category reordering later). - By modifying the
Categoryinstance and replacing it in theChecklistarray, you ensure@Publishedtriggers a view refresh, so your UI stays in sync with the data.
内容的提问来源于stack exchange,提问作者Parcker

