60

I have a module inside my iOS 7+ app which is a UIWebView. The html page loads a javascript that creates custom-shaped buttons (using the Raphaeljs library). With UIWebView, I set delegate to self. The delegate method webView: shouldStartLoadWithRequest: navigationType: is called each time one of my custom button is pressed. The requests should not be handled by the html, but rather by the iOS code. So I used a request convention (read somewhere here on stackoverflow) using "inapp" as the scheme of my requests. I then check for the host and take the appropriate action.

This code works fine on iOS 7. But the web views appear blank on iOS 8 (bug?), so I decided to use WKWebView for iOS 8 devices. The web views now render fine (and amazingly faster!), but my buttons have no effect.

I tried using - (WKNaviation *)loadRequest:(NSURLRequest *)request, but it's not called.

I can't find a direct equivalent of the UIWebView delegate method webView: shouldStartLoadWithRequest: navigationType:. What's the best way of handling those requests with WKWebView?

invalidArgument
  • 2,289
  • 4
  • 24
  • 35

8 Answers8

127

I've been looking for a good explanation myself, but haven't found one. I've used the following in my app and everything seems to work (Edit: updated based on ccoroom's comment):

UIWebViewDelegate     - webView:shouldStartLoadWithRequest:navigationType:
WKNavigationDelegate  - webView:decidePolicyForNavigationAction:decisionHandler:

Here's the other UIWebViewDelegate methods:

UIWebViewDelegate     - webViewDidStartLoad:
WKNavigationDelegate  - webView:didCommitNavigation:

UIWebViewDelegate     - webViewDidFinishLoad:
WKNavigationDelegate  - webView:didFinishNavigation:

UIWebViewDelegate     - webView:didFailLoadWithError:
WKNavigationDelegate  - webView:didFailNavigation:withError:
                      - webView:didFailProvisionalNavigation:withError:

I'd love for someone to confirm this for me though.

Edit: Actually, I've answered the question you had in the title (although I'm no longer confident that webView:didCommitNavigation: is called at the exact same point in the lifecycle), but re-reading your description it looks like what you actually need to know about is how to reimplement a Javascript/Objective-C bridge using WKWebView. So have a look at my other answer.

Community
  • 1
  • 1
SeanR
  • 7,899
  • 6
  • 27
  • 38
  • Maybe I'm missing something, but a major difference between webView:shouldStartLoadWithRequest:navigationType: and webView:didStartProvisionalNavigation: seems to be that the latter does not get the NSUrlRequest. How do you get information about the request? – ajh158 Nov 01 '14 at 05:18
  • @ajh158 You can get the URL from WkWebView.URL, but I'm not sure about other request details. – SeanR Nov 03 '14 at 01:04
  • I wouldn't say your first two are the same, since you cannot evaluate the request and stop it in WKNavigationDelegate. – Travis M. Aug 14 '15 at 19:04
  • 2
    "webView:shouldStartLoadWithRequest:navigationType:"'s counterpart is "webView:decidePolicyForNavigationAction:decisionHandler:" – ccoroom Nov 03 '15 at 13:11
  • This answers the question's title. The question's body is really a separate question, as @SeanR hints at. – Morkrom Dec 23 '15 at 02:55
  • @ajh158 We can get the request from WKNavigationAction (`WKNavigationAction.request`) in `- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;` You should use the above delegate method in place of `webView:shouldStartLoadWithRequest:navigationType:`. – Avinash B Nov 13 '18 at 17:17
75

To answer the original question, the equivalent of webView:shouldStartLoadWithRequest:navigationType: in UIWebView is webView:decidePolicyForNavigationAction:decisionHandler: in WKWebView. These methods are called before each request is made (including the initial request) and provide the ability to allow/disallow it.

robnasby
  • 966
  • 6
  • 11
38

Just use the following method , it's a part of WKNavigationDelegate

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

    NSURLRequest *request = navigationAction.request;
    NSString *url = [[request URL]absoluteString];

    decisionHandler(WKNavigationActionPolicyAllow);
}
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
Benzi Heler
  • 1,138
  • 1
  • 10
  • 18
  • decidePolicyForNavigationAction it's not calling. Do I need to add any thing else to call this method in my javascript side? – Mihir Oza Nov 07 '19 at 10:30
19

Re-reading your description it looks like what you actually need to know about is how to reimplement a Javascript/Objective-C bridge using WKWebView.

I've just done this myself, following the tutorial at http://tetontech.wordpress.com/2014/07/17/objective-c-wkwebview-to-javascript-and-back/ and the info at http://nshipster.com/wkwebkit/

