ObervableObject запускается несколько раз и не обновляет мое представление

у меня такая структура

contentView {
    navigationView{
     foreach {
        NavigationLink(ViewA(id: id))
     }
    }
}

/// где View A содержит триггер запроса в представлении Appear

struct ViewA: View {

    @State var filterString: String = ""

    var id: String!
    @ObservedObject var model: ListObj = ListObj()

    init(id: String) {
        self.id = id
    }

    var body: some View {
        VStack {
            SearchBarView(searchText: $filterString)
            List {
                ForEach(model.items.filter({ filterString.isEmpty || $0.id.contains(filterString) || $0.name.contains(filterString)  }), id: \.id) { item in
                    NavigationLink(destination: ViewB(id: item.id)) {
                        VStack {
                            Text("\(item.name) ")
                        }
                    }
                }
            }

        }
        .onAppear {
            self.model.getListObj(id: self.id) //api request, fill data and call objectWillChange.send()
        }

    }

}

}

ViewB имеет тот же код, что и ViewB, идентификатор получения, хранилище и API запроса для сбора данных.

Но список viewB не обновляется. я также заметил ViewB's

@ObservedObject var model: model = model()

был создан несколько раз

Отлаживая, я обнаружил, что каждый объект navigationLink создает его пункт назначения еще до того, как он сработал. обычно это не проблема,

но в моем случае я чувствую, что модель ViewB создается 2 раза, и мой onApear вызывает неправильный, причина, по которой self.objectWillChange.send () не обновляет мое представление


person Fernand    schedule 18.02.2020    source источник
comment
Фернан, все твои проблемы связаны с тем, что твоя модель зависит от того факта, что если какое-то View появится ... даже если ты наконец найдешь какое-то решение, оно будет очень хрупким! измените свой подход как можно скорее.   -  person user3441734    schedule 18.02.2020


Ответы (2)


Здесь есть две проблемы:

  1. SwiftUI использует типы значений, которые инициализируются снова и снова при каждом проходе через body.
  2. В отношении №1 NavigationLink не ленив.

#1

Новый ListObj создается каждый раз, когда вы вызываете ViewA.init(...). ObservedObject не работает так же, как @State, где SwiftUI тщательно отслеживает его на протяжении всего жизненного цикла на экране. SwiftUI предполагает, что конечное владение @ObservedObject существует на некотором уровне выше View, в котором он используется.

Другими словами, вам почти всегда следует избегать таких вещей, как @ObservedObject var myObject = MyObservableObject().

(Обратите внимание, даже если вы сделали @State var model = ListObj(), он будет создаваться каждый раз. Но поскольку это @State, SwiftUI заменит новый экземпляр оригиналом до вызова body.)

#2

Вдобавок NavigationLink не ленив. Каждый раз, когда вы создаете экземпляр этого NavigationLink, вы передаете новый экземпляр ViewA, который создает экземпляр вашего ListObj.

Итак, для начала, вы можете сделать LazyView, чтобы отложить создание экземпляра до тех пор, пока NavigationLink.destination.body не будет вызван:

// Use this to delay instantiation when using `NavigationLink`, etc...
struct LazyView<Content: View>: View {
    var content: () -> Content
    var body: some View {
        self.content()
    }
}

Теперь вы можете выполнить NavigationLink(destination: LazyView { ViewA() }), и создание экземпляра ViewA будет отложено до фактического отображения destination.

Простое использование LazyView решит вашу текущую проблему при условии, что это вид сверху в иерархии, например, когда вы вставляете его в NavigationView или представляете.

Однако здесь появляется комментарий @ user3441734. Что вам действительно нужно сделать, так это сохранить право собственности на model где-то за пределами вашего View из-за того, что было объяснено в №1.

person arsenius    schedule 19.02.2020
comment
Большое спасибо, я видел model = model () во многих уроках, не ожидал этого. - person Fernand; 19.02.2020

Если ваш @ObservedObject инициализируется несколько раз, это связано с тем, что владелец объекта обновляется и воссоздается каждый раз при изменении его состояния. Попробуйте использовать @StateObject, если ваше приложение iOS 14 и выше. Это предотвращает воссоздание объекта при обновлении представления. https://developer.apple.com/documentation/swiftui/stateobject.

Когда представление создает свой собственный экземпляр @ObservedObject, он воссоздается каждый раз, когда представление отбрасывается и перерисовывается. Напротив, переменная @State сохранит свое значение при перерисовке представления. @StateObject - это комбинация @ObservedObject и @State - экземпляр ViewModel будет сохранен и повторно использован даже после того, как представление будет отброшено и перерисовано.

В чем разница между ObservedObject и StateObject в SwiftUI

person ada10086    schedule 03.03.2021