SwiftUI View не обновляется на основе @ObservedObject - PullRequest
2 голосов
/ 31 марта 2020

В следующем коде наблюдаемый объект обновляется, а представление, которое его наблюдает, - нет. Есть идеи почему?

Код представляет на экране 10 цифр (0 .. <10) и кнопку. Всякий раз, когда нажимается кнопка, она случайным образом выбирает одно из 10 чисел и отображает ее видимость (видимая → скрытая или наоборот). </p>

Оператор печати показывает, что кнопка обновляет цифры, но представление не выполняет обновлять соответственно. Я знаю, что обновление значения в массиве не меняет само значение массива, поэтому я использую ручной objectWillChange.send() вызов. Я бы подумал, что должно вызвать обновление, но экран никогда не меняется.

Есть идеи? Я был бы заинтересован в решении, использующем NumberLine как класс или структуру, или вообще не использующем тип NumberLine, а вместо этого, просто используя переменную массива в структуре ContentView.

Screenshot

Вот код:

import SwiftUI

struct ContentView: View {

    @ObservedObject var numberLine = NumberLine()

    var body: some View {
        VStack {
            HStack {
                ForEach(0 ..< numberLine.visible.count) { number in
                    if self.numberLine.visible[number] {
                        Text(String(number)).font(.title).padding(5)
                    }
                }
            }.padding()

            Button(action: {
                let index = Int.random(in: 0 ..< self.numberLine.visible.count)
                self.numberLine.objectWillChange.send()
                self.numberLine.visible[index].toggle()
                print("\(index) now \(self.numberLine.visible[index] ? "shown" : "hidden")")
            }) {
                Text("Change")
            }.padding()
        }
    }
}

class NumberLine: ObservableObject {
    var visible: [Bool] = Array(repeatElement(true, count: 10))
}

Ответы [ 2 ]

4 голосов
/ 31 марта 2020

С @ObservedObject все хорошо ... давайте проанализируем ...

Итерация 1:

Возьмите ваш код без изменений и добавьте только следующую строку ( показывает в виде текста текущее состояние visible массива)

VStack { // << right below this
    Text("\(numberLine.visible.reduce(into: "") { $0 += $1 ? "Y" : "N"} )")

и запуска, и вы видите, что Text обновлен, так что наблюдаемый объект работает

demo

Итерация 2:

Удалите self.numberLine.objectWillChange.send() и используйте вместо этого шаблон по умолчанию @Published в представлении модели

class NumberLinex: ObservableObject {
    @Published var visible: [Bool] = Array(repeatElement(true, count: 10))
}

запустите и вы увидите это обновление работает так же, как в первом демонстрационном примере выше.

* Но ... основные числа в ForEach все еще не обновлены ... да, потому что проблема в ForEach - вы использовали конструктор с Range, который генерирует константу группу представления by-design (что задокументировано!).

!! Вот причина - вам нужны Dynami c ForEach, но для этой модели необходимо изменить.

Итерация 3 - Финал:

Dynami c ForEach Конструктор требует, чтобы итеративные элементы данных были идентифицируемыми, поэтому нам нужна struct как модель и обновленная модель представления.

Вот окончательное решение и демонстрация (протестировано с Xcode 11.4 / iOS 13.4)

demo2

struct ContentView: View {

    @ObservedObject var numberLine = NumberLine()

    var body: some View {
        VStack {
            HStack {
                ForEach(numberLine.visible, id: \.id) { number in
                    Group {
                        if number.visible {
                            Text(String(number.id)).font(.title).padding(5)
                        }
                    }
                }
            }.padding()

            Button("Change") {
                let index = Int.random(in: 0 ..< self.numberLine.visible.count)
                self.numberLine.visible[index].visible.toggle()
            }.padding()
        }
    }
}

class NumberLine: ObservableObject {
    @Published var visible: [NumberItem] = (0..<10).map { NumberItem(id: $0) }
}

struct NumberItem {
    let id: Int
    var visible = true
}
0 голосов
/ 01 апреля 2020

Используя ваше понимание, @Asperi, что проблема в ForEach, а не в функциональности @ObservableObject, вот небольшая модификация оригинала, которая делает свое дело:

import SwiftUI

struct ContentView: View {

    @ObservedObject var numberLine = NumberLine()

    var body: some View {
        VStack {
            HStack {
                ForEach(Array(0..<10).filter {numberLine.visible[$0]}, id: \.self) { number in
                    Text(String(number)).font(.title).padding(5)
                }
            }.padding()

            Button(action: {
                let index = Int.random(in: 0 ..< self.numberLine.visible.count)
                self.numberLine.visible[index].toggle()
            }) {
                Text("Change")
            }.padding()
        }
    }
}

class NumberLine: ObservableObject {
    @Published var visible: [Bool] = Array(repeatElement(true, count: 10))
}
...