Логическое значение внутри массива ObservableObject не запускает повторный рендеринг

В моем текущем проекте мне трудно заставить SwiftUI распознать изменение во вложенном ObservableObject, здесь демонстрационный/отладочный код.


import SwiftUI
import SwiftData

class Collection: ObservableObject, Identifiable, Hashable {
    
    static func == (lhs: Collection, rhs: Collection) -> Bool {
        return lhs.title == rhs.title && lhs.items == rhs.items
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
    }
    var id = UUID()
    @Published var title : String = ""
    @Published var items : [String] = []
    @Published var isCollapsed: Bool = true
    
    init(title: String, items: [String]) {
        self.title = title
        self.items = items
    }
}



class GridViewModel: ObservableObject {
    @Published var collections : [Collection] = []
}


struct ContentView: View {
    
//    @EnvironmentObject var gridViewModel : GridViewModel
    @StateObject var  gridViewModel = GridViewModel()

    var body: some View {
        VStack {
            Text("Toggle Visibility with observed Objects")
            Button("Add Collection", action: {
                addCollection()
            })
            
            ScrollView {
                ForEach(gridViewModel.collections, id: \.self) { coll in
                    Button("toggle visibility for:", action: {
                        coll.isCollapsed.toggle()     // does not work
                    })
                    Button("edit", action: {
                        changeItem()
                    })
                    Text(coll.title)
                    
                    if coll.isCollapsed {
                        ForEach(coll.items, id: \.self) { item in
                            Text(item)
                        }
                    }
                    Spacer(minLength: 20)
                }
            }
            
        }
        .onAppear {
            let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
            let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
            let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])

            gridViewModel.collections = [collection1, collection2, collection3]
        }
    }
    
    func addCollection() {
        let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
        gridViewModel.collections.append(collectionNew)
        gridViewModel.objectWillChange.send()   // does work
    }
    
    func changeItem() {
        gridViewModel.collections[0].items[0] = "---edit------edit------edit---"
        print("done: changed to: \(gridViewModel.collections[0].items[0])")
        gridViewModel.objectWillChange.send()   // works
    }



}

#Preview {
    var gridViewModel = GridViewModel()
    return ContentView().environmentObject(gridViewModel)
    #if os(macOS)
        .frame(width: 700, height: 500)
    #endif

}

Как видите, изменение на первом уровне, который находится внутри GridViewModel, вызывает повторный рендеринг, но изменение внутри Collection — нет.

мне нужно, чтобы SwiftUI распознал, что логическое значение isCollapsed изменилось, и пользовательский интерфейс необходимо обновить, есть какие-нибудь предложения, как заставить это работать?

Те же старые вещи, вложение ObservableObject, как вы (Коллекция внутри GridViewModel), не является хорошей идеей. При использовании вложенных ObservableObjects внутренний объект не вызывает обновление внешнего представления. Вам нужно сделать это вручную самостоятельно. Как насчет использования более современного Observable.

workingdog support Ukraine 24.08.2024 08:41

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

blendstylez 24.08.2024 10:43

Изменить коллекцию на структуру

lorem ipsum 24.08.2024 11:48

спасибо @workingdogsupportUkraine, этот макрос Observable действительно изменил ситуацию! я не ожидал этого! Пожалуйста, опубликуйте ответ, чтобы я мог отметить его как правильный - и большое спасибо!!

blendstylez 24.08.2024 17:12
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
5
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте использовать gridViewModel.objectWillChange.send(), как показано в этом коде.

Button("toggle visibility for:", action: {
    gridViewModel.objectWillChange.send() // <--- here
    coll.isCollapsed.toggle()
})

РЕДАКТИРОВАТЬ-1:

Как уже упоминалось, вы также можете использовать struct Collection включить в старый стиль class GridViewModel: ObservableObject, например:

struct Collection: Identifiable, Hashable {  //<--- here
    let id = UUID()
    
    var title: String = ""
    var items: [String] = []
    var isCollapsed: Bool = true
}

class GridViewModel: ObservableObject {
    @Published var collections : [Collection] = []
}

