Neglected Sanity
Neglected Sanity

Reputation: 1430

State not updating properly in swiftui

I have a few components, but I will simplify the code here. Essentially I have a WebView class that builds up a webview and sets the webview variable in the parent class, then I can act on that webview to do things like "goBack" and such. The problem is, even though the method is being called correctly, the webview is always nil, and I can't figure out why...

BasicView...

struct BasicView: View {
  @State var webView: WebView? = nil
  var body: some View {
    NavigationView {
      VStack {
        WebView(url: "myurl here") { view in
          print("setView called. Setting webView")
          self.webView = view
        }
      }
      .navigationTitle("My App")
      .navigationBarTitleDisplayMode(.inline)
      .navigationBarItems(
        leading: 
          Button(action: { 
            guard let view = webView else {
              print("webView not set")
              return
            }
            view.goBack()
          }) {
            Text("Back")
          }
        )
      }
    }
  }
}

Now in my WebView...

struct WebView: UIViewRepresentable {
  let url: URL
  let setParent: (WebView) -> Void

  var webView: WKWebView

  init(
    url: URL, 
    setParent: @ecaping (WebView) -> Void = {_ in },
    webView: WKWebView = WKWebView()
  )

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }

  func makeUIView(context: Context) -> some WKWebView {
    webView.navigationDelegate = context.coordinator
    webView.uiDelegate = context.coordinator
    webView.load(URLRequest(url: url))
    setParent(self)
    return webView
  }

  func updateUIView(_ uiView: UIViewType, context: Context) {
  }

  func goBack() {
    //logic for going back
  }
  
  class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
    var parent: WebView
    
    init(_ parent: WebView) {
      self.parent = parent
    }
    //all my override functions for navigation and whatnot here
  }
}

So, now when I run my app I do see in the logs "setView called. Setting webView" and then if I click on the back button in the navigation bar I see "webView not set"

So the setParent is being called and self.webView should be set because that method is being called, but when I click the button in the navigation bar, I see the webView not set show up. So somehow the state variable is not being updated when the setParent is being called.

Not sure how to proceed as this is the first time I have seen a state variable not update and be usable. Any help is greatly appreciated. Thank you

Upvotes: -3

Views: 118

Answers (1)

Try this approach using a NavigationLink to achieve what you ask ...to allow the webview to go "back" meaning the webview loads to a specific URL.

Note, SwiftUI is what is called a declarative system, you declare what you want to see, and then you change the data that the view represents.

Fully working example test code:

import Foundation
import SwiftUI
import WebKit

struct ContentView: View {
    var body: some View {
        BasicView()
    }
}

struct BasicView: View {
    let swiftUIUrl = URL(string: "https://developer.apple.com/xcode/swiftui/")!
    
    let navStackUrl = URL(string: "https://developer.apple.com/documentation/swiftui/navigationstack/")!
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 55) {
                NavigationLink(destination:  WebView(url: swiftUIUrl)) {
                    Text("Go to SwiftUI")
                }
                NavigationLink(destination:  WebView(url: navStackUrl)) {
                    Text("Go to NavigationStack")
                }
            }
            .navigationTitle("My App")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct WebView: UIViewRepresentable {
    let url: URL

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        uiView.load(request)
    }
}

Another working example code using a data model:

// the data model
struct MyData: Identifiable {
    let id = UUID()
    var url: URL?
    var name: String
}

struct BasicView: View {
    // the data
    let urls = [
        MyData(url: URL(string: "https://developer.apple.com/xcode/swiftui/"), name: "SwiftUI"),
        MyData(url: URL(string: "https://developer.apple.com/documentation/swiftui/navigationstack/"), name: "NavigationStack")
    ]
    
    var body: some View {
        NavigationStack {
            List(urls) { data in
                NavigationLink(destination:  WebView(url: data.url)) {
                    Text("Go to \(data.name)")
                }
            }
            .navigationTitle("My App")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct WebView: UIViewRepresentable {
    let url: URL?

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        if let url {
            let request = URLRequest(url: url)
            uiView.load(request)
        }
    }
}

Upvotes: 0

Related Questions