Представление не перерисовывается во вложенном цикле ForEach
У меня есть следующий компонент, который отображает сетку полупрозрачных символов:
var body: some View {
VStack{
Text("\(self.settings.numRows) x \(self.settings.numColumns)")
ForEach(0..<self.settings.numRows){ i in
Spacer()
HStack{
ForEach(0..<self.settings.numColumns){ j in
Spacer()
// why do I get an error when I try to multiply i * j
self.getSymbol(index:j)
Spacer()
}
}
Spacer()
}
}
}
settings
это EnvironmentObject
Всякий раз, когда settings
обновляется текст во внешнем VStack
правильно обновляется. Однако остальная часть представления не обновляется (сетка имеет те же размеры, что и раньше). Почему это?
Второй вопрос: почему нет доступа к i
во внутреннем ForEach
-loop и передать его в качестве аргумента функции?
Я получаю ошибку на внешнем ForEach
-петля:
Не удалось вывести универсальный параметр "Данные"
2 ответа
TL;DR
Ваши потребности ForEach id: \.self
добавлен после вашего диапазона.
Объяснение
ForEach имеет несколько инициализаторов. Ты используешь
init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
где data
должно быть константой.
Если ваш диапазон может измениться (например, вы добавляете или удаляете элементы из массива, что изменит верхнюю границу), вам необходимо использовать
init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (Data.Element) -> Content)
Вы указываете путь к id
параметр, который однозначно определяет каждый элемент, ForEach
петли. В случаеRange<Int>
, элемент, который вы перебираете, является Int
указание индекса массива, который является уникальным. Поэтому вы можете просто использовать\.self
keypath, чтобы ForEach идентифицировал каждый элемент индекса по его собственному значению.
Вот как это выглядит на практике:
struct ContentView: View {
@State var array = [1, 2, 3]
var body: some View {
VStack {
Button("Add") {
self.array.append(self.array.last! + 1)
}
// this is the key part v--------v
ForEach(0..<array.count, id: \.self) { index in
Text("\(index): \(self.array[index])")
//Note: If you want more than one views here, you need a VStack or some container, or will throw errors
}
}
}
}
Если вы запустите это, вы увидите, что при нажатии кнопки добавления элементов в массив они появятся в VStack
автоматически. Если вы удалите "id: \.self
", вы увидите исходную ошибку:
`ForEach(_:content:)` should only be used for *constant* data.
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)`
and provide an explicit `id`!"
ForEach следует использовать только для постоянных данных. Таким образом, по определению он оценивается только один раз. Попробуйте обернуть его в список, и вы увидите, как регистрируются ошибки, например:
ForEach, Int, TupleView<(Spacer, HStack, Int, TupleView <(Spacer, Text, Spacer)>>>, Spacer)>> count (7)!= Его начальное количество (0).
ForEach(_:content:)
следует использовать только для постоянных данных. Вместо этого согласовывайте данные сIdentifiable
или используйтеForEach(_:id:content:)
и предоставить явноеid
!
Меня это тоже удивило, и я не смог найти никакой официальной документации об этом ограничении.
Что касается того, почему вы не можете получить доступ к i во внутреннем цикле ForEach, я думаю, что у вас, вероятно, есть вводящая в заблуждение ошибка компилятора, связанная с чем-то еще в коде, который отсутствует в ваших фрагментах. Он действительно скомпилировался для меня после того, как я дополнил недостающие части (Xcode 11.1, Mac OS 10.14.4).
Вот что я придумал, чтобы ваш ForEach прошел через что-то идентифицируемое:
struct SettingsElement: Identifiable {
var id: Int { value }
let value: Int
init(_ i: Int) { value = i }
}
class Settings: ObservableObject {
@Published var rows = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
@Published var columns = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
}
struct ContentView: View {
@EnvironmentObject var settings: Settings
func getSymbol(index: Int) -> Text { Text("\(index)") }
var body: some View {
VStack{
Text("\(self.settings.rows.count) x \(self.settings.columns.count)")
ForEach(self.settings.rows) { i in
VStack {
HStack {
ForEach(self.settings.columns) { j in
Text("\(i.value) \(j.value)")
}
}
}
}
}
}
}