Zain
Zain

Reputation: 1679

Change to @Published var in @EnvironmentObject not reflected immediately

In this specific case, when I try to change an @EnvironmentObject's @Published var, I find that the view is not invalidated and updated immediately. Instead, the change to the variable is only reflected after navigating away from the modal and coming back.

import SwiftUI

final class UserData: NSObject, ObservableObject  {
    @Published var changeView: Bool = false
}

struct MasterView: View {
    @EnvironmentObject var userData: UserData
    @State var showModal: Bool = false

    var body: some View {
        Button(action: { self.showModal.toggle() }) {
            Text("Open Modal")
        }.sheet(isPresented: $showModal, content: {
            Modal(showModal: self.$showModal)
                .environmentObject(self.userData)
        } )
    }
}

struct Modal: View {
    @EnvironmentObject var userData: UserData
    @Binding var showModal: Bool

    var body: some View {
        VStack {
            if userData.changeView {
                Text("The view has changed")
            } else {
                Button(action: { self.userData.changeView.toggle() }) {
                    Text("Change View")
                }
            }
        }
    }
}



#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        MasterView().environmentObject(UserData())
    }
}
#endif

video showing view changing only after modal is dismissed

Is this a bug or am I doing something wrong?

This works if changeView is a @State var inside Modal. It also works if it's a @State var inside MasterView with a @Binding var inside Modal. It just doesn't work with this setup.

Upvotes: 13

Views: 5797

Answers (3)

Evan
Evan

Reputation: 1332

In Xcode 11 GM2, If you have overridden objectWillChange, then it needs to call send() on setter of a published variable.

If you don't overridden objectWillChange, once the published variables in @EnvironmentObject or @ObservedObject change, the view should be refreshed. Since in Xcode 11 GM2 objectWillChange already has a default instance, it is no longer necessary to provide it in the ObservableObject.

Upvotes: 1

Fabian
Fabian

Reputation: 5348

Changing

final class UserData: NSObject, ObservableObject  {

to

final class UserData: ObservableObject  {

does fix the issue in Xcode11 Beta6. SwiftUI does seem to not handle NSObject subclasses implementing ObservableObject correctly (at least it doesn't not call it's internal willSet blocks it seems).

Upvotes: 4

user7014451
user7014451

Reputation:

A couple of things.

  • Your setup doesn't work if you move the Button into MasterView either.
  • You don't have a import Combine in your code (don't worry, that alone doesn't help).

Here's the fix. I don't know if this is a bug, or just poor documentation - IIRC it states that objectWillChange is implicit.

Along with adding import Combine to your code, change your UserData to this:

final class UserData: NSObject, ObservableObject  {
    var objectWillChange = PassthroughSubject<Void, Never>()
    @Published var changeView: Bool = false {
        willSet {
            objectWillChange.send()
        }
    }
}

I tested things and it works.

Upvotes: 8

Related Questions