Search code examples
swifttreeswiftuichildrenobservedobject

Updating the UI for a tree structure in SwiftUI


I have an ObservableObject class, Model0 which has a Published object nested. The nested object represents a recursive tree structure. I want to be able to update the tree structure and have the UI update appropriately. However, in the below code, when I update a child of the nested object, the UI is not updated, I assume because the reference to nested is not changed. How can I get the UI to update whenever I change any of the nested objects in the tree structure?

class Nested: Identifiable {
    var id: UUID = UUID()
    var name: String
    var children:[Nested]
    
    init(name: String, children:[Nested]) {
        self.children = children
        self.name = name
    }
}

class Model0 : ObservableObject {
    @Published var nested: Nested
    
    init(nested: Nested) {
        self.nested = nested
    }
    
    func update() {
        nested.children[0] = Nested(name:"New", children: [])
    }
}

struct NestedView: View {
    var nested: Nested
    
    var body: some View {
        VStack {
            Text(nested.name)
            
            ForEach(nested.children) { c in
                NestedView(nested: c)
            }
        }
    }
}


struct Test: View {
    @ObservedObject var model: Model0 = Model0(nested: Nested(name: "Parent" ,children: [Nested(name: "Child", children: [])]))
    
    var body: some View {
        VStack {
            Button(action: {
                self.model.update()
            }) {
             Text("Update")
            }
            NestedView(nested: model.nested)
        }
    }
}

Solution

  • If you decided to make reference-type model, then you need to make all of them observable, so each level could update corresponding view. And, of course, have separated view for each level model.

    Taking above into account, here is a solution. Tested with Xcode 12 / iOS 14.

    Modified part only:

    class Nested: ObservableObject, Identifiable {
        var id: UUID = UUID()
        @Published var name: String
        @Published var children:[Nested]
    
        init(name: String, children:[Nested]) {
            self.children = children
            self.name = name
        }
    }
    
    struct NestedView: View {
        @ObservedObject var nested: Nested
    
        var body: some View {
            VStack {
                Text(nested.name)
    
                ForEach(nested.children) { c in
                    NestedView(nested: c)
                }
            }
        }
    }