One, foreword

Recently, a friend asked me about the interaction between WKWebView and JS, but I have been using UIWebView before, and have never done WKWebView interaction! Next, let’s learn how WKWebView implements native code and JS interaction.

Second, the WKWebView

  • Support for more HTML5 features

  • Up to 60fps scrolling refresh frequency with built-in gestures

  • A Safari compatible JavaScript engine

  • There are significant improvements in performance, stability less memory usage and more detailed protocol methods and functionality

  • You can obtain the loading progress.

WKWebView agent method

/ *! @abstract The web view's navigation delegate. */weak open var navigationDelegate: WKNavigationDelegate? / *! @abstract The web view's user interface delegate. */weak open var uiDelegate: WKUIDelegate?Copy the code

WKNavigationDelegate’s proxy method

// Decide whether the link is allowed to jump optional func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @ escaping (WKNavigationResponsePolicy) - > Void) / / link to start loading when the optional func webView (_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) / / received server redirect when the optional func webView (_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) / / load error when the optional func webView (_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation! , withError error: error) // Called when content starts to reach the main frame (almost complete) Optional Func webView(_ webView: WKWebView, didCommit Navigation: WKNavigation!) // Optional Func webView(_ webView: WKWebView, didFinish Navigation: WKNavigation!) // Call optional Func webView(_ webView: WKWebView, didFail Navigation: WKNavigation! , withError error: error) // Call if webView needs to respond to authentication (if server certificate needs to be verified) Optional Func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) // Called when the webView's Web content process is terminated. After (iOS 9.0) optional func webViewWebContentProcessDidTerminate (_ webView: WKWebView)Copy the code

As a developer, it is especially important to have a learning atmosphere and a communication circle. This is my iOS communication group: 642363427, no matter you are white or big, welcome to join us, share BAT, Ali interview questions, interview experience, discuss technology, and communicate with 2800+iOS developers!

Agent method of WKUIDelegate

Used to do some page events, pop-up alerts, reminders, etc.

/ / received warning panel optional func webView (_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) // Receiving acknowledgement panel Optional Func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) // Receive input box optional Func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String? , initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)Copy the code

5. Interactive use of WKWebView and JS

Create HTML file on the home page, code as follows:

