Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI View not updating based on @ObservedObject

Tags:

In the following code, an observed object is updated but the View that observes it is not. Any idea why?

The code presents on the screen 10 numbers (0..<10) and a button. Whenever the button is pressed, it randomly picks one of the 10 numbers and flips its visibility (visible→hidden or vice versa).

The print statement shows that the button is updating the numbers, but the View does not update accordingly. I know that updating a value in an array does not change the array value itself, so I use a manual objectWillChange.send() call. I would have thought that should trigger the update, but the screen never changes.

Any idea? I'd be interested in a solution using NumberLine as a class, or as a struct, or using no NumberLine type at all and instead rather just using an array variable within the ContentView struct.

Screenshot

Here's the code:

import SwiftUI  struct ContentView: View {      @ObservedObject var numberLine = NumberLine()      var body: some View {         VStack {             HStack {                 ForEach(0 ..< numberLine.visible.count) { number in                     if self.numberLine.visible[number] {                         Text(String(number)).font(.title).padding(5)                     }                 }             }.padding()              Button(action: {                 let index = Int.random(in: 0 ..< self.numberLine.visible.count)                 self.numberLine.objectWillChange.send()                 self.numberLine.visible[index].toggle()                 print("\(index) now \(self.numberLine.visible[index] ? "shown" : "hidden")")             }) {                 Text("Change")             }.padding()         }     } }  class NumberLine: ObservableObject {     var visible: [Bool] = Array(repeatElement(true, count: 10)) } 
like image 830
Anton Avatar asked Mar 31 '20 17:03

Anton


2 Answers

With @ObservedObject everything's fine... let's analyse...

Iteration 1:

Take your code without changes and add just the following line (shows as text current state of visible array)

VStack { // << right below this     Text("\(numberLine.visible.reduce(into: "") { $0 += $1 ? "Y" : "N"} )") 

and run, and you see that Text is updated so observable object works

demo

Iteration 2:

Remove self.numberLine.objectWillChange.send() and use instead default @Published pattern in view model

class NumberLinex: ObservableObject {     @Published var visible: [Bool] = Array(repeatElement(true, count: 10)) } 

run and you see that update works the same as on 1st demo above.

*But... main numbers in ForEach still not updated... yes, because problem in ForEach - you used constructor with Range that generates constant view's group by-design (that documented!).

!! That is the reason - you need dynamic ForEach, but for that model needs to be changed.

Iteration 3 - Final:

Dynamic ForEach constructor requires that iterating data elements be identifiable, so we need struct as model and updated view model.

Here is final solution & demo (tested with Xcode 11.4 / iOS 13.4)

demo2

struct ContentView: View {      @ObservedObject var numberLine = NumberLine()      var body: some View {         VStack {             HStack {                 ForEach(numberLine.visible, id: \.id) { number in                     Group {                         if number.visible {                             Text(String(number.id)).font(.title).padding(5)                         }                     }                 }             }.padding()              Button("Change") {                 let index = Int.random(in: 0 ..< self.numberLine.visible.count)                 self.numberLine.visible[index].visible.toggle()             }.padding()         }     } }  class NumberLine: ObservableObject {     @Published var visible: [NumberItem] = (0..<10).map { NumberItem(id: $0) } }  struct NumberItem {     let id: Int     var visible = true } 
like image 94
Asperi Avatar answered Sep 25 '22 13:09

Asperi


I faced the same issue. For me, replacing @ObservedObject with @StateObject worked.

like image 35
Saurabh Bajaj Avatar answered Sep 25 '22 13:09

Saurabh Bajaj