3

I am new in SwiftUI I have created WebView like this in SwiftUI

struct WebView: UIViewRepresentable {
    @Binding var title: String
    var url: URL
    var loadStatusChanged: ((Bool, Error?) -> Void)? = nil

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

    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        view.navigationDelegate = context.coordinator
        view.load(URLRequest(url: url))
        return view
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // you can access environment via context.environment here
        // Note that this method will be called A LOT
    }

    func onLoadStatusChanged(perform: ((Bool, Error?) -> Void)?) -> some View {
        var copy = self
        copy.loadStatusChanged = perform
        return copy
    }

    class Coordinator: NSObject, WKNavigationDelegate,WKScriptMessageHandler {
        @State private var studentAppID = UserDefaults.standard.string(forKey: "studentAppID")
        let parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
            print(webView.url as Any)
            parent.loadStatusChanged?(true, nil)
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print(webView as Any)
            print(webView.url as Any)
            parent.title = webView.title ?? ""
            webView.evaluateJavaScript("document.getElementsByName('applnID')[0].value='\(studentAppID!)'", completionHandler: nil)
            parent.loadStatusChanged?(false, nil)
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            parent.loadStatusChanged?(false, error)
        }
        
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
//            guard let dict = message.body as? [String : AnyObject] else {
//                return
//            }
            
            print(message.body)
        }

    }
}

I want to configure function so that I can received response from WebView when user click on the button tag on the WebView
I found the code for this in swift but don't understand how can I used it in SwiftUI The code is like this

class ViewController: UIViewController, WKScriptMessageHandler {
  let content = """
  <!DOCTYPE html><html><body>
  <button onclick="onClick()">Click me</button>
  <script>
  function onClick() {
    window.webkit.messageHandlers.backHomePage.postMessage("success");
  }
  </script>
  </body></html>
  """

  override func viewDidLoad() {
    super.viewDidLoad()

    let config = WKWebViewConfiguration()
    config.userContentController = WKUserContentController()
    config.userContentController.add(self, name: "backHomePage")

    let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 200, height: 200), configuration: config)

    view.addSubview(webView)

    webView.loadHTMLString(content, baseURL: nil)
  }

  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print(message.body)
  }
}

as flow in this link

How to receive callback in ios from javascript?

Can I configure same in SwiftUI if yes then how?

1
  • @State is for use inside a View. Not a class. Commented May 2, 2023 at 12:46

3 Answers 3

4

What you have implemented is correct however you have missed out on WKWebViewConfiguration object.

You need to create web view configuration that registers a message handler for your javascript code to call, see below:

  1. Create a class that can receive callbacks from JavaScript code:

    let scriptMessageHandler = ScriptMessageHandler()
    
    class ScriptMessageHandler: NSObject, WKScriptMessageHandler {
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            print(message.body)
        }
    }
    
  2. Create a config object and pass an object of ScriptMessageHandler as handler.

    let config = WKWebViewConfiguration()
    config.userContentController = WKUserContentController()
    config.userContentController.add(scriptMessageHandler, name: "backHomePage")```
    

You can do this in your makeUIView(context:) -> WKWebView method implementation, right before you create Web View object and pass this config to your web view.

In Javascript, above config can then be called like below:

function onClick() {
   window.webkit.messageHandlers.backHomePage.postMessage("success");
}

When above function is called from JavaScript, you will get a callback in your userContentController(_ didReceive:) implementation.

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

10 Comments

I understand but how can I do this in SwiftUI and where?
If I can config code in makeUIView(context:) -> WKWebView it shows me error. Not possible to call it there
@Muju As I mentioned in the answer, you can create configuration in your makeUIView(context:) -> WKWebView method implementation, right before you create Web View object and pass this config to your web view object. And you need to pass Coordinator object in place of self as you have set that as delegate to receive callbacks
If I write your code in makeUIView(context:) -> WKWebView it show me error Argument type 'WebView' does not conform to expected type 'WKScriptMessageHandler' even I write WKScriptMessageHandler next to WKWebView still showing error
Can you write whole code?
|
0

Base on @KishanSadhwani's answer, bellow is a test-passed example:


import SwiftUI
import WebKit

let testHTML = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Alert</title>
</head>
<body>

<button onclick="showAlert()">Click me to send message to native app</button>

<script>
    function showAlert() {
        window.webkit.messageHandlers.NAME.postMessage("[MESSAGE TO APP]");
    }
</script>

</body>
</html>

"""

class LogBookScriptMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.body)
    }
}

struct LogBookWebView: UIViewRepresentable {
    typealias UIViewType = WKWebView

    func makeUIView(context: Context) -> WKWebView {
        let config = WKWebViewConfiguration()

        let handler = LogBookScriptMessageHandler()
        config.userContentController.add(handler, name: "NAME")

        let webView = WKWebView(frame: .zero, configuration: config)

        webView.loadHTMLString(testHTML, baseURL: nil)

        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {

    }
}

#Preview {
    LogBookWebView()
}

Hope this helps.

Comments

0

I checked this at my own and its working fine for me.

struct WebView: UIViewRepresentable {
            func makeUIView(context: Context) -> WKWebView {
                let coordinator = makeCoordinator()
                let userContentController = WKUserContentController()
                userContentController.add(coordinator, name: "bridge")
                let configuration = WKWebViewConfiguration()
                configuration.userContentController = userContentController
                let wkwebview = WKWebView(frame: .zero, configuration: configuration)
                wkwebview.navigationDelegate = coordinator
                return wkwebview
            }
    
        func updateUIView(_ uiView: WKWebView, context: Context) {
                let content = """
                  <!DOCTYPE html>
                  <html>
                  <head>
                  <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, viewport-fit=cover">
                  </head>
                  <body>
                  <button>click me</button>
                  <hr/>
                  <div id="log"></div>
                  <script>
                    const log = (msg) => {
                      const p = document.createElement('p')
                      p.textContent = msg
                      document.querySelector('#log').append(p)
                    }
                    // to receive messages from native
                    webkit.messageHandlers.bridge.onMessage = (msg) => {
                      log('from native:' + msg)
                    }
        
                    document.querySelector('button').addEventListener('click', () => {
                      log(typeof webkit.messageHandlers.bridge.postMessage)
                      // send messages to native
                      webkit.messageHandlers.bridge.postMessage('{"msg": "hello?","id": ' + Date.now() + '}')
                    })
                  </script>
                  </body>
                  </html>
                  """
                uiView.loadHTMLString(content, baseURL: nil)
            }
    
        func makeCoordinator() -> Coordinator {
            return Coordinator()
        }
    
        class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
    
            private var webView: WKWebView?
    
            func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
                self.webView = webView
                print("WebView: navigation finished")
            }
    
            func webViewDidFinishLoad(webView: WKWebView) {
                print("Loaded: \(String(describing: webView.url))")
            }
    
            func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
                print("Loaded: \(String(describing: webView.url))")
        
    
            }
        
        // this method is used to get the message from the Webview
                func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
                    print("Signal: \(message.body)")
                    messageToWebview(msg: "iOS Native")
                }
        
        // this method is used to send the message to the Webview
                func messageToWebview(msg: String) {
                    self.webView?.evaluateJavaScript("webkit.messageHandlers.bridge.onMessage('\(msg)')")
                }
            }
    }

Hope this will help for you

Comments

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.