WKWebView评估JavaScript返回值

WKWebView评估JavaScript返回值,javascript,ios,objective-c,wkwebview,Javascript,Ios,Objective C,Wkwebview,我需要将一个函数从UIWebView更改为WKWebView来评估JavaScript。我需要返回这个函数中的求值结果 现在,我打电话: [wkWebView evaluateJavaScript:call completionHandler:^(NSString *result, NSError *error) { NSLog(@"Error %@",error); NSLog(@"Result %@",result); }]; 但我需要得到返回值这样的结果,比如在UIWeb

我需要将一个函数从UIWebView更改为WKWebView来评估JavaScript。我需要返回这个函数中的求值结果

现在,我打电话:

[wkWebView evaluateJavaScript:call completionHandler:^(NSString *result, NSError *error)
{
    NSLog(@"Error %@",error);
    NSLog(@"Result %@",result);
}];
但我需要得到返回值这样的结果,比如在
UIWebView
中。
您能提出解决方案吗?

更新:这在iOS 12+上不再有效。


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

我使用nsrunlop等待,但我不确定这是否是最好的方式

下面是我现在使用的类别扩展源代码:

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

如果javascript代码出现N错误,此解决方案也可以工作:

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

我只是偶然发现了同样的问题,并为此编写了一个小的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)
        }
    }
}

我发现注入的javascript中final语句的值是作为id参数传递给completion函数的返回值(如果没有异常)。例如:

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

可以使用调度信号量。它在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. 

根据@mort3m的回答,这里是一个使用Swift 5的WKWebView扩展

扩展WKWebView{ func求值(脚本:字符串,完成:@escaping(Any?,Error?)->Void){ var finished=false evaluateJavaScript(脚本,completionHandler:{(结果,错误)在 如果错误==nil{ 如果结果为!=nil{ 完成(结果为零) } }否则{ 完成(无,错误) } 完成=正确 }) 一会儿!完成了{ RunLoop.current.run(模式:RunLoop.mode(rawValue:“NSDefaultRunLoopMode”),在:NSDate.distantFuture之前) } } }

NSString*returnVal=[self.webView stringByEvaluatingJavaScriptFromString:@“func(\“arg\”)”这个功能不工作吗?不,这个功能在UIWebView中并且正在工作,我需要将它更改为WKWebView。我可以通过一些回调来解决它,但是在我的项目中它太复杂了。。。奇怪,控制台输出什么?NSLOGS之后,这段代码的问题是,如果出现JS错误,您的本机函数将无限运行!解决方案是检查块安全布尔值,而不是结果字符串。在使用多个wkwebview时,查看下方将导致崩溃;这个解决方案不再有效了。回调现在从主线程调用,如果将主线程锁定在
while
循环中(如本解决方案中),则永远不会调用回调处理程序
evaluateJavaScript
等待主线程释放,但它从未发生,因为它锁定在循环中。对iOS 12或iOS 13有什么想法吗?此解决方案在iOS 13中不起作用。有人有iOS 13的工作解决方案吗?我不能使用你的代码,因为运行循环。我的编译器找不到RunLoop变量。我确实导入了WKWebKIT和基金会…当我使用NSRunLoop.currentRunLoop().run()时,它会一直运行,我不能说要运行多长时间。我做错了什么?我使用了:NSRunLoop.currentRunLoop().runMode(“NSDefaultRunLoopMode”,beforeDate:NSDate.distantFuture())这有效!谢谢你的回答!!!抱歉,如果您使用回调,这有什么意义?@Shizam您可能愿意确保首先加载WKWebview。当您试图检查文档是否准备好时,这种方法实际上不起作用,同时,它只适合于提取一些内部HTML。文档中说:“完成处理程序总是在主线程上运行。”因此,如果在主线程上调用此代码,它将永远阻塞。在典型的
webView.evaluateJavaScript(jsCode){(值,错误)中如何使用此代码在
语句中?你必须使用这个新函数
evaluate
,而不是
evaluateJavaScript
。我完全没有意识到它只是
evaluate
。我想是深夜吧。代码工作得很好,顺便说一句!嘿@Nicolas,我在这段代码中遇到了新的崩溃错误(我相信是由Swift 5引入的)@JustinBush我和Swift 5合作得很好。你的错误是什么?