Как заставить представление SwiftUI обновляться при изменении вложенного свойства класса модели? - PullRequest
1 голос
/ 19 июня 2020

Я создал тривиальный проект, чтобы попытаться лучше понять это. Код ниже.

У меня есть источник данных (DataSource), который содержит @Published массив MyObject элементов. MyObject содержит одну строку. Нажатие кнопки в пользовательском интерфейсе вызывает немедленное обновление одного из экземпляров MyObject, плюс включает таймер для обновления второго экземпляра через несколько секунд.

Если MyObject - это структура, все работает как Я полагаю, это должно быть. Но если MyObject - это класс, тогда refre sh не срабатывает.

Я ожидаю, что изменение значения структуры приведет к размещению измененного экземпляра в массиве, что приведет к возникновению цепочки обновления. Однако, если MyObject является классом, при изменении строки в ссылочном типе в массиве остается тот же экземпляр. Array не понимает, что произошло изменение, поэтому не упоминает об этом в моем DataSource. Обновление пользовательского интерфейса не происходит.

Итак, возникает вопрос - что нужно сделать, чтобы пользовательский интерфейс обновлялся при изменении свойства класса MyObject? Я попытался сделать MyObject an ObservableObject и добавить некоторые didchange.send() инструкции, но все безуспешно (я считаю, что сейчас они излишни в любом случае).

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

import Foundation
import SwiftUI

struct ContentView: View
{
    @ObservedObject var source = DataSource()

    var body: some View
    {
        VStack
        {
            ForEach(0..<5)
            {i in
                HelloView(displayedString: self.source.results[i].label)
            }
            Button(action: {self.source.change()})
            {
                Text("Change me")
            }
        }
    }
}

struct HelloView: View
{
    var displayedString: String

    var body: some View
    {
        Text("\(displayedString)")
    }
}

class MyObject // Works if declared as a Struct
{
    init(label: String)
    {
        self.label = label
    }

    var label: String
}

class DataSource: ObservableObject
{
    @Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)

    func change()
    {
        print("I've changed")
        results[3].label = "sooner"
        _ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: {_ in self.results[1].label = "Or later"})
    }
}

struct ContentView_Previews: PreviewProvider
{
    static var previews: some View
    {
        ContentView()
    }
}

1 Ответ

0 голосов
/ 19 июня 2020

Когда MyObject является типом класса, results содержит ссылки, поэтому, когда вы меняете свойство любого экземпляра внутри results, ссылка этого экземпляра не изменяется, поэтому results не изменяется, поэтому ничего не публикуется и пользовательский интерфейс не обновляется.

В таком случае решение состоит в том, чтобы принудительно принудительно опубликовать sh при выполнении любого изменения внутренней модели

class DataSource: ObservableObject
{
    @Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)

    func change()
    {
        print("I've changed")
        results[3].label = "sooner"
        self.objectWillChange.send()    // << here !!

        _ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) {[weak self] _ in
            self?.results[1].label = "Or later"
            self?.objectWillChange.send()            // << here !!
        }
    }
}
...