#swiftui #swiftui-list
#swiftui #swiftui-список
Вопрос:
Я хочу добавить a StateObject
в подвид строки, отображаемой в секционированном списке, чтобы выполнить некоторую работу и обновить подвид при @Published
обновлении свойства из StateObject
(например, загрузить изображение). Работа должна выполняться при появлении подвид ( .onAppear { }
).
Строка инициализируется один раз и появляется / исчезает при прокрутке. При добавлении элементов представление инициализируется снова (не обязательно перерисовывается, но это нормально, если ничего не изменилось). Аналогично, вложенное представление инициализируется один раз и появляется / исчезает при прокрутке. При добавлении элементов представление инициализируется снова (не обязательно перерисовывается, но это нормально, если ничего не изменилось).
Но StateObject
из подвид инициализируется и деинитируется несколько раз при прокрутке. Этого не должно быть, поскольку это a StateObject
, а не an ObservedObject
. Почему это происходит?
Когда я добавляю это StateObject
в строку вместо этого, StateObject
оно правильно инициализируется один раз (даже при добавлении новых элементов в список) и никогда не деинитируется.
Пример кода приведен ниже 👇🏻.
import SwiftUI
struct SectionTest: Identifiable {
var id: String { title }
var title: String
var items = [Item]()
}
struct Item: Identifiable {
var id: String { name }
var name: String
var section: String
}
class Loader: ObservableObject {
private var name: String
init(name: String) {
self.name = name
print("Loader • init (name)")
}
func load() {
print("Loader • load (name)")
}
deinit {
print("Loader • deinit (name)")
}
}
struct LoaderView: View {
@StateObject private var loader: Loader
let name: String
init(name: String) {
print("LoaderView • init (name)")
self.name = name
self._loader = StateObject(wrappedValue: Loader(name: "LoaderView ".appending(name)))
}
var body: some View {
Rectangle()
.frame(height: 200)
.onAppear { loader.load() }
.onAppear {
print("LoaderView • appear (name)")
}
.onDisappear {
print("LoaderView • disappear (name)")
}
}
}
struct RowView: View {
@StateObject private var loader: Loader
let name: String
init(name: String) {
print("RowView • init (name)")
self.name = name
self._loader = StateObject(wrappedValue: Loader(name: "RowView ".appending(name)))
}
var body: some View {
VStack(alignment: .leading) {
Text(name)
.foregroundColor(.red)
LoaderView(name: name)
}
.onAppear {
print("RowView • appear (name)")
}
.onDisappear {
print("RowView • disappear (name)")
}
}
}
struct StateObjectListView: View {
@State private var items = [
Item(name: UUID().uuidString, section: "First"),
Item(name: UUID().uuidString, section: "First"),
Item(name: UUID().uuidString, section: "First"),
Item(name: UUID().uuidString, section: "Second"),
Item(name: UUID().uuidString, section: "Second"),
Item(name: UUID().uuidString, section: "Third"),
Item(name: UUID().uuidString, section: "Third"),
Item(name: UUID().uuidString, section: "Third")
]
private var sections: [SectionTest] {
Dictionary(grouping: items) { $0.section }
.map { SectionTest(title: $0.key, items: $0.value.sorted { $0.name < $1.name }) }
.sorted { $0.title < $1.title }
}
var body: some View {
NavigationView {
VStack {
Button("Add") {
let section = ["First", "Second", "Third"].randomElement()!
items.append(Item(name: UUID().uuidString, section: section))
}
ScrollView {
LazyVStack {
ForEach(sections) { section in
Section(header: Text(section.title).font(.caption2)) {
ForEach(section.items) { item in
RowView(name: item.name)
}
}
}
}
}
}
}
}
}
Комментарии:
1. StateObject не подходит в этом сценарии, поскольку List повторно использует представления строк для данных из контейнера.
2. Итак, каков был бы правильный способ извлечения изображения для строки? На уровне строки вместо того, чтобы иметь это в подвид моей строки?