matyasl
matyasl

Reputation: 519

Why does change in the model not reflect in child views?

struct ContentView: View {
    
    @StateObject var cntrl=ContentViewController2()
    
    var body: some View {
        VStack {
            ItemsDisplay(items: cntrl.model.items, handler: cntrl.updateItem)
        }
    }
    
}

struct ItemsDisplay:View {
    
    let items:[SubItem]
    let handler:(SubItem)->Void
    
    init(items: [SubItem], handler: @escaping (SubItem) -> Void) {
        self.items = items
        self.handler = handler
        
        print("init of ItemsDisplay")
        for item in items {
            print("\(item.id) \(item.value)")
        }
    }
    
    var body: some View {
        VStack {
            ForEach(items, id: \.id) {item in
                ItemDisplay(item: item, handler: handler)
            }
        }
    }
}

struct ItemDisplay:View {
    
    let item:SubItem
    let handler:(SubItem)->Void
    
    init(item: SubItem, handler: @escaping (SubItem) -> Void) {
        self.item = item
        self.handler = handler
        print("init of ItemDisplay: \(item.value)")
    }
    
    var body: some View {
        VStack {
            Text("\(item.id)")
            Text("\(item.value)")
        }
        .padding(.all)
        .border(.black, width:5)
        .onTapGesture {
            print("tapped")
            handler(item)
        }
    }
}

struct SubItem:Equatable {
    
    static func == (lhs: SubItem, rhs: SubItem) -> Bool {
        lhs.id == rhs.id
    }
    
    let id:String
    var value:Int
    
    mutating func changeValue(_ newval:Int) {
        print("changing value to: \(newval)")
        value=newval
    }
}

struct ContentViewModel2 {
    var items:[SubItem]
    
    
    mutating func updateItem(_ item:SubItem) {
        if let i=items.firstIndex(of: item) {
            let newValue=item.value+1
            print("newValue for item: \(item.id) should be: \(newValue)")
            items[i].changeValue(newValue)
            print("newValue of item: \(items[i].id) is: \(items[i].value)")
        }
    }
    
}

class ContentViewController2:ObservableObject {
    @Published var model:ContentViewModel2
    
    init() {
        model=ContentViewModel2(items: [SubItem(id: "box1", value: 1), SubItem(id: "box10", value: 10)])
    }
    
    func updateItem(_ item:SubItem) {
        if let i=model.items.firstIndex(of: item) {
            print("updating: \(item.id)")
            model.updateItem(item)
        }
//        objectWillChange.send()
    }
}

The code above creates a VStack of 2 SubItem structs as a box with a name and a number. When the box is clicked, the value of the number displayed should increment by 1 by calling a handler of the controller that holds the "source of truth" in @Published var model.

The problem is that the change in value does not show.

I init each View to see what values are passed. It seems to work all the way down to ItemsDisplay:View. But it seems that the body of this View is not called, thus the ItemDisplay:View never gets to display the new value.

I don't understand why when the init is called with items that have changed correctly, it's body is not called.

Any ideas?

When I click on the box1, this prints out:

tapped updating: box1
newValue for item: box1 should be: 2
changing value to: 2
newValue of item: box1 is: 2
init of ItemsDisplay
box1 2
box10 10

Upvotes: 0

Views: 54

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29614

This

static func == (lhs: SubItem, rhs: SubItem) -> Bool {
     lhs.id == rhs.id
}

Is telling SwiftUI to only reload views when the id changes.

Remove it.

Keep the original implementation of Equatable

SwiftUI is dependent on Equatable, Identifiable and Hashable. Don’t override default implementations unless you have a really good reason.

Upvotes: 0

Related Questions