WKWebView has a built-in way of communicating between Javascript and Objective-C/Swift: WKScriptMessageHandler.

First, include the WebKit headers and WKScriptMessageHandler protocol in your view controller's header:

#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@interface ViewController : UIViewController <WKScriptMessageHandler>

@end

The when initialising your WKWebView, you need to configure it with a script message handler. Name it whatever you want, but to me it seems like naming it for your app makes sense.

    WKWebViewConfiguration *theConfiguration = 
          [[WKWebViewConfiguration alloc] init];
    [theConfiguration.userContentController 
          addScriptMessageHandler:self name:@"myApp"];

    _theWebView = [[WKWebView alloc] initWithFrame:self.view.frame 
                      configuration:theConfiguration];
    [_theWebView loadRequest:request];
    [self.view addSubview:_theWebView];

Now, implement userContentController:didReceiveScriptMessage:. This fires when your webview receives a message, so it does the work you were previously doing with webView:shouldStartLoadWithRequest:navigationType:.

- (void)userContentController:(WKUserContentController *)userContentController 
                        didReceiveScriptMessage:(WKScriptMessage *)message {
    NSDictionary *sentData = (NSDictionary *)message.body;
    NSString *messageString = sentData[@"message"];
    NSLog(@"Message received: %@", messageString);
}

You're now ready to receive messages from Javascript. The function call you need to add to your Javascript is this:

window.webkit.messageHandlers.myApp.postMessage({"message":"Hello there"});
SeanR
  • 7,899
  • 6
  • 27
  • 38
  • First, I apologize for the delay. I tested your solution for one of my links, and it worked. Thank you! I will apply the solution everywhere now. Though, I can see my web content on the simulator, but I can't see it on my device. On the device, I still see a blank page. Any idea why? – invalidArgument Oct 19 '14 at 20:27
  • 1
    @invalidArgument Are you loading the web content from the web, or your bundle? Because I've since learned there's a bit of a bad bug in WKWebView for loading local content: http://stackoverflow.com/questions/24882834/wkwebview-not-working-in-ios-8-beta-4 – SeanR Oct 20 '14 at 00:11
  • It is loaded from my bundle. I just posted a question entirely dedicated to the blank page issue: http://stackoverflow.com/q/26455432/873436. That link was very helpful, thanks again. It led to another issue, though. – invalidArgument Oct 20 '14 at 03:40
  • 3
    It's so complicated. Why not use decidePolicyForNavigationAction? – OpenThread Apr 09 '16 at 03:38
  • @OpenThread FWR decidePolicyForNavigationAction does not fire, but userContentController does and it is not, actually, that much complicated. – Boris Gafurov Feb 17 '23 at 01:42
13

In Swift you can do something like this:

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {

    switch navigationAction.request.URLString {
    case "http://action.is.needed/some-action":
        self.someFunc()
        decisionHandler(.Cancel)
        break
    default:
        decisionHandler(.Allow)
        break
    }

}

And this is the link in web page:

<a href="http://action.is.needed/some-action">Hello world!</a>
He Yifei 何一非
  • 2,592
  • 4
  • 38
  • 69
7

for swift 4.2: (taking from Yifei He 何一非. )

 func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {

        let url = navigationAction.request.url
        let urlStr = url?.absoluteString
        switch urlStr {
        case BASE_URL:
            //self.someFunc()
            decisionHandler(.cancel)
            break
        default:
            decisionHandler(.allow)
            break
        }

    }
ingconti
  • 10,876
  • 3
  • 61
  • 48
1

You can add Observer for your WKWebView

 static void* keyValueObservingContext = &keyValueObservingContext;

[webView addObserver:self forKeyPath:@"URL" options:0 context:keyValueObservingContext];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 {

if ([keyPath isEqualToString:@"URL"]){
// do something
  }
}

Don't forget to remove it in viewWillDisappear

-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[webView removeObserver:self forKeyPath:@"URL"];
}
ramzi shadid
  • 81
  • 1
  • 10
0

Using for Delegate methods on migrate for UIWebview to WKWebview

UIWebViewDelegate => WKNavigationDelegate

  1. delegate => NavigationDelegate
  2. didFailLoadWithError => didFailNavigation
  3. webViewDidFinishLoad => didFinishNavigation
  4. webViewDidStartLoad => didStartProvisionalNavigation
  5. shouldStartLoadWithRequest => decidePolicyForNavigationAction
Srinivasan_iOS
  • 972
  • 10
  • 12