hi I'm implementing simple WKWebView appplication and I want to be able to ask user for input via prompt dialogue, I tried to use solution mentioned here
https://stackoverflow.com/a/40157363/1665293
but I'm not sure how should it work when implemented - should this just add extension to WKWebView for triggering e.g. regular alert() from javascript or I should pass some different instructions in js to trigger this native alert?
so my question is:
1) how this should work when implemented
2) what am I missing in implementation
here's my controller code (giving whole controller, as I dont know what could be important here)
thanks in advance!
import UIKit
import WebKit
class ViewController:
UIViewController
, WKNavigationDelegate
, UIScrollViewDelegate
, WKUIDelegate
{
#IBOutlet var webView: WKWebView!
let getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
let getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
override func loadView() {
self.webView = WKWebView()
self.webView.navigationDelegate = self
//for prompt
self.webView?.uiDelegate = self
view = webView
}
override func viewWillAppear(_ animated: Bool) {//white status bar
super.viewWillAppear(animated)
webView.isOpaque = false //removes white flash on WKWebView load
webView.backgroundColor = UIColor(red: 41/255, green: 45/255, blue: 91/255, alpha: 1)
UIApplication.shared.statusBarStyle = .lightContent
do {
let paid = Bundle.main.infoDictionary?["paid"] as? Bool;
var fileName = "none"
if(paid!){
fileName = "index-ios-wvd-inlined--paid"
} else {
fileName = "index-ios-wvd-inlined"
}
guard let filePath = Bundle.main.path(forResource: fileName, ofType: "html")
else {
print ("File reading error")
return
}
let contents = try String(contentsOfFile: filePath, encoding: .utf8)
let baseUrl = URL(fileURLWithPath: filePath)
webView.loadHTMLString(contents as String, baseURL: baseUrl)
}
catch {
print ("File HTML error")
}
}
override var preferredStatusBarStyle : UIStatusBarStyle {//white status bar
return .lightContent
}
override func viewDidLoad() {
webView.scrollView.bounces = false;
super.viewDidLoad()
webView.scrollView.delegate = self //disable zoom
//for haptics
let config = WKWebViewConfiguration()
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
//disable zoom
func viewForZooming(in: UIScrollView) -> UIView? {
return nil;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tapped(i:Int) {
print("Triggering haptic #\(i)")
switch i {
case 1:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.error)
case 2:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
case 3:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.warning)
case 4:
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
case 5:
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
case 6:
let generator = UIImpactFeedbackGenerator(style: .heavy)
generator.impactOccurred()
default:
let generator = UISelectionFeedbackGenerator()
generator.selectionChanged()
}
}
//alert/prompt/confirm dialogs
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler()
}))
present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(false)
}))
present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(nil)
}))
present(alertController, animated: true, completion: nil)
}
}
//sending scripts commands to JS and back
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case getUrlAtDocumentStartScript:
tapped(i: message.body as! Int)
//print("start: \(message.body)")
case getUrlAtDocumentEndScript:
tapped(i: message.body as! Int)
//print("tapped: \(message.body)")
default:
break;
}
}
}
extension WKUserScript {
class func getUrlScript(scriptName: String) -> String {
return "webkit.messageHandlers.\(scriptName).postMessage(1)"
}
}
extension WKWebView {
func loadUrl(string: String) {
if let url = URL(string: string) {
load(URLRequest(url: url))
}
}
}
extension WKWebViewConfiguration {
func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) {
let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
userContentController.addUserScript(userScript)
userContentController.add(scriptMessageHandler, name: scriptHandlerName)
}
}
okay, so I found answers and solution,
1) this will add support for native JS methods. alert(), prompt() and confirm() could be called from JS or via
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("confirm('Hello from evaluateJavascript()')", completionHandler: nil)
}
2) here's the implementation of methods I'm using right now (I insert this in the bottom of ViewController class:
func webView(_ webView: WKWebView,
runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping () -> Void) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let title = NSLocalizedString("OK", comment: "OK Button")
let ok = UIAlertAction(title: title, style: .default) { (action: UIAlertAction) -> Void in
alert.dismiss(animated: true, completion: nil)
}
alert.addAction(ok)
present(alert, animated: true)
completionHandler()
}
func webView(_ webView: WKWebView,
runJavaScriptTextInputPanelWithPrompt prompt: String,
defaultText: String?,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (String?) -> Void) {
let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.text = defaultText
}
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
if let text = alert.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(nil)
}))
self.present(alert, animated: true, completion: nil)
// if ipad will crash on this do this (https://stackoverflow.com/questions/42772973/ios-wkwebview-javascript-alert-crashing-on-ipad?noredirect=1&lq=1):
// if let presenter = alertController.popoverPresentationController {
// presenter.sourceView = self.view
// }
//
// self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView,
runJavaScriptConfirmPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(false)
}))
self.present(alertController, animated: true, completion: nil)
}
also to the bottom of viewDidLoad() I added this code:
webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView!)
Adding full code into collapsed snippet - in case someone will be confused how exactly this should be used:
import UIKit
import WebKit
class ViewController:
UIViewController
, WKNavigationDelegate
, UIScrollViewDelegate
, WKUIDelegate
{
//wk webvew set
#IBOutlet var webView: WKWebView!
let getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
let getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
//webkit.messageHandlers.GetUrlAtDocumentEnd.postMessage('1')
override func loadView() {
self.webView = WKWebView()
self.webView.navigationDelegate = self
//for prompt ??
self.webView?.uiDelegate = self
view = webView
}
override func viewWillAppear(_ animated: Bool) {//white status bar
super.viewWillAppear(animated)
webView.isOpaque = false //removes white flash on WKWebView load
webView.backgroundColor = UIColor(red: 41/255, green: 45/255, blue: 91/255, alpha: 1)
UIApplication.shared.statusBarStyle = .lightContent
do {
let paid = Bundle.main.infoDictionary?["paid"] as? Bool;
var fileName = "none"
if(paid!){
fileName = "index-ios-wvd-inlined--paid"
} else {
fileName = "index-ios-wvd-inlined"
}
guard let filePath = Bundle.main.path(forResource: fileName, ofType: "html")
else {
// File Error
print ("File reading error")
return
}
let contents = try String(contentsOfFile: filePath, encoding: .utf8)
let baseUrl = URL(fileURLWithPath: filePath)
webView.loadHTMLString(contents as String, baseURL: baseUrl)
}
catch {
print ("File HTML error")
}
}
override var preferredStatusBarStyle : UIStatusBarStyle {//white status bar
return .lightContent
}
override func viewDidLoad() {
webView.scrollView.bounces = false;
super.viewDidLoad()
//disable zoom
webView.scrollView.delegate = self
let config = WKWebViewConfiguration()
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentStartScript), scriptHandlerName:getUrlAtDocumentStartScript, scriptMessageHandler: self, injectionTime: .atDocumentStart)
config.addScript(script: WKUserScript.getUrlScript(scriptName: getUrlAtDocumentEndScript), scriptHandlerName:getUrlAtDocumentEndScript, scriptMessageHandler: self, injectionTime: .atDocumentEnd)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView!)
// Do any additional setup after loading the view, typically from a nib.
}
func viewForZooming(in: UIScrollView) -> UIView? {
return nil;
}
//disable zoom
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tapped(i:Int) {
print("Running \(i)")
switch i {
case 1:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.error)
case 2:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
case 3:
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.warning)
case 4:
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
case 5:
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
case 6:
let generator = UIImpactFeedbackGenerator(style: .heavy)
generator.impactOccurred()
default:
let generator = UISelectionFeedbackGenerator()
generator.selectionChanged()
}
}
//default alert/confirm/prompt dialogs
func webView(_ webView: WKWebView,
runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping () -> Void) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let title = NSLocalizedString("OK", comment: "OK Button")
let ok = UIAlertAction(title: title, style: .default) { (action: UIAlertAction) -> Void in
alert.dismiss(animated: true, completion: nil)
}
alert.addAction(ok)
present(alert, animated: true)
completionHandler()
}
func webView(_ webView: WKWebView,
runJavaScriptTextInputPanelWithPrompt prompt: String,
defaultText: String?,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (String?) -> Void) {
let alert = UIAlertController(title: nil, message: prompt, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.text = defaultText
}
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
if let text = alert.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(nil)
}))
self.present(alert, animated: true, completion: nil)
// if ipad will crash on this try to uncomment (based on https://stackoverflow.com/questions/42772973/ios-wkwebview-javascript-alert-crashing-on-ipad?noredirect=1&lq=1):
// if let presenter = alertController.popoverPresentationController {
// presenter.sourceView = self.view
// }
//
// self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView,
runJavaScriptConfirmPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo,
completionHandler: #escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
completionHandler(false)
}))
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("confirm('Hello from evaluateJavascript()')", completionHandler: nil)
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case getUrlAtDocumentStartScript:
tapped(i: message.body as! Int)
//print("start: \(message.body)")
case getUrlAtDocumentEndScript:
tapped(i: message.body as! Int)
//print("tapped: \(message.body)")
default:
break;
}
}
}
extension WKUserScript {
class func getUrlScript(scriptName: String) -> String {
return "webkit.messageHandlers.\(scriptName).postMessage(1)"
}
}
extension WKWebView {
func loadUrl(string: String) {
if let url = URL(string: string) {
load(URLRequest(url: url))
}
}
}
extension WKWebViewConfiguration {
func addScript(script: String, scriptHandlerName:String, scriptMessageHandler: WKScriptMessageHandler, injectionTime:WKUserScriptInjectionTime) {
let userScript = WKUserScript(source: script, injectionTime: injectionTime, forMainFrameOnly: false)
userContentController.addUserScript(userScript)
userContentController.add(scriptMessageHandler, name: scriptHandlerName)
}
}
Related
I have the following webview implementation built by borrowing code (I am learning SwiftUI). I want to show a message in the webview when the networks is down. It works with a static html loaded from a string and a button but I am unable to do the same calling a web server and triggering javascript using the network status.
import SwiftUI
import WebKit
import Combine
private let urlString: String = "http://127.0.0.1"
class WebViewData: ObservableObject {
#Published var parsedText: NSAttributedString? = nil
var functionCaller = PassthroughSubject<Void,Never>()
var isInit = false
var shouldUpdateView = true
}
struct WebView: UIViewRepresentable {
#StateObject var data: WebViewData
func makeUIView(context: Context) -> WKWebView {
let wkWebview = WKWebView()
wkWebview.navigationDelegate = context.coordinator
return wkWebview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard data.shouldUpdateView else {
data.shouldUpdateView = false
return
}
context.coordinator.tieFunctionCaller(data: data)
context.coordinator.webView = uiView
guard let url = URL(string: urlString) else { return }
let request = URLRequest(url: url)
uiView.load(request)
}
func makeCoordinator() -> WebViewCoordinator {
return WebViewCoordinator(view: self)
}
func viewWillAppear(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>){
}
}
class WebViewCoordinator : NSObject, WKNavigationDelegate {
var parent: WebView
var webView: WKWebView? = nil
private var cancellable : AnyCancellable?
init(view: WebView) {
self.parent = view
super.init()
}
func tieFunctionCaller(data: WebViewData) {
cancellable = data.functionCaller.sink(receiveValue: { _ in
self.webView?.evaluateJavaScript("message_generator(\"network_down\"")
})
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
if !self.parent.data.isInit {
self.parent.data.isInit = true
}
}
}
}
Here the ContentView:
import SwiftUI
struct ContentView: View {
#StateObject var webViewData = WebViewData()
#ObservedObject var networkManager = NetworkManager()
var body: some View {
ZStack{
Color.white
VStack {
ExecuteCode {
print(networkManager.isConnected)
webViewData.functionCaller.send() //???
}
WebView(data: webViewData)
if (!networkManager.isConnected) {
//???
}
}
.onReceive(webViewData.$parsedText, perform: { parsedText in
if let parsedText = parsedText {
print(parsedText)
}
})
}
}
}
struct ExecuteCode : View {
init( _ codeToExec: () -> () ) {
codeToExec()
}
var body: some View {
return EmptyView()
}
}
Here the NetworkManager:
import Foundation
import Network
class NetworkManager: ObservableObject {
let monitor = NWPathMonitor()
let queue = DispatchQueue(label: "NetworkManager")
#Published var isConnected = true
var imageName: String {
return isConnected ? "wifi" : "wifi.slash"
}
var connectionDescription: String {
if isConnected {
return "Internet connection looks good!"
} else {
return "It looks like you're not connected to the internet. Make sure WiFi is enabled and try again"
}
}
init() {
monitor.pathUpdateHandler = { path in
DispatchQueue.main.async {
if path.status == .satisfied {
self.isConnected = true
} else {
self.isConnected = false
}
}
}
monitor.start(queue: queue)
}
}
Thanks for helping.
Pointers to good docs are very appreciated.
I am trying to create a library that open a web view in a class, where the objetive is opened it but does not see it in the screen. I created and try to execute a javaScript that makes a postMessage, to obtain the result for example in the method userContentController(), but I am getting and error when I execute it. Also, I am used webView.callAsyncJavaScript, cause then I want to execute some async functions that webView.evaluateJavaScript does not let. Some knows what could be the reason that can help me. Thanks.
Code:
import WebKit
class FingerprintJs : NSObject, WKScriptMessageHandler, WKNavigationDelegate{
public func tryFinger(){
getWebView()
}
public func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage){
print("userContentController")
print(message.name)
}
private func getWebView(){
if let viewController = UIWindow.key?.rootViewController {
if Thread.isMainThread {
let webView = self.makeWebView(viewController: viewController as! ViewController)
webView.loadHTMLString(getHtml(), baseURL: nil)
if #available(iOS 14.0, *) {
print("bunea")
webView.callAsyncJavaScript(getJavaScript()!, arguments: [:], in: nil, in: .defaultClient, completionHandler:{(result) in
print("queeeeee")
print(result as Any)
})
} else {
print("Llegaste aca? porqueee ")
// Fallback on earlier versions
}
// webView.evaluateJavaScript(getJavaScript()!,completionHandler:{(result , error) in
// if error == nil {
// print("success")
// print(result as Any)
// }
// else {
// print("error in executing js ", error!)
// }
// })
} else {
print("Llegaste aca? ")
}
}else{
NSLog("The UIApplication keyWindow of the rootViewController must be loaded first", "")
}
}
private func makeWebView(viewController: ViewController) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "message5")
config.preferences = preferences
let webView = WKWebView(frame: .init(x: 1.0, y: 1.0, width: 0, height: 0),
configuration: config)
webView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(webView)
return webView
}
private func getJavaScript() -> String?{
var javaScript: String? = nil
if let jsSourcePath = Bundle.main.path(forResource: "ejemplo", ofType: "js") {
do {
javaScript = try String(contentsOfFile: jsSourcePath)
}
catch {
NSLog("getJavaScriptString: "+(error.localizedDescription), "")
}
}
return javaScript
}
private func getHtml() -> String {
let html: String =
"""
<html>
<body>
<script>
</script>
</body>
</html>
"""
return html
}
private var webView: WKWebView? {
didSet {
oldValue?.removeFromSuperview()
oldValue?.configuration.userContentController.removeScriptMessageHandler(forName: "message5")
}
}
}
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
Javascript:
testFunct();
function testFunct() {
window.webkit.messageHandlers.message5.postMessage("nice" )
}
Error:
failure(Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined is not an object (evaluating 'window.webkit.messageHandlers'), WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A JavaScript exception occurred})
In addition for example here I have some changes of code but for example I never receive in method userContentController the message. Where I set the window.webkit.messageHandlers in the html.
import WebKit
import UIKit
class FingerprintJs : NSObject, WKScriptMessageHandler, WKNavigationDelegate{
public func tryFinger(){
getWebView()
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
assert(message.name == "myMessageName")
}
private func getWebView(){
if let viewController = UIWindow.key?.rootViewController {
if Thread.isMainThread {
let webView = self.makeWebView(viewController: viewController as! ViewController)
webView.loadHTMLString(getHtml(), baseURL: nil)
// webView.evaluateJavaScript(getJavaScript()!,completionHandler:{(result , error) in
// if error == nil {
// print("success")
// print(result as Any)
// }
// else {
// print("error in executing js ", error!)
// }
// })
} else {
print("Llegaste aca? ")
}
}else{
NSLog("The UIApplication keyWindow of the rootViewController must be loaded first", "")
}
}
private func makeWebView(viewController: ViewController) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "myMessageName")
config.preferences = preferences
let webView = WKWebView(frame: .init(x: 1.0, y: 1.0, width: 0, height: 0),
configuration: config)
webView.translatesAutoresizingMaskIntoConstraints = false
viewController.view.addSubview(webView)
webView.navigationDelegate = self
return webView
}
private func getJavaScript() -> String?{
var javaScript: String? = nil
if let jsSourcePath = Bundle.main.path(forResource: "ejemplo", ofType: "js") {
do {
javaScript = try String(contentsOfFile: jsSourcePath)
}
catch {
NSLog("getJavaScriptString: "+(error.localizedDescription), "")
}
}
return javaScript
}
private func getHtml() -> String {
let html: String =
"""
<html>
<body>
<script>
window.webkit.messageHandlers.myMessageName.postMessage("message")
</script>
</body>
</html>
"""
return html
}
private var webView: WKWebView? {
didSet {
oldValue?.removeFromSuperview()
oldValue?.configuration.userContentController.removeScriptMessageHandler(forName: "message5")
}
}
}
extension UIWindow {
static var key: UIWindow? {
if #available(iOS 13, *) {
return UIApplication.shared.windows.first { $0.isKeyWindow }
} else {
return UIApplication.shared.keyWindow
}
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("testFunct()")
}
try calling the JS method from didFinish navigation method once the webView is loaded. It might be due to calling the JS method before the webView is properly loaded.
I am trying to intercept a POST request before opening an HTML page using webView.
I followed the tutorial on: https://medium.com/#madmuc/intercept-all-network-traffic-in-webkit-on-android-9c56c9262c85
which injects the javascript file through a .js file in the assets folder:
XMLHttpRequest.prototype.origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// these will be the key to retrieve the payload
this.recordedMethod = method;
this.recordedUrl = url;
this.origOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
// interceptor is a Kotlin interface added in WebView
if(body) recorder.recordPayload(this.recordedMethod, this.recordedUrl, body);
this.origSend(body);
};
However, the open and send are never called, I added logs to test and if I move the recorder.recordPayload(...) (which is the method in the javascript interface) out of those functions then recordPayload is called, so the problem is not with the interface or reading from the file.
webView class:
class TestWebView : WebView {
private var urlReturned = false
private var postbackUrl: String? = "https://www.xxxxx.com"
private var postbackHandled = false```
lateinit var recorder: PayloadRecorder
lateinit var payload: String
constructor(context: Context?) : super(context) {
initUI()
}
private fun initUI() {
if (!isInEditMode) {
settings.javaScriptEnabled = true
settings.builtInZoomControls = true
settings.displayZoomControls = false
settings.loadWithOverviewMode = true
settings.useWideViewPort = true
recorder = PayloadRecorder()
addJavascriptInterface(recorder, "recorder")
evaluateJavascript(
context.assets.open("override.js").reader().readText(), null
)
}
val client: WebViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
val payload = recorder.getPayload(request.method, request.url.toString())
// handle the request with the given payload and return the response
return super.shouldInterceptRequest(view, request)
}
override fun onPageStarted(
view: WebView,
url: String,
favicon: Bitmap?
) {
Log.i("testURL", url)
evaluateJavascript(
context.assets.open("override.js").reader().readText(),
object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
Log.i("onReceiveValue", value)
}
}
)
}
}
setWebChromeClient(object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean {
Log.d("WebView", consoleMessage.message())
return true
}
})
}
constructor(context: Context?, attrs: AttributeSet?) : super(
context,
attrs
) {
initUI()
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyle: Int
) : super(context, attrs, defStyle) {
initUI()
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyle: Int,
privateBrowsing: Boolean
) : super(context, attrs, defStyle) {
initUI()
}
class PayloadRecorder {
private val payloadMap: MutableMap<String, String> =
mutableMapOf()
#JavascriptInterface
fun recordPayload(
method: String,
url: String,
payload: String
) {
payloadMap["$method-$url"] = payload
Log.i("payloadRecorder", "recordPayLoad")
}
fun getPayload(
method: String,
url: String
): String? =
payloadMap["$method-$url"]
}
/**
* The function that is called and starts the html progress
*/
#JvmOverloads
fun process(
acsUrl: String?,
md: String?,
paReq: String?,
postbackUrl: String? = null
) {
urlReturned = false
postbackHandled = false
if (!TextUtils.isEmpty(postbackUrl)) {
this.postbackUrl = postbackUrl
}
val postParams: String
payload = paReq?: ""
postParams = //my code
postUrl(acsUrl, postParams.toByteArray())
}
}
recordPayload is never called therefore in shouldInterceptRequest payload always comes back null.
Is there any way to print logs that is created by "console.log" in web page in UIWebView or WKWebView in swift (like Chrome F12.)
Have a nice days.
Here's a basic implementation for the UIWebView:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create the web view.
let webView = UIWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
webView.delegate = self
webView.loadRequest(URLRequest(url: URL(string: "https://www.google.com.py")!))
}
}
extension ViewController: UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
let js = "console.log = function() {window.location = 'logger://webview?' + JSON.stringify(Array.prototype.slice.call(arguments))}"
webView.stringByEvaluatingJavaScript(from: js)
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if request.url?.scheme == "logger" {
guard let data = request.url?.query?.removingPercentEncoding?.data(using: .utf8) else { return true }
guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) else { return true }
guard let jsonData = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) else { return true }
guard let json = String(data: jsonData, encoding: .utf8) else { return true }
print(json)
}
return true
}
}
Example
console.log(4, 3.53, 'Hello', {d: {f: 4}}, function() {}, undefined, null, true)
Prints the following in Xcode log:
[
4,
3.5299999999999998,
"Hello",
{
"d" : {
"f" : 4
}
},
null,
null,
null,
true
]
Caveats
This only prints logs executed after the page has loaded.
Since we're using JSON.stringify, we can't print the following types: function, undefined
Since we're using JSON.stringify, true and false are
printed as 1 and 0, respectively
I have been using WKWebView.
I would like to work javascript on iOS9+Swift 2.0.
But It couldn't work.
console.log("hoge") couldn't even work.
My code is here
import UIKit
class CommonWebVCL: GABase,UIWebViewDelegate,UIScrollViewDelegate,WKNavigationDelegate,WKUIDelegate {
var webview: WKWebView!
var str_url:String! = ""
var url:String!
var gobackSearch:Bool! = false
var previousScrollViewYOffset:CGFloat? = 0
var toolbarheight:CGFloat = 0
var analytics_category:String!
override func viewDidLoad() {
super.viewDidLoad()
changeUserAgent()
let webViewConfiguration = WKWebViewConfiguration()
self.webview = WKWebView(frame: self.view.bounds, configuration: webViewConfiguration)
self.webview.navigationDelegate = self
self.webview.UIDelegate = self
addObserverForWKWebView()
self.view.addSubview(webview)
setBackButton()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationBarInit()
//タブの遷移でdelegateが外れるので貼り直す
self.webview.scrollView.delegate = self
}
func setBackBtnForWeb(){
let leftbtn = UIButton(frame: CGRect(x: 0, y: 0, width: 48, height: 22))
leftbtn.setImage(UIImage(named: "back_btn"), forState: .Normal)
leftbtn.addTarget(self, action: "goBackForWeb:", forControlEvents: UIControlEvents.TouchUpInside)
let backButton = UIBarButtonItem(customView: leftbtn)
self.navigationItem.setLeftBarButtonItem(backButton, animated: true)
}
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!){
// インジケータを表示する
if let tmpstr_url = self.webview.URL?.absoluteString{
if (tmpstr_url.rangeOfString("tel") == nil){
SVProgressHUD.show()
}
}
}
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!){
// インジケータを表示する
if SVProgressHUD.isVisible(){
SVProgressHUD.dismiss()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationBarInit()
let URL = NSURL(string: url)
let urlRequest: NSURLRequest = NSURLRequest(URL: URL!)
self.webview.loadRequest(urlRequest)
}
override func viewDidLayoutSubviews() {
self.webview.frame = self.view.bounds
navigationBarInit()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
if SVProgressHUD.isVisible(){
SVProgressHUD.dismiss()
}
if (self.gobackSearch != nil){
sendEvent(analytics_category, str_action: "back")
self.navigationController?.popViewControllerAnimated(true)
}
self.webview.scrollView.delegate = nil
self.webview.navigationDelegate = nil
self.webview.stopLoading()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
func addObserverForWKWebView(){
self.webview.addObserver(self, forKeyPath: "canGoBack", options: NSKeyValueObservingOptions.New, context: nil) // WEBページで戻ることができるか監視のため、Observerに追加
self.webview.addObserver(self, forKeyPath: "URL", options: NSKeyValueObservingOptions.New, context: nil) // WEBページで戻ることができるか監視のため、Observerに追加
}
// Observerで監視対象のプロパティに変更があったときの処理
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if object!.isEqual(webview){
if keyPath == "estimatedProgress"{
self.navigationController?.setSGProgressMaskWithPercentage(Float(self.webview.estimatedProgress*100))
navigationBarInit()
}else if keyPath == "canGoBack"{
}else if keyPath == "URL"{
if let tmpstr_url = self.webview.URL?.absoluteString{
self.str_url = tmpstr_url
handleUrl()
}
}
}
}
//URLを見て何かするところ
func handleUrl(){
}
// 画面を閉じるときにObserverを削除。削除しないとアプリ落ちる
deinit{
if self.webview != nil{
//self.webview.removeObserver(self, forKeyPath: "estimatedProgress", context: nil)
self.webview.removeObserver(self, forKeyPath: "canGoBack", context: nil)
self.webview.removeObserver(self, forKeyPath: "URL", context: nil)
}
}
#IBAction func goBackForWeb(sender: AnyObject) {
if self.webview.canGoBack{
webview.goBack()
}else{
self.navigationController?.popViewControllerAnimated(true)
}
}
func webViewDidStartLoad(webView: UIWebView){
SVProgressHUD.showWithStatus("読み込み中")
navigationBarInit()
}
func webViewDidFinishLoad(webView: UIWebView){
SVProgressHUD.dismiss()
}
func webView(webView: UIWebView, didFailLoadWithError error: NSError?){
SVProgressHUD.dismiss()
}
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void){
let alertController = UIAlertController(title: frame.request.URL?.host, message: message, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: { action in
completionHandler()
}))
self.presentViewController(alertController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
What is this problem?
By the way this problem is only iOS9 by swift2.0.
It works correctly on other platform including iOS9 by swift 1.2.
I might resolve it by myself.
This web page always use http protocol.
So I changed https protocol,javascript have worked correctly.