I want to add a StateObject to a subview of a row displayed in a sectioned list in order to perform some work and update the subview when a @Published property from the StateObject is updated (e.g. download an image). The work should be performed when the subview appears (.onAppear { }).
The row is initialised once, and appears / disappears when scrolling. When adding items, the view is initialised again (not necessarily redrawn but that's normal if nothing changed). Similarly, the subview is initialised once, and appears / disappears when scrolling. When adding items, the view is initialised again (not necessarily redrawn but that's normal if nothing changed).
But the StateObject from the subview is initialised and deinit multiple times when I scroll. It should not as it's a StateObject and not an ObservedObject. Why is that happening?
When I add this StateObject to the row instead, the StateObject is correctly initialised once (even when adding new items to the list) and is never deinit.
Sample code provided below .
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)
}
}
}
}
}
}
}
}
}

