SwiftUI — объект состояния в подвид строки инициализируется и деинитируется несколько раз при прокрутке

#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. Итак, каков был бы правильный способ извлечения изображения для строки? На уровне строки вместо того, чтобы иметь это в подвид моей строки?