Javascript console.log () em um iOS UIWebView

84

Ao escrever um aplicativo para iPhone / iPad com UIWebView, o console não fica visível. esta excelente resposta mostra como interceptar erros, mas eu gostaria de usar o console.log () também.

TinkerTank
fonte
1
Escreva-o no navegador primeiro, ative as Ferramentas do desenvolvedor e, em seguida, observe a saída do console.
beatgammit

Respostas:

185

Depois de consultar um estimado colega hoje, ele me alertou sobre o Safari Developer Toolkit e como ele pode ser conectado a UIWebViews no simulador iOS para saída de console (e depuração!).

Passos:

  1. Abra as Preferências do Safari -> guia "Avançado" -> habilite a caixa de seleção "Mostrar menu de desenvolvimento na barra de menu"
  2. Inicie o aplicativo com UIWebView no simulador iOS
  3. Safari -> Desenvolver -> Simulador i (Pad / Pod) -> [the name of your UIWebView file]

Agora você pode colocar Javascript complexo (no meu caso, flot ) e outras coisas em UIWebViews e depurar à vontade.

EDIT: Como apontado por @Joshua J McKinnon, essa estratégia também funciona ao depurar UIWebViews em um dispositivo. Basta habilitar o Web Inspector nas configurações do seu dispositivo: Configurações-> Safar-> Avançado-> Web Inspector (elogios @Jeremy Wiebe)

ATUALIZAÇÃO: WKWebView também é compatível

NSTJ
fonte
13
Observe que essa estratégia também funciona ao depurar em dispositivos iOS reais.
Joshua J. McKinnon
2
+100 se eu pudesse. Isso é maravilhoso, funciona também para aplicativos de abertura de telefone!
Andy Novocin
2
Tentando com um iPad, quando vou para o menu de desenvolvimento no Safari, não há dispositivos para escolher. Quando implanto no simulador, funciona perfeitamente.
Floydian
10
@Floydian você deve habilitar o Web Inspector no dispositivo. Configurações-> Safar-> Avançado-> Web Inspector.
Jeremy Wiebe
2
meu aplicativo não está aparecendo no menu de desenvolvimento. Eu habilitei o inspetor da web. O Safari aparece, mas meu aplicativo (que é atual; exibindo 2 UIWebviews) não foi detectado .. alguma ideia?
narco
82

Eu tenho uma solução para fazer login, usando javascript, no console de depuração de aplicativos. É um pouco grosseiro, mas funciona.

Primeiro, definimos a função console.log () em javascript, que abre e remove imediatamente um iframe com um 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;

Agora temos que capturar esse URL no UIWebViewDelegate no aplicativo iOS usando a função shouldStartLoadWithRequest.

- (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;
}
TinkerTank
fonte
1
veja a ideia simples de NSTJ abaixo.
Ashwin S
no Swift 4, talvez? : D
Konstantinos Natsios
35

Esta é a solução do Swift: (é um pouco um hack para obter o contexto)

  1. Você cria o UIWebView.

  2. Obtenha o contexto interno e substitua a função javascript console.log () .

    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")
    
Leslie Godwin
fonte
3
+100! me economizou TONELADAS de tempo, ótimo hack, requer 0 alterações no código JS. Obrigado!! Apenas meus 2 centavos para futuros leitores: não se esqueça de vincular o JavaScriptCoreframework ao seu projeto e importele em seu arquivo swift do webview.
Mindbomb
Funciona para mim com o Swift 4 ... você deve lançar "log" para NSString..context.objectForKeyedSubscript ("console"). SetObject (unsafeBitCast (logFunction, to: AnyObject.self), forKeyedSubscript: "log" como NSString)
Serge
28

A partir do iOS7, você pode usar a ponte Javascript nativa. Algo tão simples como seguir

 #import <JavaScriptCore/JavaScriptCore.h>

JSContext *ctx = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
ctx[@"console"][@"log"] = ^(JSValue * msg) {
NSLog(@"JavaScript %@ log message: %@", [JSContext currentContext], msg);
    };
Ji Fang
fonte
Sem interesse, onde é o local ideal para colocar este código?
Leslie Godwin
OK, descobri. Depois de criar o, UIWebviewvocê pode configurar qualquer JSContextcoisa.
Leslie Godwin
4
Será que JSContextainda funciona em iOS 8+ com WKWebView?
Nikolai Samteladze
Eu coloquei - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationTypee funciona perfeitamente!
Artur Bartczak
@NikolaiSamteladze: Tentei com o WKWebViewiOS 11.4.1 e ele não consegue encontrar documentViewe trava. Eu vi essa resposta e parece que não é possível assim.
teste de
10

NativeBridge é muito útil para a comunicação de um UIWebView para Objective-C. Você pode usá-lo para passar logs de console e chamar funções 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);
};

A vantagem dessa técnica é que coisas como novas linhas nas mensagens de log são preservadas.

tangente 405
fonte
+1. Observação para usuários do Apache Cordova - o Cordova já gerencia console.log, mas a window.onerrorfunção nesta resposta é muito útil!
mpontillo
Para desenvolvedores Appcelerator / Titanium: isso também funciona para depurar seu Ti.UI.WebView
Byters
0

Tentei a correção de Leslie Godwin, mas estava obtendo este erro:

'objectForKeyedSubscript' is unavailable: use subscripting

Para Swift 2.2, aqui está o que funcionou para mim:

Você precisará importar JavaScriptCore para este código compilar:

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

Em seguida, em seu código javascript, chamar console.log ("_ your_log_") imprimirá no console Xcode.

Melhor ainda, adicione este código como uma extensão para 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")
        }
    }
}

Em seguida, chame este método durante a etapa de inicialização do UIWebView:

let webView = UIWebView(frame: CGRectZero)
webView.hijackConsoleLog()
parag
fonte
0

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