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
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