< HTML lang="en"> <head> <meta charset=" UTF-8 "/> <title>JS interactive </title> <style> body {font-size:30px; text-align:center; } * { margin: 30px; padding: 0; } h1{ color: red; } button{ width: 300px; height: 50px; font-size: 30px; </style> </head> <body> <h1> <h1> <h2></h2> <button onclick="testA()"> </button> <button < span style =" box-sizing: border-box; color: RGB (74, 74, 74); font-size: 13px! Important; word-break: break-word! Important; < span style =" box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 13px! Important; word-break: inherit! Important;" Alert (" I am a popover message in JS "); } function testB(value) {alert(value); } function testC(value) { return value + "value"; } function testObject(name,age) {var object = {name:name,age:age}; return object; } function testConfrim() {comfirm(" Are you sure to modify the data?" ) } function buttonAction(){ try { <! - js transfer data to the iOS - > Windows. Its. MessageHandlers. GetMessage. PostMessage (" I am js passed data ")} the catch (e) {the console. The log (e)}} </script> </body></html>Copy the code

1. IOS calls the method in JS and passes the parameters

//案例1self.webView?.evaluateJavaScript("testInput('123')", completionHandler: { (data            , error) in            print(data as Any)        })//案例2self.webView?.evaluateJavaScript("testObject('xjf',26)", completionHandler: { (data, err) in            print("\(String(describing: data)),\(String(describing: err))")        })
Copy the code

2. Js passes data to iOS

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {print("\(message.name)" + "\(message.body)");Copy the code

3. Click the button in JS to implement the popover

//MARK:WKUIDelegate// This method is an implementation of the js alert method interface. The default popup window should only have a prompt message and a confirm button. You can add more buttons and other content, but it won't work. The //// parameter message is <message> in the js method alert(<message>), func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {let alertViewController = UIAlertController(title: "查 看 ", message:message, preferredStyle: UIAlertController. Style. Alert) alertViewController. AddAction (UIAlertAction (title: "confirm", Style: UIAlertAction.Style.default, handler: { (action) in completionHandler() })) self.present(alertViewController, animated: true, completion: Nil)} // confirm (); // confirm (); // confirm (); // confirm (); <message>func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alertVicwController = UIAlertController(title: "Hint ", message: message, preferredStyle: UIAlertController. Style. Alert) alertVicwController. AddAction (UIAlertAction (title: "cancel", Style: UIAlertAction.Style.cancel, handler: {(alertAction) in completionHandler (false)})) alertVicwController. AddAction (UIAlertAction (title: "sure," style: UIAlertAction.Style.default, handler: { (alertAction) in completionHandler(true) })) self.present(alertVicwController, animated: true, completion: Nil)} // Prompt (); // Prompt (); // Prompt (); // Prompt (); // The parameter "prompt" is "prompt" (<message>, <defaultValue>); The <message>// parameter defaultText is prompt(<message>, <defaultValue>); The < defaultValue > func webView (_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String? , initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alertViewController = UIAlertController(title: prompt, message: "", preferredStyle: UIAlertController.Style.alert) alertViewController.addTextField { (textField) in textField.text = defaultText } AlertViewController. AddAction (UIAlertAction (title: "complete", style: UIAlertAction style.css. Default, handler: { (alertAction) in completionHandler(alertViewController.textFields! [0].text) })) self.present(alertViewController, animated: true, completion: nil)}Copy the code

4. Obtain the data of nodes in the web page

// webView loading completed -(void)webView (WKWebView *)webView didFinishNavigation (WKNavigation *) Navigation {// set JS NSString * JS = @"document.getElementsByTagName('h1')[0].innerText"; // execute JS [webView evaluateJavaScript: JS completionHandler:^(id _Nullable response, NSError * _Nullable error) { NSLog(@"value: %@ error: %@", response, error); }]; }Copy the code

5. Modify the content of the node by injecting JS

Let js = "document. The getElementsByTagName (' h2) [0]. The innerText = 'this is the way to a iOS writing"; // Inject js into the web pageCopy the code

6, js to obtain several ways of DOM node

document.getElementById(); / / id name, document. The getElementsByTagName (); / / document. The tag name getElementsByClassName (); / / the name of the class document. GetElementsByName (); Document.queryselector (); // The CSS selector pattern returns the first element that matches the pattern. The result is an element; If no match is found in the element, it returns nulldocument. QuerySelectorAll () / / CSS selectors mode, return and the model of all matched elements, the results for a class arrayCopy the code

Six, JavaScriptCore

JavaScriptCore is a library that Apple added to the standard library after iOS 7. It has had an epoch-making impact on iOS Native interacting with JS.

JavaScriptCore is basically made up of four classes and a protocol:

  • JSContext is the JS execution context, which you can think of as the environment in which JS is running.

  • JSValue is a reference to a JavaScript value, and any value in JS can be wrapped as a JSValue.

  • JSManagedValue is the packaging of JSValue with “conditional retain” added.

  • JSVirtualMachine represents a standalone environment for JavaScript execution.

There is also the JSExport protocol:

1
Implement a protocol for exporting native classes and their instance methods, class methods, and properties into JavaScript code. `

JSContext, JSValue, JSManagedValue

Usage of JSVirtualMachine and its relationship to JSContext

A JSVirtualMachine instance represents a stand-alone environment for JavaScript execution. You use this class for two main purposes: to support concurrent JavaScript execution, and to manage the memory of objects bridged between JavaScript and Objective-C or Swift.

About the use of JSVirtualMachine, generally we do not need to manually create JSVirtualMachine. Because when we get the JSContext, the JSContext that we get belongs to a JSVirtualMachine.

Each JavaScript context (JSContext object) belongs to a JSVirtualMachine. Each JSVirtualMachine can contain multiple contexts, allowing values (JSValue objects) to be passed between contexts. However, each JSVirtualMachine is different, that is, we cannot pass values created in one JSVirtualMachine to the context in another.

The JavaScriptCore API is thread-safe — for example, we can create a JSValue object or run a JS script from any thread — but all other threads trying to use the same JSVirtualMachine will be blocked. To run JavaScript scripts simultaneously (concurrently) on multiple threads, use separate Instances of JSVirtualMachine for each thread.

Seven, case source code:

class NAHomeViewController : UIViewController,WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate,UINavigationControllerDelegate { var webView :  WKWebView? var content : JSContext? var userContentController : WKUserContentController? Override func viewDidLoad () {super viewDidLoad () the self. The navigationItem. Title = "home page" / / create a configuration object configuration = WKWebViewConfiguration () / / set preferences for WKWebViewController let preference = WKPreferences () configuration. The preferences = Preference / / allow native interact with js preference. JavaScriptEnabled = true / / change the webView person that let the webView = WKWebView. Init (frame: CGRect(x: 0, y: 64, width: self.view.frame.size.width, height: 300)) let path = Bundle.main.path(forResource: "wKWebView", ofType: "html") webView.navigationDelegate = self webView.uiDelegate = self let request = URLRequest.init(url: URL.init(fileURLWithPath: path!) ) webView.load(request) self.view.addSubview(webView) self.webView = webView let userContentController = WKUserContentController() configuration.userContentController = userContentController userContentController.add(self,name: "getMessage") self.userContentController = userContentController let btn = UIButton.init(frame: CGRect(x: 100, y: 390, width: 100, height: 50)) btn.settitlecolor (.black, for:.normal) btn.settitle ("oc call js", for: .normal) btn.addTarget(self, action: #selector(btnAction), for: .touchUpInside) self.view.addSubview(btn) self.view.backgroundColor = .white } @objc func btnAction() { self.webView? .evaluateJavaScript("testInput('123')", completionHandler: { (data , error) in print(data as Any) }) self.webView? .evaluateJavaScript("testObject('xjf',26)", completionHandler: { (data, err) in print("\(String(describing: data)),\(String(describing: err))") }) } func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {print("\(message.name)" + "\(message.body)")// message.name method name // message.body} override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {// webView.evaluateJavaScript("testA()") { (data, err) in// print("\(String(describing: data)),\(String(describing: err))")// } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation! , withError error: Error) {print("\(Error) ")} //MARK:WKUIDelegate // this method is an implementation of the js alert method interface. The default popup window should only have a prompt message and a confirm button, but you can add more buttons and other content. But it doesn't work // Click the ok button for the corresponding event, need to execute the completionHandler, The message argument is <message> func webView(_ webView) in the js method alert(<message>) WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {let alertViewController = UIAlertController(title: "查 看 ", message:message, preferredStyle: UIAlertController. Style. Alert) alertViewController. AddAction (UIAlertAction (title: "confirm", Style: UIAlertAction.Style.default, handler: { (action) in completionHandler() })) self.present(alertViewController, animated: true, completion: Nil)} // confirm (); // confirm (); // confirm (); // confirm (); <message> func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let alertVicwController = UIAlertController(title: "Hint ", message: message, preferredStyle: UIAlertController. Style. Alert) alertVicwController. AddAction (UIAlertAction (title: "cancel", Style: UIAlertAction.Style.cancel, handler: {(alertAction) in completionHandler (false)})) alertVicwController. AddAction (UIAlertAction (title: "sure," style: UIAlertAction.Style.default, handler: { (alertAction) in completionHandler(true) })) self.present(alertVicwController, animated: true, completion: Nil)} // Prompt (); // Prompt (); // Prompt (); // Prompt (); // The parameter "prompt" is "prompt" (<message>, <defaultValue>); The <message> // parameter defaultText is prompt(<message>, <defaultValue>); The < defaultValue > func webView (_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String? , initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { let alertViewController = UIAlertController(title: prompt, message: "", preferredStyle: UIAlertController.Style.alert) alertViewController.addTextField { (textField) in textField.text = defaultText } AlertViewController. AddAction (UIAlertAction (title: "complete", style: UIAlertAction style.css. Default, handler: { (alertAction) in completionHandler(alertViewController.textFields! [0].text) })) self.present(alertViewController, animated: true, completion: nil) }}Copy the code