Quinn
Quinn

Reputation: 9476

Swift can't update view from parent

So I have a SwiftUI view that I instantiate in a parents ViewControllers viewDidLoad() method.

It looks something like this:

class ChildViewController: UIViewController {
    private var historyView: HistoryView?
    private var models: [HistoryModel]?
    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        historyView = HistoryView(models: models)
        let historyViewHost = UIHostingController(rootView: historyView)
        view.addSubview(historyViewHost.view)
        historyViewHost.didMove(toParent: self)
        historyViewHost.view.frame = view.frame
    }
    ...
}

At some point i need to update the models in the historyView and I do so like this:

func updateHistory() {
    let updatedModels = requests.map({ HistoryModel.fromRequest(request: $0) })

    historyView!.historyCellModels = updatedModels
}

The problem is the models in the view do not actually get updated. And I don't mean that the view doesn't display the new models, but it actually doesn't even get the new list of models.

My HistoryView also updates everytime I hit a button, when I hit that button I have a breakpoint set in the construction of the view, and from the debugger I can see that the models do not get updated. I also have a breakpoint in my updateHistory method, where i can see that the models in the parent ARE updated, its just not getting passed down to the child.

I had the idea that maybe 2 instances of the view were being created and I was just updating the wrong one. So I viewed the memory of the historyView when I had a breakpoint inside it, and I wrote down where it was in memory. Then at the breakpoint I have in the parent view, i went to look at the memory of historyView and it pointed to 0x00! But whats even stranger is that historyView is not nil! I even did a force cast of it to a non-optional and the program had no issues.

So I figured the debugger must be lying to me and just not giving the right info. So I went to some old trusty print statements. When I added print statements like this:

    func updateHistory() {
        let updatedModels = requests.map({ HistoryCellModel.fromRequest(request: $0) })

        print(updatedModels.count)
        historyView!.historyCellModels = updatedModels
        print(historyView?.historyCellModels.count)
    }

And then I create a new model, and call the update function it will output:

11
Optional(10)

How is that possible!

My HistoryView looks something like this:

struct HistoryView: View {
    @State var historyCellModels: [HistoryModel]

    var body: some View {
        List(historyCellModels) { ... }    
    }
}

I clearly set the two variables to be equal, the view is non-null, there is only one instance of it... I'm really not sure what the next step to hunting this bug is. Is there something obvious I could be missing?

Upvotes: 2

Views: 106

Answers (1)

Asperi
Asperi

Reputation: 258345

@State is designed to be used only inside SwiftUI view itself (and recommended always to be declared as private).

So here is a way...

Use instead

struct HistoryView: View {
    @ObservedObject var historyCellModels: HistoryViewModel

    init(models: HistoryViewModel) {
       historyCellModels = models
    }
    ...

where

import Combine

class HistoryViewModel: ObservableObject {
    @Published var historyCellModels: [HistoryModel]
}

and now it can be

class ChildViewController: UIViewController {
    private var models: HistoryViewModel = HistoryViewModel()
    ...

and

func updateHistory() {
    let updatedModels = requests.map({ HistoryModel.fromRequest(request: $0) })

    models.historyCellModels = updatedModels
}

and all should work.

Upvotes: 1

Related Questions