How to get the Id of clicked element in a WebKit view? - ios

I have a UIWebKit loaded in an url and I want to pick the id of a html element when clicked. I can get the element when I know the id but how to get an unknown element Id when it is clicked.
thank you for helping me !

You can do it following way.
Inject some javascript into the WebView using WKUserScript.
Injected javascript will listen to document body for any click event.
Upon an click event received, find the DOM element using elementFromPoint.
Setup that way, javascript will communicate with native code.
Upon receiving clicks and finding the element, contact native code for clicked DOM.
I've tested this way of working & it works for me.
import UIKit
import WebKit
class ViewController: UIViewController {
#IBOutlet weak var webView: WKWebView!
private var url = URL(string: "https://www.google.com")!
override func viewDidLoad() {
super.viewDidLoad()
initializeWebView()
loadData()
}
private func initializeWebView() {
let javascript = """
window.onload = function() {
document.addEventListener("click", function(evt) {
var tagClicked = document.elementFromPoint(evt.clientX, evt.clientY);
window.webkit.messageHandlers.jsMessenger.postMessage(tagClicked.outerHTML.toString());
});
}
"""
let userScript = WKUserScript.init(source: javascript,
injectionTime: .atDocumentStart, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(userScript)
webView.configuration.userContentController.add(self, name: "jsMessenger")
}
private func loadData() {
let request = URLRequest(url: url)
webView?.load(URLRequest.init(url: url))
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}
}
It gives you the whole element as string

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
#IBOutlet weak var webContentView: UIView!
var web: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
let JSsource = """
window.onload = function() {
document.addEventListener("click", function(evt) {
var tagClicked = document.elementFromPoint(evt.clientX, evt.clientY);
window.webkit.messageHandlers.jsMessenger.postMessage(tagClicked.outerHTML.toString());
});
}
"""
let script = WKUserScript(source: JSsource, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
config.userContentController.addUserScript(script)
config.userContentController.add(self as! WKScriptMessageHandler, name: "jsMessenger")
self.web = WKWebView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height-60), configuration : config)
self.web.navigationDelegate = self
self.web.uiDelegate = self
self.webContentView.addSubview(self.web!)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.body)
}

Related

How to Handle Image uploading in WKWebview?

I have use webkit to show url on my app.
when I click on image button(green button in ), it shows
camera or photo library.
issue is, when click Use photo, the webkit
refresh automatically instead showing on page
I have already give photo usage, media, camera permission in plist file
my code is
import UIKit
import WebKit
import Alamofire
class ContinueViewController: UIViewController, WKUIDelegate {
#IBOutlet weak var activityView: UIActivityIndicatorView!
#IBOutlet weak var webview: WKWebView!
func getPostString(params:[String:Any]) -> String{
var data = [String]()
for(key, value) in params
{
data.append(key + "=\(value)")
}
return data.map { String($0) }.joined(separator: "&")
}
var url: URL?
var traderCategoryId = 0, tradeSkillId = 0
override func viewDidLoad() {
super.viewDidLoad()
webview.navigationDelegate = self
self.navigationController?.navigationBar.isHidden = false
webview.uiDelegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
activityView.startAnimating()
guard let url = self.url else { return }
var req = URLRequest(url: url)
let params = ["id" : id,"trader_skills" : tradeSkillId]
let postString = self.getPostString(params: params)
req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.httpMethod = "POST"
req.httpBody = postString.data(using: .utf8)
self.webview.load(req)
}
}
extension ContinueViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
activityView.stopAnimating()
}
}
extension UIImagePickerController {
open override func viewDidLoad() {
super.viewDidLoad()
// self.modalPresentationStyle = .fullScreen
}
}
issue is, when click Use photo, the webkit refresh automatically instead showing on page
Because you self.webview.load(req) in
override func viewWillAppear(_ animated: Bool) {
shows camera or photo library, then return to this page
func viewWillAppear called again
self.webview.load(req) called again
do it simple ,
put self.webview.load(req) the relevant in
override func viewDidLoad() {

How to get JS event handler with URL in swift

class FeedBackFormViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
#IBOutlet weak var webViewShowing: UIView!
var formWebView: WKWebView!
private let fileString = UserSingleton.shared.feedbackFormLink
override func viewDidLoad() {
super.viewDidLoad()
setupJSFile()
formWebView.navigationDelegate = self
previewFiles()
}
// show files in web view
private func previewFiles() {
if let fileString = fileString, fileString != "" {
let url = URL(string: fileString)
let myRequest = URLRequest(url: url!)
UserSingleton.shared.showHUD()
formWebView.load(myRequest)
}else {
Alerts.shared.show(alert: .error, message: "no file found", type: .error)
}
}
private func setupJSFile() {
let config = WKWebViewConfiguration()
let js = "document.addEventListener('submit', function(){ window.webkit.messageHandlers.clickListener.postMessage('My hovercraft is full of eels!'); })"
let script = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
config.userContentController.addUserScript(script)
config.userContentController.add(self, name: "submit")
formWebView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webViewShowing.addSubview(formWebView)
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print(message.name)
}
#IBAction func dismissFeedbackFormVC(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
extension FeedBackFormViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
webView.evaluateJavaScript("document.readyState") { (result, error) in
if let result = result {
print(result)
}
}
}
}
only call wk navigation when the web view is loaded and I click anywhere no trigger call in userContentController any event
You'd need to add this script for execution, just append it at the end of the func setupJSFile:
formWebView.configuration.userContentController.addUserScript(script)
also, you'd need to change your JS script to
document.addEventListener('click', function(){ window.webkit.messageHandlers['iosListener'].postMessage('click clack!'); })

