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

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a bunch! Got lost in too many SO questions and medium articles, I think. Much obliged!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.