Search code examples
iosswiftswiftuicombinexcode13.2

SwiftUI: @State variable never get updated from @Published


I'm trying to trigger an alert when is an error in the model but it never get updated to show the alert:

Here is my implementation in the view:

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel
    @State var showAlert = false
    init() {
        viewModel = ViewModel()
        showAlert = viewModel.showAlert
    }
    var body: some View {
        NavigationView {
            Text("Hello, world!")
                .padding()
        }
        .alert(isPresented: $showAlert) {
            Alert(title: Text("This works"),
                  message: Text("Hello"),
                  dismissButton: .default(Text("got it"))
        )}
    }
}

Here is my models:

class ViewModel: ObservableObject {
    @Published var showAlert = false
    var cancellables = Set<AnyCancellable>()
    
    init() {
        DoSomething.shared.showAlert.sink { _ in
            print("got new Value")
        } receiveValue: {[weak self] value in
            print("value")
            self?.showAlert = value
        }.store(in: &cancellables)
    }
}
class DoSomething {
    let showAlert = PassthroughSubject<Bool, Never>()
    static let shared = DoSomething()
    private init() {
        checkToShowAlert()
    }
    func checkToShowAlert() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
            print("change value")
            self?.showAlert.send(true)
        }
    }
}

Any of you knows why the showAlert variable it never gets updated?

I'll really appreciate your help


Solution

  • In your current code, you're setting ContentView's showAlert to the ViewModel's showAlert at that point in time:

    init() {
      viewModel = ViewModel()
      showAlert = viewModel.showAlert //<-- assignment at the time of init
    }
    

    Meaning, it's false at the time of assignment. Because it's just a Bool getting assigned to another Bool, there's no mechanism to keep it updated if ViewModel's showAlert changes.

    The simplest solution is to get rid of your @State variable and observe the @Published property directly:

    struct ContentView: View {
        @StateObject var viewModel: ViewModel = ViewModel()
    
        var body: some View {
            NavigationView {
                Text("Hello, world!")
                    .padding()
            }
            .alert(isPresented: $viewModel.showAlert) {
                Alert(title: Text("This works"),
                      message: Text("Hello"),
                      dismissButton: .default(Text("got it"))
            )}
        }
    }