Почему диапазон ForEach l oop SwiftUI не обновляется? - PullRequest
2 голосов
/ 19 марта 2020

Я пытаюсь отобразить список элементов в подпредставлении (ListView) внутри ContainerView всякий раз, когда значение массива этих элементов не равно nil. Каждый раз, когда массив обновляется, должен отображаться новый ListView, заменяющий предыдущий или удаляемый, если массив равен nil. Проблема в том, что ListView, кажется, отображается, удаляется и корректно обновляется, за исключением ForEach l oop. Он использует один и тот же диапазон каждый раз, даже если кажется, что отображается новый ListView.

Я создал простой пример, чтобы продемонстрировать проблему. Это ContainerView:

struct ContainerView: View {

    @State private var items: [Int]?
    private var listView: some View {
        items.map { ListView(items: $0) }
    }

    var body: some View {
        VStack {
            Button("Display new List") { self.items = self.randomItems() }
            listView
        }
    }

    private func randomItems() -> [Int] {...}
}

Это ListView:

struct ListView: View {

    private let randomInt: Int
    private let items: [Int]

    init(randomInt: Int = Int.random(in: 100 ..< 999), items: [Int]) {
        self.randomInt = randomInt
        self.items = items
        print("init | items: \(items)")
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("\(randomInt)")
            ForEach(0 ..< items.count) { index -> Text? in
                print("ForEach | index: \(index) items.count: \(self.items.count)")
                return self.items.count > index ? Text("\(self.items[index])") : nil
            }
        }
    }
}

При запуске и нажатии кнопки «Показать новый список» несколько раз будет напечатано что-то как это:

init | items: [0, 1, 2]
ForEach | index: 0 items.count: 3
ForEach | index: 1 items.count: 3
ForEach | index: 2 items.count: 3
init | items: [0, 1, 2, 3, 4, 5, 6, 7, 8]
ForEach | index: 0 items.count: 9
ForEach | index: 1 items.count: 9
ForEach | index: 2 items.count: 9
init | items: [0, 1, 2, 3, 4, 5]
ForEach | index: 0 items.count: 6
ForEach | index: 1 items.count: 6
ForEach | index: 2 items.count: 6

Итак, очевидно, создается новый ListView, и на устройстве вы также видите, что отображается правильный randomInt, но по какой-то причине ForEach использует один и тот же диапазон каждый время независимо от длины нового массива.

Почему диапазон не обновляется или как заставить его обновиться?

Ответы [ 2 ]

2 голосов
/ 19 марта 2020

Бэкэнд SwiftUI оптимизирует рендеринг представлений и не воссоздает представление, если считает, что оно равно, поэтому необходимо сообщить, что представление отличается.

Следующие исправления описывают проблему. (Проверено с Xcode 11.4beta3 / iOS 13.3)

private var listView: some View {
    items.map { ListView1(items: $0).id($0.count) }
}
2 голосов
/ 19 марта 2020
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ForEach where Data == Range<Int>, ID == Int, Content : View {

    /// Creates an instance that computes views on demand over a *constant*
    /// range.
    ///
    /// This instance only reads the initial value of `data` and so it does not
    /// need to identify views across updates.
    ///
    /// To compute views on demand over a dynamic range use
    /// `ForEach(_:id:content:)`.
    public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}

Для вычисления представлений по запросу в динамическом c диапазоне используйте ForEach(_:id:content:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...