WKWebView completionHandler called before dismissal

hariszaman picture hariszaman · Oct 1, 2015 · Viewed 8k times · Source

I am using WKUIDelegate this function to handle javascript alert

-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{

    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Test Alert", nil)
                                                     message:message
                                                    delegate:self
                                           cancelButtonTitle:nil
                                           otherButtonTitles:@"OK", nil] autorelease];

    [alert show];
    completionHandler();
}

According to Apple documentation we should call compeletionHandler() of an alert after OK button is pressed on alert as mentioned here

How to call completionHandler() after press OK button is pressed? If I don't call completionHandler() expection is thrown

**[WKWebViewController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:
    completionHandler:]:
***** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
   reason: 'Completion handler passed to -[WKWebViewController
   webView:runJavaScriptAlertPanelWithMessage:
   initiatedByFrame:completionHandler:] was not called'****

UPDATE:

The solution mentioned below by Stefan is working fine with JS Alert but not with JS Confirm. Following is the code I get same exception even if the completionHandler() is called in ok and cancel button.

-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
{
    MKCLOG_DEBUG(@"%@", frame.request.URL);
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:
                                NSLocalizedString(@"Test", nil) message: message
                                                            preferredStyle: UIAlertControllerStyleAlert];

    UIAlertAction *cancelAction = [UIAlertAction
                                   actionWithTitle:NSLocalizedString(@"Cancel", @"")
                                   style:UIAlertActionStyleCancel
                                   handler:^(UIAlertAction *action)
                                   {
                                       MKCLOG_DEBUG(@"Cancel action");
                                       completionHandler(NO);
                                   }];

    UIAlertAction *okAction = [UIAlertAction
                               actionWithTitle:NSLocalizedString(@"OK", @"OK action")
                               style:UIAlertActionStyleDefault
                               handler:^(UIAlertAction *action)
                               {
                                   MKCLOG_DEBUG(@"OK action");
                                   completionHandler(YES);
                               }];

    [alert addAction:cancelAction];
    [alert addAction:okAction];
}

Answer

Stefan Arentz picture Stefan Arentz · Oct 2, 2015

The way your code is setup now, you display the UIAlertView and immediately run the completionHandler(). Both happen at the same time.

What you should do instead is something like this:

UIAlertController* alert = [UIAlertController alertControllerWithTitle:
    NSLocalizedString(@"Test Alert", nil) message: message
        preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle: @"OK"
    style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
      completionHandler();
}]; 
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];

This will present the alert and call the completionHandler when the user dismisses it.

Note that I am using the UIAlertController, which is only available on iOS 8 and up, but that should be fine since you depend on WKWebView.