UIWebView and JavaScriptInterface in Swift

How can I to create the JavascriptInterface channel from my web site to my UIWebView?
Example in Android:
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
And from this JavascriptInterface I would like to draw the methods, as for example:
func webViewDidStartLoad(webView: UIWebView)
or
myActivityIndicator.startAnimating()
How can I do?
For WKWebView: source here
JavaScript
function callNativeApp () {
try {
webkit.messageHandlers.callbackHandler.postMessage("Hello from JavaScript");
} catch(err) {
console.log('The native context does not exist yet');
}
}
Swift
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler {
#IBOutlet var containerView: UIView? = nil
var webView: WKWebView?
override func loadView() {
super.loadView()
let contentController = WKUserContentController()
contentController.addScriptMessageHandler(self, name: "callbackHandler")
let config = WKWebViewConfiguration()
config.userContentController = contentController
self.webView = WKWebView( frame: self.containerView!.bounds, configuration: config)
self.view = self.webView
}
override func viewDidLoad() {
super.viewDidLoad()
//I use the file html in local
let path = NSBundle.mainBundle().pathForResource("index", ofType: "html")
let url = NSURL(fileURLWithPath: path!)
let req = NSURLRequest(URL: url)
self.webView!.loadRequest(req)
}
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {// edit: changed fun to func
if (message.name == "callbackHandler"){
print("\(message.body)")
}
}
}
For UIWebView: source here
JavaScript in HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>
(function(){
$(window).load(function(){
$('.clickMe').on('click', function(){
window.location = "foobar://fizz?Hello_from_javaScript";
});
});
})(jQuery);
</script>
Swift
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var Web: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("index", ofType: "html")
let url = NSURL(fileURLWithPath: path!)
let req = NSURLRequest(URL: url)
Web.delegate = self
Web.loadRequest(req)
}
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.URL?.query != nil {
print("\(request.URL!.query!)")
}
return true
}
}
Here's a quick example:
Register a URL Scheme such as foobar in Xcode
Handle this URL Scheme in your web view
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.URL?.query?.containsString("show_activity_indicator=true") {
myActivityIndicator.startAnimating()
}
}
Finally, call this from your JavaScript
// Show your activity indicator from JavaScript.
window.location = "foobar://fizz?show_activity_indicator=true"
Note: See my question here for more information on web view communication in iOS.

ScriptMessageHandler not always called on actual device, works fine on simulator

