WKWebView evaluate JavaScript return value

后端 未结 6 1365
孤独总比滥情好
孤独总比滥情好 2020-12-04 09:33

I need to change a function to evaluate JavaScript from UIWebView to WKWebView. I need to return result of evaluating in this function.

Now, I am calling:



        
相关标签:
6条回答
  • 2020-12-04 09:44

    This solution also works if the javascript's code raise 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;
    }
    
    0 讨论(0)
  • 2020-12-04 09:50

    Base on @mort3m's answer, here is a WKWebView extension working with Swift 5.

    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)
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-04 09:52

    It's possible to use dispatch semaphore. It works on iOS12+

    Example:

        __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. 
    
    0 讨论(0)
  • 2020-12-04 09:54

    I just stumbled about the same problem and wrote a little Swift (3.0) WKWebView extension for it, thought I might share it:

    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)
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-04 10:03

    Update: This is not working on iOS 12+ anymore.


    I solved this problem by waiting for result until result value is returned.

    I used NSRunLoop for waiting, but I'm not sure it's best way or not...

    Here is the category extension source code that I'm using now:

    @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
    

    Example code:

    NSString *userAgent = [_webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
    
    NSLog(@"userAgent: %@", userAgent);
    
    0 讨论(0)
  • 2020-12-04 10:10

    I've found that the value of final statement in your injected javascript is the return value passed as the id argument to the completion function, if there are no exceptions. So, for example:

    [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);
        }
    }];
    
    0 讨论(0)
提交回复
热议问题