Проблема SwiftUI .onTapGesture в TableView с разделами и строками

#swift #swiftui

#swift #swiftui

Вопрос:

У меня есть две модели:

 struct Category: Identifiable {
    
    var id = UUID()
    var title: String
    var number: Int
    var items: [ChecklistItem]
}
  

и:

 struct ChecklistItem: Identifiable {
  
  let id = UUID()
  var name: String
  var isChecked = false
}
  

с:

    class Checklist: ObservableObject {
    
      @Published var items = [Category]()

  func deleteListItem(whichElement: IndexSet) {
    items.remove(atOffsets: whichElement)
  }

  func moveListItem(whichElement: IndexSet, destination: Int) {
    items.move(fromOffsets: whichElement, toOffset: destination)
  }


}
  

Я пытаюсь реализовать нажатие на строку, чтобы проверить и снять флажок с элемента списка в TableView с разделами и строками, но я не могу понять, как это можно освободить. Мой код:

 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 {
                        if let matchingIndex =
                            category.items.firstIndex(where: { $0.id == item.id }) {
                            category.items[matchingIndex].isChecked.toggle()
                      }
                    }

                }
            }
        }
        .onDelete(perform: checklist.deleteListItem)
        .onMove(perform: checklist.moveListItem)
      }
      .navigationBarItems(
        leading: Button(action: { self.newChecklistItemViewIsVisible = true }) {
          HStack {
            Image(systemName: "plus.circle.fill")
            Text("Add")
          }
        },
        trailing: EditButton()
      )
      .navigationBarTitle("List")
    }
    .sheet(isPresented: $newChecklistItemViewIsVisible) {
      NewChecklistItemView(checklist: self.checklist)
    }
  }
}
  

Я получаю ошибку с этим кодом в строке с category.items[matchingIndex].IsChecked .переключить ():

 Cannot use mutating member on immutable value: 'category' is a 'let' constant
  

Как я могу добраться до ChecklistItem и заставить его проверять и снимать флажок при нажатии.

Ответ №1:

 import SwiftUI
//Change to class and add NSObject structs are immutable
class Category: NSObject, Identifiable {
    
    let id = UUID()
    var title: String
    var number: Int
    var items: [ChecklistItem]
    //Now you need an init
    init(title: String , number: Int, items: [ChecklistItem]) {
        self.title = title
        self.number = number
        self.items = items
    }
}
//Change to class and add NSObject structs are immutable
class ChecklistItem: Identifiable {
    
    let id = UUID()
    var name: String
    var isChecked: Bool = false
    //Now you need an init
    init(name: String) {
        self.name = name
    }
}
class Checklist: ObservableObject {
    @Published var items = [Category]()
}
struct ChecklistView: View {
    //Can be an @EnvironmentObject if the @ObservedObject comes from a higher View
    @ObservedObject var checklist: 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
                            Button(action: {
                                print(item.isChecked.description)
                                item.isChecked.toggle()
                                //Something to trigger the view to refresh will not be necessary if using something like @FetchRequest or after you somehow notify `checklist.items` that there is a change
                                checklist.objectWillChange.send()
                            }) {
                                    HStack {
                                        Text(item.name)
                                        Spacer()
                                        Text(item.isChecked ? "✅" : "🔲")
                                }//HStack
                                    //White is incompatible with Text Color in Dark Mode
                                    .background(Color.gray)
                            }//Button
                        }//ForEach
                    }//Section
                }//ForEach
                //Methods not provided
                //.onDelete(perform: checklist.deleteListItem)
                //.onMove(perform: checklist.moveListItem)
            }
            .navigationBarItems(
                leading: Button(action: {
                    self.newChecklistItemViewIsVisible = true
                    //Code to Add Samples
                    checklist.items.append(Category(title: "Test", number: Int.random(in: 0...100), items: [ChecklistItem(name: "Test")]))
                }) {
                    HStack {
                        Image(systemName: "plus.circle.fill")
                        Text("Add")
                    }
                },
                trailing: EditButton()
            )
            .navigationBarTitle("List")
        }
        .sheet(isPresented: $newChecklistItemViewIsVisible) {
            //Pass as an @EnvironmentObject
            NewChecklistItemView().environmentObject(checklist)
        }
    }
}

struct NewChecklistItemView: View {
    
    @EnvironmentObject var checklist: Checklist
    
    
    var body: some View {
        Text(checklist.items.count.description)
    }
}
struct ChecklistView_Previews: PreviewProvider {
    static var previews: some View {
        //When the @ObservedObject comes from a higher View remove comment below
        ChecklistView()//.environmentObject(Checklist())
    }
}
  

Комментарии:

1. Спасибо за ваш ответ. Можете ли вы также предложить, как я могу объединить методы перемещения и удаления для перемещения и удаления элемента контрольного списка. Я добавил их в свой список кода.

2. Пример кода, который Xcode 12 генерирует для нового проекта, поможет вам начать с этого. Большой разницей будет поиск правильного элемента. Я бы предложил переместить методы во 2-й ForEach , чтобы ваш indexSet ссылался на item vs category . Если у вас есть конкретный вопрос, который вы можете опубликовать в другом потоке, как вы сделали для этой конкретной проблемы.

Ответ №2:

Причина, по которой вы получаете эту ошибку, заключается в том, что структуры неизменяемы. Вы должны использовать метод, помеченный как «мутирующий» внутри желаемой структуры. Что-то вроде

 if let matchingIndex = category.items.firstIndex(where: { $0.id == item.id }) {
    category.items[matchingIndex].toggleItem()
}
  

и внутри вашей структуры:

 mutating func toggleItem() {
   self.isChecked.toggle()
}
  

Но я бы рекомендовал вам вместо этого использовать @State, потому что то, что вы пытаетесь сделать, напрямую связано с тем, как вы представляете свое представление. И позже, когда пользователь захочет что-то сделать с этим выбором, вы отправляете эти данные в свою модель