WKWebView评估JavaScript返回值

WKWebView evaluate JavaScript return value

本文关键字:返回值 JavaScript 评估 WKWebView      更新时间:2023-09-26

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

现在,我打电话给:

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

但我需要得到像返回值一样的结果,就像在UIWebView中一样。你能提出一个解决方案吗?

更新:这在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);

如果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;
}

我刚刚偶然发现了同样的问题,并为它写了一个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)
        }
    }
}

基于@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)
        }
    }
}

我发现,如果没有异常,那么注入的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);
    }
}];

基于@mort3m的注释。这是工作的Objective-C版本。

@implementation WKWebView(SynchronousEvaluateJavaScript)
- (void)stringByEvaluatingJavaScriptFromString:(NSString *)script completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
{
    __block BOOL finished = FALSE;
    [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
        if (error == nil) {
            if (result != nil) {
                completionHandler(result, error);
            }
        } else {
            completionHandler(NULL, error);
        }
        finished = TRUE;
    }];
    while(!finished) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}
@end

Swift 5.7

你只需要

webView.evaluateJavaScript("your js code") { res, err in 
    // TODO
}

完整的代码片段示例是

let js = """
function add(x, y) {
    return x + y
}
add(1, 2)
"""
webView.evaluateJavaScript(js) { res, err in
    print("res : '(res)") 
}

如果您喜欢异步/等待:

let js = """
function add(x, y) {
    return x + y
}
add(1, 2)
"""
Task {
    let res = try? await webView.evaluate(javascript: js)
    print("res : '(res)")
}
extension WKWebView {
    
    @discardableResult
    func evaluate(javascript: String) async throws -> Any {
        return try await withCheckedThrowingContinuation({ continuation in
            evaluateJavaScript(javascript, in: nil, in: .page) { result in
                switch result {
                case .success(let output):
                    continuation.resume(returning: output)
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
        })
    }
}

只有这一点有效,以上答案对我无效。

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

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