struct ContentView: View {
    //    @EnvironmentObject var gridViewModel : GridViewModel
    @StateObject private var gridViewModel = GridViewModel()
    
    var body: some View {
        VStack {
            Text("Toggle Visibility with observed Objects")
            Button("Add Collection") {
                addCollection()
            }
            
            ScrollView {
                ForEach($gridViewModel.collections) { $coll in  //<--- here $
                    Button("toggle visibility for:", action: {
                        coll.isCollapsed.toggle()
                    })
                    Button("edit", action: {
                        changeItem()
                    })
                    Text(coll.title)
                    
                    if coll.isCollapsed {
                        ForEach(coll.items, id: \.self) { item in
                            Text(item)
                        }
                    }
                    Spacer(minLength: 20)
                }
            }
        }
        .onAppear {
            let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
            let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
            let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])
            
            gridViewModel.collections = [collection1, collection2, collection3]
        }
    }
    
    func addCollection() {
        let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
        gridViewModel.collections.append(collectionNew) 
    }
    
    func changeItem() {
        gridViewModel.collections[0].items[0] = "---edit------edit------edit---" 
        print("done: changed to: \(gridViewModel.collections[0].items[0])")
    }
    
}

РЕДАКТИРОВАТЬ-2:

Как уже упоминалось в моих комментариях, я использую рекомендованные более современные Observable framework

@Observable class Collection: Identifiable {  // <--- here
    let id = UUID()
    
    var title: String
    var items: [String]
    var isCollapsed: Bool
    
    init(title: String, items: [String], isCollapsed: Bool = true) {
        self.title = title
        self.items = items
        self.isCollapsed = isCollapsed
    }
}

@Observable class GridViewModel {  // <--- here
    var collections : [Collection] = []
}

struct ContentView: View {
    // when passing from parent ...  .environment(gridViewModel)
    // @Environment(GridViewModel.self) private var gridViewModel
    
    @State private var  gridViewModel = GridViewModel() // <---here
    
    var body: some View {
        VStack {
            Text("Toggle Visibility with observed Objects")
            Button("Add Collection") {
                addCollection()
            }
            
            ScrollView {
                ForEach(gridViewModel.collections) { coll in
                    Button("toggle visibility for:", action: {
                        coll.isCollapsed.toggle()
                    })
                    Button("edit", action: {
                        changeItem()
                    })
                    Text(coll.title)
                    
                    if coll.isCollapsed {
                        ForEach(coll.items, id: \.self) { item in
                            Text(item)
                        }
                    }
                    Spacer(minLength: 20)
                }
            }
        }
        .onAppear {
            let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
            let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
            let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])
            
            gridViewModel.collections = [collection1, collection2, collection3]
        }
    }
    
    func addCollection() {
        let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
        gridViewModel.collections.append(collectionNew) //<--- here
    }
    
    func changeItem() {
        gridViewModel.collections[0].items[0] = "---edit------edit------edit---" //<--- here
        print("done: changed to: \(gridViewModel.collections[0].items[0])")
    }
    
}

Обратите внимание: не используйте static func == ... и func hash..., удалите их.

Также обратите внимание: использование ForEach(coll.items, id: \.self) является плохой практикой, убедитесь, что ваш coll.items не содержит несколько одинаковых строк, используя, например, struct ItemName: Identifiable {....}

используйте @Observable вместо ob ObservableObject, исправив проблему: @Observable class Collection: Identifiable, Hashable { static func == (lhs: Collection, rhs: Collection) -> Bool { return lhs.title == rhs.title && lhs.items == rhs.items } func hash(into hasher: inout Hasher) { hasher.combine(title) } var id = UUID() var title : String = "" var items : [String] = [] var isCollapsed: Bool = true init(title: String, items: [String]) { self.title = title self.items = items } }

blendstylez 24.08.2024 21:59

обновил свой ответ, используя @Observable, как я уже упоминал в своих комментариях.

workingdog support Ukraine 25.08.2024 01:40

Спасибо большое (с)

blendstylez 25.08.2024 11:58

Другие вопросы по теме