#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
vscategory
. Если у вас есть конкретный вопрос, который вы можете опубликовать в другом потоке, как вы сделали для этой конкретной проблемы.
Ответ №2:
Причина, по которой вы получаете эту ошибку, заключается в том, что структуры неизменяемы. Вы должны использовать метод, помеченный как «мутирующий» внутри желаемой структуры. Что-то вроде
if let matchingIndex = category.items.firstIndex(where: { $0.id == item.id }) {
category.items[matchingIndex].toggleItem()
}
и внутри вашей структуры:
mutating func toggleItem() {
self.isChecked.toggle()
}
Но я бы рекомендовал вам вместо этого использовать @State, потому что то, что вы пытаетесь сделать, напрямую связано с тем, как вы представляете свое представление. И позже, когда пользователь захочет что-то сделать с этим выбором, вы отправляете эти данные в свою модель