【问题标题】:WKWebView evaluate JavaScript return valueWKWebView 评估 JavaScript 返回值
【发布时间】:2015-01-02 22:17:58
【问题描述】:

我需要更改一个函数来评估 JavaScript 从 UIWebView 到 WKWebView。我需要在这个函数中返回评估结果。

现在,我正在打电话:

[wkWebView evaluateJavaScript:call completionHandler:^(NSString *result, NSError *error)
{
    NSLog(@"Error %@",error);
    NSLog(@"Result %@",result);
}];

但我需要得到类似返回值的结果,比如UIWebView。 您能提出解决方案吗?

【问题讨论】:

  • NSString *returnVal = [self.webView stringByEvaluatingJavaScriptFromString:@"func(\"arg\")"]; 这不行吗?
  • 没有这个功能在 UIWebView 并且正在工作,我需要将其更改为 WKWebView。我可以通过一些回调来解决它,但是在我的项目中它太复杂了。
  • hm...奇怪,控制台输出什么? NSLogs 之后

标签: javascript ios objective-c wkwebview


【解决方案1】:

更新:这不再适用于 iOS 12+。


我通过等待结果直到返回结果值解决了这个问题。

我使用 NSRunLoop 来等待,但我不确定这是不是最好的方法......

这是我现在使用的分类扩展源代码:

@interface WKWebView(SynchronousEvaluateJavaScript)
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
@end

@implementation WKWebView(SynchronousEvaluateJavaScript)

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
{
    __block NSString *resultString = nil;
    __block BOOL finished = NO;

    [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
        if (error == nil) {
            if (result != nil) {
                resultString = [NSString stringWithFormat:@"%@", result];
            }
        } else {
            NSLog(@"evaluateJavaScript error : %@", error.localizedDescription);
        }
        finished = YES;
    }];

    while (!finished)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    return resultString;
}
@end

示例代码:

NSString *userAgent = [_webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

NSLog(@"userAgent: %@", userAgent);

【讨论】:

  • 这段代码的问题是,如果引发 JS 错误,你的原生函数将无限运行!解决方案是检查块安全布尔值而不是 resultString。往下看
  • 使用多个wkwebview会导致crash;
  • 此解决方案不再有效。现在从主线程调用回调,如果您将主线程锁定在while 循环中(就像在这个解决方案中一样),回调处理程序将永远不会被调用。 evaluateJavaScript 等待主线程释放,但它永远不会发生,因为它锁定在循环中。
  • 对 iOS 12 或 iOS 13 有什么想法吗?
  • 此解决方案在 iOS 13 中不起作用。有没有人有适用于 iOS 13 的解决方案?
【解决方案2】:

如果 javascript 的代码引发 NSError,此解决方案也有效:

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script {
    __block NSString *resultString = nil;
    __block BOOL finished = NO;

    [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
        if (error == nil) {
            if (result != nil) {
                resultString = [NSString stringWithFormat:@"%@", result];
            }
        } else {
            NSLog(@"evaluateJavaScript error : %@", error.localizedDescription);
        }
        finished = YES;
    }];

    while (!finished)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }

    return resultString;
}

