Javascript console.log() in an iOS UIWebView

JavascriptIosUiwebview

Javascript Problem Overview


When writing a iPhone / iPad app with a UIWebView, the console isn't visible. this excellent answer shows how to trap errors, but I would like to use the console.log() as well.

Javascript Solutions


Solution 1 - Javascript

After consulting with an esteemed colleague today he alerted me to the Safari Developer Toolkit, and how this can be connected to UIWebViews in the iOS Simulator for console output (and debugging!).

Steps:

  1. Open Safari Preferences -> "Advanced" tab -> enable checkbox "Show Develop menu in menu bar"
  2. Start app with UIWebView in iOS Simulator
  3. Safari -> Develop -> i(Pad/Pod) Simulator -> [the name of your UIWebView file]

You can now drop complex (in my case, flot) Javascript and other stuff into UIWebViews and debug at will.

EDIT: As pointed out by @Joshua J McKinnon this strategy also works when debugging UIWebViews on a device. Simply enable Web Inspector on your device settings: Settings->Safari->Advanced->Web Inspector (cheers @Jeremy Wiebe)

UPDATE: WKWebView is supported too

Solution 2 - Javascript

I have a solution to log, using javascript, to the apps debug console. It's a bit crude, but it works.

First, we define the console.log() function in javascript, which opens and immediately removes an iframe with a ios-log: url.

// Debug
console = new Object();
console.log = function(log) {
  var iframe = document.createElement("IFRAME");
  iframe.setAttribute("src", "ios-log:#iOS#" + log);
  document.documentElement.appendChild(iframe);
  iframe.parentNode.removeChild(iframe);
  iframe = null;	
};
console.debug = console.log;
console.info = console.log;
console.warn = console.log;
console.error = console.log;

Now we have to catch this URL in the UIWebViewDelegate in the iOS app using the shouldStartLoadWithRequest function.

- (BOOL)webView:(UIWebView *)webView2 
shouldStartLoadWithRequest:(NSURLRequest *)request 
 navigationType:(UIWebViewNavigationType)navigationType {
    
    NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
    //NSLog(requestString);
    
    if ([requestString hasPrefix:@"ios-log:"]) {
        NSString* logString = [[requestString componentsSeparatedByString:@":#iOS#"] objectAtIndex:1];
                               NSLog(@"UIWebView console: %@", logString);
		return NO;
	}
    
    return YES;
}

Solution 3 - Javascript

Here's the Swift solution: (It's a bit of a hack to get the context)

  1. You create the UIWebView.

  2. Get the internal context and override the console.log() javascript function.

     self.webView = UIWebView()
     self.webView.delegate = self
    
     let context = self.webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as! JSContext
    
     let logFunction : @convention(block) (String) -> Void =
     {
         (msg: String) in
    
         NSLog("Console: %@", msg)
     }
     context.objectForKeyedSubscript("console").setObject(unsafeBitCast(logFunction, AnyObject.self), 
                                                          forKeyedSubscript: "log")
    

Solution 4 - Javascript

Starting from iOS7, you can use native Javascript bridge. Something as simple as following

 #import <JavaScriptCore/JavaScriptCore.h>

JSContext *ctx = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
ctx[@"console"][@"log"] = ^(JSValue * msg) {
NSLog(@"JavaScript %@ log message: %@", [JSContext currentContext], msg);
	};

Solution 5 - Javascript

NativeBridge is very helpful for communicating from a UIWebView to Objective-C. You can use it to pass console logs and call Objective-C functions.

https://github.com/ochameau/NativeBridge

console = new Object();
console.log = function(log) {
	NativeBridge.call("logToConsole", [log]);
};
console.debug = console.log;
console.info = console.log;
console.warn = console.log;
console.error = console.log;

window.onerror = function(error, url, line) {
    console.log('ERROR: '+error+' URL:'+url+' L:'+line);
};

The advantage of this technique is that things like newlines in log messages are preserved.

Solution 6 - Javascript

Swift 5

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
      webView.evaluateJavaScript("your javascript string") { (value, error) in
          if let errorMessage = (error! as NSError).userInfo["WKJavaScriptExceptionMessage"] as? String {
                print(errorMessage)
          }
      }
 }

Solution 7 - Javascript

Tried Leslie Godwin's fix but was getting this error:

'objectForKeyedSubscript' is unavailable: use subscripting

For Swift 2.2, here's what worked for me:

You will need to import JavaScriptCore for this code to compile:

import JavaScriptCore

if let context = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") {
    context.evaluateScript("var console = { log: function(message) { _consoleLog(message) } }")
    let consoleLog: @convention(block) String -> Void = { message in
        print("javascript_log: " + message)
    }
    context.setObject(unsafeBitCast(consoleLog, AnyObject.self), forKeyedSubscript: "_consoleLog")
}

Then in your javascript code, calling console.log("your_log") will print in Xcode console.

Better yet, add this code as an extension to UIWebView:

import JavaScriptCore

extension UIWebView {
    public func hijackConsoleLog() {
        if let context = valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") {
            context.evaluateScript("var console = { log: function(message) { _consoleLog(message) } }")
            let consoleLog: @convention(block) String -> Void = { message in
                print("javascript_log: " + message)
            }
            context.setObject(unsafeBitCast(consoleLog, AnyObject.self), forKeyedSubscript: "_consoleLog")
        }
    }
}

And then call this method during your UIWebView initialization step:

let webView = UIWebView(frame: CGRectZero)
webView.hijackConsoleLog()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTinkerTankView Question on Stackoverflow
Solution 1 - JavascriptNSTJView Answer on Stackoverflow
Solution 2 - JavascriptTinkerTankView Answer on Stackoverflow
Solution 3 - JavascriptLeslie GodwinView Answer on Stackoverflow
Solution 4 - JavascriptJi FangView Answer on Stackoverflow
Solution 5 - Javascripttangent405View Answer on Stackoverflow
Solution 6 - JavascriptBarathView Answer on Stackoverflow
Solution 7 - JavascriptparagView Answer on Stackoverflow