Javascript console.log() in an iOS UIWebView

78,455

Solution 1

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 2

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 3

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

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

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.

Share:
78,455
TinkerTank
Author by

TinkerTank

Updated on November 12, 2020

Comments

  • TinkerTank
    TinkerTank over 3 years

    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.

  • mpontillo
    mpontillo almost 11 years
    +1. Note to Apache Cordova users - Cordova already handles console.log, but the window.onerror function in this answer is very useful!
  • Joshua J. McKinnon
    Joshua J. McKinnon over 10 years
    Note, this strategy also works when debugging on real iOS devices.
  • Andy Novocin
    Andy Novocin over 10 years
    +100 if I could. This is wonderful, it works for phone gap apps too!
  • Roderic Campbell
    Roderic Campbell over 10 years
    This should be made the answer. This is wonderful and I agree with the above, this should get all the up votes
  • Floydian
    Floydian over 10 years
    Can anyone tell me please how can it be done with iOS devices?
  • NSTJ
    NSTJ over 10 years
    @Floydian what seems to be the problem?
  • Floydian
    Floydian about 10 years
    Trying with an iPad, when I go to the develop menu on Safari, there are no Devices to choose. When I deploy on the simulator, it works like a charm.
  • Jeremy Wiebe
    Jeremy Wiebe about 10 years
    @Floydian you have to enable Web Inspector on the device. Settings->Safari->Advanced->Web Inspector.
  • Ashwin S
    Ashwin S about 10 years
    see the simple idea of NSTJ below.
  • juanpaco
    juanpaco over 9 years
    Whenever I set a breakpoint in a JS file this way, the debugger breaks there, but then the step buttons don't work. They don't respond to clicks. has anyone else seen this?
  • Leslie Godwin
    Leslie Godwin over 8 years
    Out of interest, where is the ideal place to put this code?
  • Leslie Godwin
    Leslie Godwin over 8 years
    OK, figured it out. Just after you created the UIWebview you can setup any JSContext stuff.
  • mindbomb
    mindbomb over 8 years
    +100! saved me TONS of time, great hack, requires 0 changes in JS code. Thanks!! Just my 2 cents for future readers: don't forget to link JavaScriptCore framework to your project and import it in your webview swift file.
  • narco
    narco over 8 years
    my app is not showing up in my develop menu. I have enabled web inspector. Safari shows up, but my app (which is current;y displaying 2 UIWebviews) is not detected.. any ideas?
  • Nikolai Samteladze
    Nikolai Samteladze over 8 years
    Does JSContext still work in iOS 8+ with WKWebView?
  • ar34z
    ar34z over 8 years
    Don't miss out on @Floydian comment: you need to enable Web Inspector on the device as well.
  • Artur Bartczak
    Artur Bartczak about 8 years
    I put it into - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType and it works perfectly!
  • Byters
    Byters over 6 years
    For Appcelerator/Titanium developers: this too works to debug your Ti.UI.WebView
  • Konstantinos Natsios
    Konstantinos Natsios about 6 years
    in swift 4 maybe? :D
  • Serge
    Serge over 5 years
    Works for me with Swift 4... you haveto cast "log" to NSString..context.objectForKeyedSubscript("console").setObje‌​ct(unsafeBitCast(log‌​Function, to: AnyObject.self), forKeyedSubscript: "log" as NSString)
  • Curtis
    Curtis over 5 years
    Any way to get this to work in the simulator? I don't have i(Pad/Pod) Simulator in my Develop menu in Safari.
  • NSTJ
    NSTJ over 5 years
    @Curtis it should definitely work in the Simulator+Safari - are you still having the issue?
  • Curtis
    Curtis over 5 years
    Yes I have Xcode 10.1, and when I run my app in the simulator and go to Safari and click Develop, there is no Simulator option. If I run on my iPhone, it does show that iPhone in the Develop menu.
  • testing
    testing over 5 years
    @NikolaiSamteladze: I tried with WKWebView and iOS 11.4.1 and he can't find documentView and crashes. I saw this answer and it seems that, it isn't possible this way.
  • maxi-code
    maxi-code about 5 years
    @Curtis I had that issue too. It's working for me now. It's strange but I have to open a new instance of safari and also close the simulator and run the app again