I use WKWebView and I want to be notified when website is fully loaded. The webView:didFinishNavigation method of WKNavigationDelegate is fired when document.readyState is either interactive or complete and I want to be sure that site was completely loaded. I came up with the solution which uses JavaScript injection. Here is my MWE:
import UIKit
import WebKit
class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate {
var webView: WKWebView!
#IBOutlet weak var loadLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let contentController = WKUserContentController()
let scriptPath = NSBundle.mainBundle().pathForResource("script", ofType: "js")!
let scriptString = try! String(contentsOfFile: scriptPath)
let script = WKUserScript(source: scriptString, injectionTime: .AtDocumentStart, forMainFrameOnly: true)
contentController.addUserScript(script)
contentController.addScriptMessageHandler(self, name: "readyHandler")
let configuration = WKWebViewConfiguration()
configuration.userContentController = contentController
webView = WKWebView(frame: CGRect.zero, configuration: configuration)
webView.navigationDelegate = self
loadLabel.text = nil
}
#IBAction func loadWebsite() {
webView.loadRequest(NSURLRequest(URL: NSURL(string: "http://stackoverflow.com")!))
loadLabel.text = "Loading..."
}
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
print("message received")
loadLabel.text = "Complete"
}
}
And this is the content of script.js file:
document.onreadystatechange = function () {
if(document.readyState === "complete"){
webkit.messageHandlers.readyHandler.postMessage("");
}
}
userContentController:didReceiveScriptMessage method is always called on iOS Simulator, but on the actual device (iPhone 6 in my case) it isn't called most of the times. Any idea what can be wrong about it or what's the other way of checking if website is completely loaded?
For some reason you need to add the webView to a visible view for this to work on the device. If you don't want the webView to be visible, add it and then set the hidden property to true.
For the code example above:
func viewDidLoad(){
...
webView.hidden = true
view.addSubview(webView)
}

Send data to WKWebView

I am creating a View which displays a local webpage which also has styles and JavaScript (Essentially a hybrid native application) and I am using a WKWebView.
I know how to send data from the JavaScript to the native code but it is not clear how to do it the reverse way. I am wanting to send a JSON Object that is fetched using native iOS code to my MKWebView.
I am getting the following error
Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo=0x7bfb3c30 {NSLocalizedDescription=A JavaScript exception occurred}
nil
Below is my code.
//
// WebViewController.swift
//
// Created by Adam Bulmer on 09/03/2015.
// Copyright (c) 2015 Adam Bulmer. All rights reserved.
//
import Foundation
import WebKit
class WebViewController: BaseViewController, WKScriptMessageHandler, WKNavigationDelegate {
#IBOutlet var containerView : UIView! = nil
var webView: WKWebView!
override func loadView() {
super.loadView()
var config = WKWebViewConfiguration();
var contentController = WKUserContentController();
var userScript = WKUserScript(
source: "bootstrap()",
injectionTime: WKUserScriptInjectionTime.AtDocumentEnd,
forMainFrameOnly: true
)
contentController.addUserScript(userScript)
config.userContentController = contentController;
self.webView = WKWebView(frame:self.view.frame, configuration: config)
self.webView.navigationDelegate = self;
self.view = self.webView!
}
override func viewDidLoad() {
super.viewDidLoad()
var path = NSBundle.mainBundle().pathForResource(webViewName(),
ofType: "html");
var url = NSURL(fileURLWithPath: path!);
var request = NSURLRequest(URL: url!);
self.webView!.loadRequest(request);
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
var param = "Hello World";
var exec_template = "test(\(param)');";
self.webView!.evaluateJavaScript(exec_template, completionHandler: { (test, error) -> Void in
println(error);
println(test);
})
}
func webViewName() -> String {
return "";
}
// MARK: WKScriptMessageHandler callback
// Delegate callback for when scripts sends message
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
}
}
This code:
var param = "Hello World";
var exec_template = "test(\(param)');";
Will result in ..
test(Hello World');
.. being executed, which is not valid JS. You missed the opening ' in your code.

Resources