У меня есть массив объектов («вещей») какой-то сторонней библиотеки, которые я хочу отобразить в представлении SwiftUI. Эти объекты "Вещи" можно идентифицировать и хэшировать по идентификатору, но при перезагрузке нового набора вещей их содержимое могло измениться (скажем, "статус" или "текст" этой Вещи, хотя это снова та же Вещь. ). Таким образом, идентификатор остается прежним, но содержание вещи может измениться. Проблема в том, что SwiftUI не обновляет пользовательский интерфейс, когда я получаю новый массив вещей. Я предполагаю, что это потому, что Вещи снова «идентифицируются» как одни и те же Вещи по их идентификатору. Я не могу изменить Вещь, потому что она из сторонней библиотеки.
Теперь я просто обернул Вещь в другой класс, и вдруг она заработала! Но я хочу понять , почему это работает, и если это определенное поведение, а не просто совпадение или «удача».
Кто-нибудь может объяснить, что здесь происходит за кулисами? В частности, в чем основное различие между DirectThingView и WrappedThingView, из-за которого SwiftUI обновляет пользовательский интерфейс для последнего, а не для первого?
Или есть какие-то предложения, как решить эту проблему лучше?
Вот пример кода, который показывает все: он отображает вещи в двух столбцах; первый столбец использует DirectThingView, а второй столбец использует WrappedThingView. Если вы нажмете кнопку «Обновить», массив things заполнится измененными объектами, но только пользовательский интерфейс правого столбца правильно обновляет значения; левый столбец всегда остается в исходном состоянии.
//
// TestView.swift
//
// Created by Manfred Schwind on 10.07.20.
// Copyright © 2020 mani.de. All rights reserved.
//
import SwiftUI
// The main model contains an array of "Things",
// every Thing has an id and contains a text.
// For testing purposes, every other time a Thing gets instantiated, its text contains either "A" or "B".
// Problem here: the "current" text of a Thing with the same id can change, when Things are reloaded.
class TestViewModel: ObservableObject {
@Published var things = [Thing(id: 1), Thing(id: 2), Thing(id: 3)]
}
struct TestView: View {
@ObservedObject var viewModel = TestViewModel()
var body: some View {
VStack (spacing: 30) {
HStack (spacing: 40) {
// We try to display the current Thing array in the UI
// The views in the first column directly store the Thing:
// Problem here: the UI does not update for changed Things ...
VStack {
Text("Direct")
ForEach(self.viewModel.things, id: \.self) { thing in
DirectThingView(viewModel: thing)
}
}
// The views in the second column store the Thin wrapped into another class:
// In this case, the problem magically went away!
VStack {
Text("Wrapped")
ForEach(self.viewModel.things, id: \.self) { thing in
WrappedThingView(viewModel: thing)
}
}
}
Button(action: {
// change the Thing array in the TestViewModel, this causes the UI to update:
self.viewModel.things = [Thing(id: 1), Thing(id: 2), Thing(id: 3)]
}) {
Text("Reload")
}
}
}
}
struct DirectThingView: View {
// first approach just stores the passed Thing directly internally:
private let viewModel: Thing
init(viewModel: Thing) {
self.viewModel = viewModel
}
var body: some View {
Text(self.viewModel.text)
}
}
struct WrappedThingView: View {
// second approach stores the passed Thing wrapped into another Object internally:
private let viewModel: WrappedThing
init(viewModel: Thing) {
// take the Thing like in the first approach, but internally store it wrapped:
self.viewModel = WrappedThing(childModel: viewModel)
}
var body: some View {
Text(self.viewModel.childModel.text)
}
// If type of WrappedThing is changed from class to struct, then the problem returns!
private class WrappedThing {
let childModel: Thing
init(childModel: Thing) {
self.childModel = childModel
}
}
}
// Thing has do be Identifiable and Hashable for ForEach to work properly:
class Thing: Identifiable, Hashable {
// Identifiable:
let id: Int
// The text contains either "A" or "B", in alternating order on every new Thing instantiation
var text: String
init(id: Int) {
self.id = id
struct Holder {
static var flip: Bool = false
}
self.text = Holder.flip ? "B" : "A"
Holder.flip = !Holder.flip
}
// Hashable:
public func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
// Equatable (part of Hashable):
public static func == (lhs: Thing, rhs: Thing) -> Bool {
return lhs.id == rhs.id
}
}
#if DEBUG
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
#endif
введите описание изображения здесь
Заранее большое спасибо!