WKWebView evaluate JavaScript return value
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);
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;}
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) } }}