@Published var in a nested ObservableObject changed only after second change

I put Store1:ObservableObject into GlobalStore2:ObservableObject and use field1 and field2 in MultiStoresDemo2:View . I type A character in field1 , field2 doesn't redraw in View . I type B character in field1 , field2 shows A character only.

Is this SwiftUI bug?

I wrote a little code to demo how to reproduce it.

final class GlobalStore2: ObservableObject {
    static let shared = GlobalStore2()

    @Published var store1 = Store1()
}

final class Store1: ObservableObject {
    static let shared = Store1()

    @Published var field1 = "" {
        didSet {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                self.field2 = self.field1
            }
        }
    }
    @Published var field2 = ""
}

struct MultiStoresDemo2: View {
    @ObservedObject var store = GlobalStore2.shared

    var body: some View {
        VStack {
            TextField("field1", text: $store.store1.field1)
            Text("field2 \(store.store1.field2)")
        }
    }
}

If I change my code like below it will works

struct MultiStoresDemo2: View {
    @ObservedObject var store1 = Store1.shared
...
            Text("field2 \(Store1.shared.field2)")
            Text("field2 \(store1.field2)")
...
}

I am not sure, what do you expect, I guess that

  1. field2 can be set independently until field1 change
  2. if field1 is changed, field2 should be updated with value of field1

Generally, mixing property observers (willSet didSet) with property wrappers is very bad idea. Most likely the problem could be solved some different way with help of Combine. We don't know detail implementation of @Published property wrapper, but for sure it is some Publisher. Try the next to see how to work with them.

import Combine
import PlaygroundSupport

    class Store: ObservableObject {

        @Published var field1: String = "<txt1>"
        @Published var field2: String = "hello world"
        
        deinit {
            print("deinit")
        }
    }

    // 1) field2  can be set in model independently
    // 2) if field1 is changed, field2 should be updatet with value of field1

    do {
        let store = Store()
        
        store.$field1.sink { (s) in
            print("1:", s)
        }
        store.$field2.sink { (s) in
            print("2:", s)
        }
        
        store.$field1.sink { (s) in
            store.field2 = s
            print("3:", s)
        }
        
        store.$field2.sink { (s) in
            print("4:", s)
        }
        
        store.field1 = "alfa"
        print()
        store.field2 = "beta"
        print()
        store.field2 = "gama"
        print()
        store.field1 = "delta"
    }

it should print in the proper order ...

1: \<txt1>
2: hello world
2: \<txt1>
3: \<txt1>
4: \<txt1>
1: alfa
2: alfa
4: alfa
3: alfa

2: beta
4: beta

2: gama
4: gama

1: delta
2: delta
4: delta
3: delta
deinit
Terms of Service

Privacy Policy

Cookie Policy