在iOS的UIWebView中使用Javascript的console.log()函数

92
在使用UIWebView编写iPhone/iPad应用程序时,控制台不可见。这个出色的答案展示了如何捕获错误,但我也想使用console.log()。

1
先在浏览器中编写代码,打开开发者工具,然后查看控制台输出。 - beatgammit
1
在这个类似的问题中提供了很多选项:*如何使用PC调试iPad Safari* - 这些选项不需要使用任何本地代码。 - Simon East
7个回答

200

今天与一位受人尊敬的同事商量后,他向我介绍了Safari开发者工具包,以及如何将其与iOS模拟器中的UIWebViews连接起来进行控制台输出(和调试)。

步骤:

  1. 打开Safari首选项->“高级”选项卡->启用复选框“在菜单栏中显示开发菜单”
  2. 使用带有UIWebView的iOS模拟器启动应用程序
  3. Safari->开发->i(Pad/Pod)模拟器->[您的UIWebView文件名称]

现在,您可以将复杂的(在我的情况下是flot)JavaScript和其他内容放入UIWebViews中,并随意进行调试。

编辑:如@Joshua J McKinnon所指出,此策略也适用于在设备上调试UIWebViews。只需在设备设置中启用Web检查器:设置->Safari->高级->Web检查器(感谢@Jeremy Wiebe)

更新:WKWebView也得到支持。


13
注意,这种策略在调试真实的iOS设备时同样适用。 - Joshua J. McKinnon
3
如果可以,给我加100分。太棒了,它也适用于PhoneGap应用程序! - Andy Novocin
2
尝试使用iPad时,在Safari的开发菜单中没有可选择的设备。但在模拟器上部署时,一切都很顺利。 - Floydian
11
@Floydian 你需要在设备上启用Web检查器。设置->Safari->高级->Web检查器。 - Jeremy Wiebe
2
我的应用程序没有显示在开发菜单中。我已经启用了Web检查器。Safari可以显示,但是我的应用程序(目前正在显示2个UIWebviews)没有被检测到...有什么想法吗? - narco
显示剩余9条评论

82

我有一个使用JavaScript记录应用程序调试控制台的解决方案。这有点粗糙,但它有效。

首先,我们在JavaScript中定义console.log()函数,该函数打开并立即删除带有ios-log:URL的iframe。

// 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;

现在我们需要在iOS应用程序中使用shouldStartLoadWithRequest函数在UIWebViewDelegate中捕获这个URL。

- (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;
}

1
请查看NSTJ的简单想法。 - Ashwin S
可能是在Swift 4中吧? :D - Konstantinos Natsios

37

下面是 Swift 解决方案: (这是一个有点 hack 的方法来获取上下文)

  1. 创建 UIWebView。

  2. 获取内部上下文并覆盖 console.log() JavaScript 函数。

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")

3
+100!这个超级技巧为我节省了大量时间,而且完全不需要更改JS代码。谢谢!!给未来的读者提个建议:别忘了将“JavaScriptCore”框架链接到你的项目中,并在你的WebView Swift文件中进行“import”。 - mindbomb
在Swift 4中,这对我有效...你需要将"log"转换为NSString。context.objectForKeyedSubscript("console").setObject(unsafeBitCast(logFunction, to: AnyObject.self), forKeyedSubscript: "log" as NSString) - Serge

28

从iOS7开始,您可以使用原生的Javascript桥接。像下面这样简单。

 #import <JavaScriptCore/JavaScriptCore.h>

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

有趣的是,把这段代码放在哪个位置最理想? - Leslie Godwin
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Leslie Godwin
4
JSContext 仍然能够在 iOS 8+ 中与 WKWebView 一起使用吗? - Nikolai Samteladze
我将它放入了 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 中,它完美地运行了! - Artur Bartczak
@NikolaiSamteladze:我尝试使用WKWebView和iOS 11.4.1,但它找不到documentView并崩溃了。我看到了这个答案,似乎这种方式不可行。 - testing

9

NativeBridge是一款非常有用的工具,可以在UIWebView和Objective-C之间进行通信。您可以使用它将控制台日志传递给Objective-C函数并调用它。

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);
};

这种技术的优点是能够保留日志消息中的换行符等内容。

注意:对于Apache Cordova用户来说,Cordova已经处理了console.log,但是这个答案中的window.onerror函数非常有用! - mpontillo
对于Appcelerator/Titanium开发人员:这个工具也适用于调试您的Ti.UI.WebView。 - Byters

1

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)
          }
      }
 }

这段代码应该放在哪里? - Yaroslav Dukal

0
尝试了Leslie Godwin的修复方法,但是出现了以下错误:
'objectForKeyedSubscript' is unavailable: use subscripting

对于Swift 2.2,以下是我测试通过的代码:

您需要导入JavaScriptCore才能编译此代码:

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")
}

然后在你的JavaScript代码中,调用console.log("_your_log_")将会在Xcode控制台中打印。

更好的方法是将此代码作为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")
        }
    }
}

并且在你的UIWebView初始化步骤中调用此方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接