0

I have a UIViewRepresentable wrapper around a WebView. I have added a bar beneath the webview with forward and back buttons. I want the buttons to be disabled when the WebView's canGoBack and canGoForward properties return false and vice versa.

ViewModel includes:

class ViewModel: ObservableObject {
  ...
   @Published var canGoBackPublisher = CurrentValueSubject<Bool, Never>(false)
   @Published var canGoForwardPublisher = CurrentValueSubject<Bool, Never>(false)
}

The ContentView includes:

struct ContentView: View {
   @ObservedObject var viewModel = ViewModel()
   ...
   
   var body: some View {
      VStack(spacing: 0) {
         WebView(viewModel: viewModel).overlay (
            RoundedRectangle(cornerRadius: 4, style: .circular)
               .stroke(Color.gray, lineWidth: 0.5)
         )
         WebNavigationView(viewModel: viewModel)
             .frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64)
             .background(Color.white)
      }
      
   }

The WebNavigationView (the button bar) includes:

struct WebNavigationView: View {
   @ObservedObject var viewModel: ViewModel
   ...      
   
   var body: some View {
      HStack(alignment: .center, spacing: 64, content: {
         Button(action: goBack) {
            Image(systemName: "chevron.left").resizable().aspectRatio(contentMode: .fit)
            }.disabled(!viewModel.canGoBackPublisher.value).
                frame(width: 24, height: 24, alignment: .center).padding(.leading, 32)
         Button(action: goForward) {
            Image(systemName: "chevron.right").resizable().aspectRatio(contentMode: .fit)
            }.disabled(!viewModel.canGoForwardPublisher.value)
                .frame(width: 24, height: 24, alignment: .center)
         Spacer()
      })
   }
   ...

the WebView's delegate includes:

   func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
      parent.viewModel.canGoBackPublisher.send(webView.canGoBack)
      parent.viewModel.canGoForwardPublisher.send(webView.canGoForward)
   }

The buttons start up grayed-out and disabled as expected. But they don't react to the state change, they stay disabled even when viewModel.canGoBackPublisher.value returns true. I'm a longtime iOS developer but very, very, very new to SwiftUI

Nate Birkholz
  • 2,739
  • 4
  • 20
  • 29

1 Answers1

2

You're ending up doubling up the publisher property by defining them as @Published and CurrentValueSubject.

The easiest fix would be to just make them Published, which handles most of the work for you:

class ViewModel: ObservableObject {
   @Published var canGoBack = false
   @Published var canGoForward = false
}

//...
//In delegate:
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
  viewModel.canGoBack = webView.canGoBack
  viewModel.canGoForward = webView.canGoForward
}

//...
//In Navigation view:
Button(action: goBack) {
  Image(systemName: "chevron.left").resizable().aspectRatio(contentMode: .fit)
}.disabled(!viewModel.canGoBack) //<-- here
.frame(width: 24, height: 24, alignment: .center).padding(.leading, 32)

Button(action: goForward) {
  Image(systemName: "chevron.right").resizable().aspectRatio(contentMode: .fit)
}.disabled(!viewModel.canGoForward) //<-- here
.frame(width: 24, height: 24, alignment: .center)

You could still define them as CurrentValueSubject if you want (and ditch the @Published property wrapper), but there's probably no need to in this case.

Good SO question on the difference between @Published and CurrentValueSubject: Difference between CurrentValueSubject and @Published

jnpdx
  • 45,847
  • 6
  • 64
  • 94