WKWebView evaluate JavaScript return value

JavascriptIosObjective CWkwebview

Javascript Problem Overview


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:

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

But I need get result like return value, like in UIWebView. Can you suggest a solution?

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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

Solution 3 - Javascript

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

Solution 4 - Javascript

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

Solution 5 - Javascript

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

Solution 6 - Javascript

Only this works,the answers above not work for me.

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

Solution 7 - Javascript

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. 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionredak105View Question on Stackoverflow
Solution 1 - JavascriptUnknownStackView Answer on Stackoverflow
Solution 2 - JavascriptBramsView Answer on Stackoverflow
Solution 3 - Javascriptmort3mView Answer on Stackoverflow
Solution 4 - JavascriptNicolas MandicaView Answer on Stackoverflow
Solution 5 - JavascriptSteve YostView Answer on Stackoverflow
Solution 6 - Javascriptma saView Answer on Stackoverflow
Solution 7 - JavascriptVlad E. BorovtsovView Answer on Stackoverflow