【讨论】:

    【解决方案3】:

    我偶然发现了同样的问题,并为它写了一个小的 Swift (3.0) WKWebView 扩展,我想我可能会分享它:

    extension WKWebView {
        func evaluate(script: String, completion: (result: AnyObject?, error: NSError?) -> Void) {
            var finished = false
    
            evaluateJavaScript(script) { (result, error) in
                if error == nil {
                    if result != nil {
                        completion(result: result, error: nil)
                    }
                } else {
                    completion(result: nil, error: error)
                }
                finished = true
            }
    
            while !finished {
                RunLoop.current().run(mode: .defaultRunLoopMode, before: Date.distantFuture)
            }
        }
    }
    

    【讨论】:

    • 由于 RunLoop,我无法使用您的代码。我的编译器找不到 RunLoop 变量。我确实导入了 WKWebKit 和 Foundation ......当我使用 NSRunLoop.currentRunLoop().run() 时,它会继续运行,我不能说运行多长时间。我做错了什么?
    • 我用过:NSRunLoop.currentRunLoop().runMode("NSDefaultRunLoopMode", beforeDate: NSDate.distantFuture()) 这有效!谢谢你的回答!!!
    • 抱歉,如果您仍然使用回调,这有什么意义呢?
    【解决方案4】:

    基于@mort3m 的回答,这是一个使用 Swift 5 的 WKWebView 扩展。

    extension WKWebView {
        func evaluate(script: String, completion: @escaping (Any?, Error?) -> Void) {
            var finished = false
    
            evaluateJavaScript(script, completionHandler: { (result, error) in
                if error == nil {
                    if result != nil {
                        completion(result, nil)
                    }
                } else {
                    completion(nil, error)
                }
                finished = true
            })
    
            while !finished {
                RunLoop.current.run(mode: RunLoop.Mode(rawValue: "NSDefaultRunLoopMode"), before: NSDate.distantFuture)
            }
        }
    }
    

    【讨论】:

    • 如何在典型的webView.evaluateJavaScript(jsCode) { (value, error) in 语句中使用此代码?
    • 你必须使用这个新功能evaluate而不是evaluateJavaScript
    • 我完全错过了它只是evaluate。我想是深夜。代码运行良好,顺便说一句!
    • 嘿@Nicolas,我在这段代码中遇到了新的崩溃错误(我相信是 Swift 5 引入的)。
    • @JustinBush 对我来说,它与 Swift 5 配合得很好。你的错误是什么?
    【解决方案5】:

    我发现你注入的 javascript 中的 final 语句的值是作为 id 参数传递给完成函数的返回值,如果没有异常的话。所以,例如:

    [self.webview evaluateJavaScript:@"var foo = 1; foo + 1;" completionHandler:^(id result, NSError *error) {
        if (error == nil)
        {
            if (result != nil)
            {
                NSInteger integerResult = [result integerValue]; // 2
                NSLog(@"result: %d", integerResult);
            }
        }
        else
        {
            NSLog(@"evaluateJavaScript error : %@", error.localizedDescription);
        }
    }];
    

    【讨论】:

      【解决方案6】:

      只有这个有效,上面的答案对我不起作用。

      - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
      {
          __block NSString *resultString = nil;
          dispatch_semaphore_t sem = dispatch_semaphore_create(0);
          [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
              if (error == nil) {
                  if (result != nil) {
                      resultString = [NSString stringWithFormat:@"%@", result];
                  }
              } else {
                  NSLog(@"evaluateJavaScript error : %@", error.localizedDescription);
              }
              dispatch_semaphore_signal(sem);
          }];
          dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
          
          return resultString;
      }
      

      【讨论】:

        【解决方案7】:

        可以使用调度信号量。它适用于 iOS12+

        例子:

            __block NSString *resultString = nil;
            dispatch_semaphore_t sem = dispatch_semaphore_create(0);
            [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
                if (error == nil) {
                    if (result != nil) {
                        resultString = [NSString stringWithFormat:@"%@", result];
                        dispatch_semaphore_signal(sem);
                    }
                } else {
                    NSLog(@"evaluateJavaScript error : %@", error.localizedDescription);
                }
                finished = YES;
            }];
        
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        
            //process resultString here. 
        

        【讨论】:

        • @Shizam 您可能愿意确保首先加载您的 WKWebview。当您尝试检查文档是否准备好时,这种方法确实不起作用,同时它对我来说很好,只是为了提取一些内部HTML。
        • 文档说:“完成处理程序总是在主线程上运行。”因此,如果在主线程上调用此代码,它将永远阻塞。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-02
        • 1970-01-01
        • 2010-12-22
        • 2015-11-12
        • 1970-01-01
        • 2016-05-03
        • 1970-01-01
        相关资源
        最近更新 更多