#swift #swiftui
Вопрос:
Очень новичок в SwiftUI amp; Swift и пытаюсь разобраться в привязках.
В частности, у меня есть цикл ForEach, для Tasks
которого отображается новое TaskRow
значение Tasks
. В этой строке задачи есть два действия:
- Кнопка для удаления задачи, отображаемая в соответствующей строке
- Переключатель для изменения состояния задачи с «завершено» на «не завершено»
Без переключателя всякий раз, когда я удаляю задачу, представление обновляется просто отлично. Но при наличии переключателя удаление задачи вызывает:
Thread 1: Fatal error: Index out of range
ошибка.
Я предполагаю, что это связано с $isSet
привязкой к данным self.$, но я не знаю, как еще передать эти данные таким образом, чтобы переключатель обновился. Код ниже, мысли глубоко ценятся:
Список задач.свифт
import SwiftUI
struct TaskList: View {
@ObservedObject private var data = TaskData()
@State private var showFinished: Bool = false
var visibleTasks: [Task] {
data.tasks.filter { task in
showFinished || !task.finished
}
}
var body: some View {
VStack {
Toggle(isOn: $showFinished) {
Text("Show Finished")
}
ForEach(data.tasks.indices, id: .self) { index in
TaskRow(
isSet: self.$data.tasks[index].finished,
task: self.data.tasks[index],
onChange: { self.data.save() },
onDelete: { self.deleteTask(task_index: index)})
Divider()
}
AddTask(data: data)
}
.padding()
.onAppear {
data.load()
}
}
func deleteTask(task_index: Int) {
self.data.tasks.remove(at: self.data.tasks[task_index].id)
}
}
ТаскРоу.свифт
импорт SwiftUI
struct TaskRow: View {
@Binding var isSet: Bool
@State var task: Task
var onChange: () -> ()
var onDelete: () -> ()
var body: some View {
HStack() {
Toggle("", isOn: $isSet)
.onChange(of: isSet) { _isOn in
onChange()
}
Text(task.name)
Spacer()
Button(action: onDelete) {
Image(systemName: "minus.circle")
}
}
.scaledToFit()
}
}
Комментарии:
1. Вам следует посмотреть видео демистификации SwiftUI с WWDC 2021. У тебя есть несколько маленьких ошибок.
2. Привет, ПланкТон, не могли бы вы, пожалуйста, сделать данные не приватным var, а var? Затем проверьте, не обновляются ли по-прежнему данные.задачи.индексы.
3. @MacUserT почему ты думаешь, что это может что-то изменить?
4. Забудь об этом, это не так. Тем не менее, наблюдаемый объект-это var, а не частный var.
5. @MacUserT он может быть легко приватным, если вам не нужно создавать дополнительный параметр инициализатора для этого var.
Ответ №1:
В iOS 15 вы можете сделать следующее:
ForEach(Array($data.tasks.enumerated()), id: .element.wrappedValue) { (index, $task) in
TaskRow(
isSet: $task.finished,
task: task,
onChange: { self.data.save() },
onDelete: { self.deleteTask(task_index: index) }
)
Divider()
}
Для iOS 14 вы получаете предупреждение ниже (и другие).:
Соответствие «Привязки» к «Последовательности» доступно только в iOS 15.0 или новее
Ответ №2:
Это происходит, когда вы перечисляете по indexes
, а затем получаете привязки по индексу внутри ForEach
. Это небезопасно из-за неправильных идентификаторов во время обновления состояния. Я создан ForEachIndexed
для таких случаев:
struct ForEachIndexed<Data: RandomAccessCollection, RowContent: View, ID: Hashable>: View, DynamicViewContent where Data.Index: Hashable {
var data: [(Data.Index, Data.Element)] {
forEach.data
}
let forEach: ForEach<[(Data.Index, Data.Element)], ID, RowContent>
init(_ data: Binding<Data>,
@ViewBuilder rowContent: @escaping (Data.Index, Binding<Data.Element>) -> RowContent
) where
Data.Element: Identifiable,
Data.Element.ID == ID,
Data: MutableCollection {
forEach = ForEach(
Array(zip(data.wrappedValue.indices, data.wrappedValue)),
id: .1.id
) { i, _ in
rowContent(i, Binding(get: {
data.wrappedValue[i]
}, set: {
data.wrappedValue[i] = $0
}))
}
}
var body: some View {
forEach
}
}
Использование:
ForEachIndexed($data.tasks) { index, taskBinding in
TaskRow(
isSet: taskBinding.finished,
task: taskBinding.wrappedValue,
onChange: { self.data.save() },
onDelete: { self.deleteTask(task_index: index)})
Divider()
}