SwiftUI View не обновляется при изменении ObservedObject - PullRequest
3 голосов
/ 15 января 2020

У меня есть представление содержимого, где я показываю список элементов, используя ForEach

@ObservedObject var homeVM : HomeViewModel

var body: some View {
    ScrollView (.horizontal, showsIndicators: false) {
        HStack(spacing: 10) {
            Spacer().frame(width:8)
            ForEach(homeVM.favoriteStores , id: \._id){ item in
                StoreRowOneView(storeVM: item)
            }
            Spacer().frame(width:8)
        }
    }.frame(height: 100)
}

И мой ObservableObject содержит

@Published var favoriteStores = [StoreViewModel]()

И StoreViewModel определяется следующим образом:

class StoreViewModel  : ObservableObject {
    @Published var store:ItemStore

    init(store:ItemStore) {
        self.store = store
    }

    var _id:String {
        return store._id!
    }
}

Если я заполняю массив напрямую, он работает нормально, и я могу видеть список магазинов, но если я заполнил массив после сетевого вызова (фоновое задание), представление не получило уведомления о том, что произошли изменения

 StoreApi().getFavedStores(userId: userId) { (response) in
            guard response != nil else {
                self.errorMsg = "Something wrong"
                self.error = true
                return
            }
            self.favoriteStores = (response)!.map(StoreViewModel.init)
            print("counnt favorite \(self.favoriteStores.count)")
        }

Но странная вещь такова: - если я добавлю текст, показывающий количество элементов массива, представление получит уведомление, и все будет работать нормально

Вот что я имею в виду:

    @ObservedObject var homeVM : HomeViewModel
    var body: some View {
        ScrollView (.horizontal, showsIndicators: false) {
            HStack(spacing: 10) {
                Spacer().frame(width:8)
                ForEach(homeVM.favoriteStores , id: \._id){ item in
                    StoreRowOneView(storeVM: item)
                }
                Text("\(self.homeVM.favoriteStores.count)").frame(width: 100, height: 100)
                Spacer().frame(width:8)
            }
        }.frame(height: 100)
    }

Любое объяснение этому?

Ответы [ 3 ]

1 голос
/ 04 марта 2020

Для горизонтального ScrollView вам нужно иметь фиксированную высоту или просто использовать условный ScrollView, который будет отображаться, когда someModels.count > 0

var body: some View {
   ScrollView(.horizontal) {
      HStack() {
         ForEach(someModels, id: \.someParameter1) { someModel in
            someView(someModel)
         }
      }.frame(height: someFixedHeight)
   }
}
// or
var body: some View {
   VStack {
      if someModels.count > 0 {
         ScrollView(.horizontal) {
            HStack() {
               ForEach(someModels, id: \.someParameter1) { someModel in
                  someView(someModel)
               }
            }
         }
      } else {
        EmptyView()
        // or other view
      }
   }
}

Правильно соответствует протоколу Hashable путем реализации

func hash(into hasher: inout Hasher) { 
   hasher.combine(someParameter1)
   hasher.combine(someParameter2)
}

и

static func == (lhs: SomeModel, rhs: SomeModel) -> Bool {
   lhs.someParameter1 == rls.someParameter1 && lhs.someParameter2 == rls.someParameter2

}

А также после предложения @Asperi об использовании правильного идентификатора из модели, который должен быть уникальным для каждого элемента.

ForEach(someModels, id: \.someParameter1) { someModel in
    someView(someModel)
}

Для ForEach нет другого способа уведомлять об обновлениях, отличных от уникальных идентификаторов.

Text(someModels.count) работал, потому что он уведомляет об изменениях по мере изменения количества.

1 голос
/ 08 марта 2020

Поделитесь некоторыми впечатлениями, которые могут помочь.

Когда я использовал ScrollView + ForEach + fetch, ничего не отображается, пока я не активирую представление refre sh другими способами. Обычно показывает, когда я предоставляю фиксированные данные без извлечения. Я решил это, указав ширину .infinity для ScrollView.

Моя теория заключается в том, что ForEach выполняет рендеринг повторно, когда выборка завершается, но ScrollView каким-то образом не регулирует свою ширину, чтобы соответствовать содержимому.

Это похоже в вашем случае. Предоставляя текст фиксированной ширины, ForEach может вычислить его ширину во время инициализации, а ScrollView может использовать ее для регулировки ширины. Но в случае выборки ForEach имеет начальную ширину содержимого 0, как и ScrollView. Когда выборка завершается, ScrollView не обновляет свою ширину соответственно.

Предоставление ScrollView некоторой начальной ширины должно решить вашу проблему.

0 голосов
/ 15 января 2020

Я бы начал с пересылки результатов в основную очередь, как показано ниже

DispatchQueue.main.async {
    self.favoriteStores = (response)!.map(StoreViewModel.init)
}

Далее ... ForEach отслеживает id параметры, поэтому вы должны быть уверены, что результаты имеют разные _id s в следующем, в противном случае ForEach не помечен как необходимый для обновления

ForEach(homeVM.favoriteStores , id: \._id){ item in
    StoreRowOneView(storeVM: item)
}
...