Optimize WKWebView click behavior

If your iOS app utilizes WKWebView to display web content, you might want to consider optimizing click behavior for the following reasons:

  • WKWebView doesn't support tabbed browsing. Ad clicks that attempt to open a new tab do nothing by default.

  • Ad clicks that open in the same tab reload the page. You might want to force ad clicks to open outside the WKWebView, for example if you host H5 games and want to maintain the state of each game.

  • AutoFill doesn't support credit card information in WKWebView. This could lead to less ecommerce conversions for advertisers, negatively affecting the web content's monetization.

This guide provides recommended steps to optimize the click behavior in mobile web views while preserving the web view content.

Prerequisites

Implementation

Ad links can have the href target attribute set to either _blank, _top, _self, or _parent. With Ad Manager you can control the target attribute to be _blank or _top by setting ads to open in a new tab or window. Ad links can also contain JavaScript functions such as window.open(url, "_blank").

The following table describes how each of these links behave in a web view.

href target attribute Default WKWebView click behavior
target="_blank" Link not handled by the web view.
target="_top" Reload link in the existing web view.
target="_self" Reload link in the existing web view.
target="_parent" Reload link in the existing web view.
JavaScript function Default WKWebView click behavior
window.open(url, "_blank") Link not handled by the web view.

Follow these steps to optimize the click behavior in your WKWebView instance:

  1. Set the WKUIDelegate on your WKWebView instance.

  2. Set the WKNavigationDelegate on your WKWebView instance.

  3. Determine whether to optimize the behavior of the click URL.

    • Check if the navigationType property on the WKNavigationAction object is a click type you want to optimize. The code snippet checks for .linkActivated which only applies to clicks on a link with an href attribute.

    • Check the targetFrame property on the WKNavigationAction object. If it returns nil, it means the target of the navigation is a new window. Since WKWebView can't handle that click, these clicks must be handled manually.

  4. Decide whether to open the URL in an external browser, SFSafariViewController, or the existing web view. The code snippet shows how to open URLs navigating away from the site by presenting a SFSafariViewController.

Code example

The following code snippet shows how to optimize the web view click behavior. As an example, it checks if the current domain is different than the target domain. This is just one approach as the criteria you use could vary.

Swift

import GoogleMobileAds
import SafariServices
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()

    // ... Register the WKWebView.

    // 1. Set the WKUIDelegate on your WKWebView instance.
    webView.uiDelegate = self;
    // 2. Set the WKNavigationDelegate on your WKWebView instance.
    webView.navigationDelegate = self
  }

  // Implement the WKUIDelegate method.
  func webView(
      _ webView: WKWebView,
      createWebViewWith configuration: WKWebViewConfiguration,
      for navigationAction: WKNavigationAction,
      windowFeatures: WKWindowFeatures) -> WKWebView? {
    guard let url = navigationAction.request.url,
        let currentDomain = webView.url?.host,
        let targetDomain = url.host else { return nil }

    // 3. Determine whether to optimize the behavior of the click URL.
    if didHandleClickBehavior(
        url: url,
        currentDomain: currentDomain,
        targetDomain: targetDomain,
        navigationAction: navigationAction) {
      print("URL opened in SFSafariViewController.")
    }

    return nil
  }

  // Implement the WKNavigationDelegate method.
  func webView(
      _ webView: WKWebView,
      decidePolicyFor navigationAction: WKNavigationAction,
      decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
  {
    guard let url = navigationAction.request.url,
        let currentDomain = webView.url?.host,
        let targetDomain = url.host else { return decisionHandler(.cancel) }

    // 3. Determine whether to optimize the behavior of the click URL.
    if didHandleClickBehavior(
        url: url,
        currentDomain: currentDomain,
        targetDomain: targetDomain,
        navigationAction: navigationAction) {
      return decisionHandler(.cancel)
    }

    decisionHandler(.allow)
  }

  // Implement a helper method to handle click behavior.
  func didHandleClickBehavior(
      url: URL,
      currentDomain: String,
      targetDomain: String,
      navigationAction: WKNavigationAction) -> Bool {
    // Check if the navigationType is a link with an href attribute or
    // if the target of the navigation is a new window.
    guard navigationAction.navigationType == .linkActivated ||
      navigationAction.targetFrame == nil,
      // If the current domain does not equal the target domain,
      // the assumption is the user is navigating away from the site.
      currentDomain != targetDomain else { return false }

    // 4.  Open the URL in a SFSafariViewController.
    let safariViewController = SFSafariViewController(url: url)
    present(safariViewController, animated: true)
    return true
  }
}

Objective-C

@import GoogleMobileAds;
@import SafariServices;
@import WebKit;

@interface ViewController () <WKNavigationDelegate, WKUIDelegate>

@property(nonatomic, strong) WKWebView *webView;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // ... Register the WKWebView.

  // 1. Set the WKUIDelegate on your WKWebView instance.
  self.webView.uiDelegate = self;
  // 2. Set the WKNavigationDelegate on your WKWebView instance.
  self.webView.navigationDelegate = self;
}

// Implement the WKUIDelegate method.
- (WKWebView *)webView:(WKWebView *)webView
  createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
             forNavigationAction:(WKNavigationAction *)navigationAction
                  windowFeatures:(WKWindowFeatures *)windowFeatures {
  NSURL *url = navigationAction.request.URL;
  NSString *currentDomain = webView.URL.host;
  NSString *targetDomain = navigationAction.request.URL.host;

  // 3. Determine whether to optimize the behavior of the click URL.
  if ([self didHandleClickBehaviorForURL: url
      currentDomain: currentDomain
      targetDomain: targetDomain
      navigationAction: navigationAction]) {
    NSLog(@"URL opened in SFSafariViewController.");
  }

  return nil;
}

// Implement the WKNavigationDelegate method.
- (void)webView:(WKWebView *)webView
    decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
                    decisionHandler:
                        (void (^)(WKNavigationActionPolicy))decisionHandler {
  NSURL *url = navigationAction.request.URL;
  NSString *currentDomain = webView.URL.host;
  NSString *targetDomain = navigationAction.request.URL.host;

  // 3. Determine whether to optimize the behavior of the click URL.
  if ([self didHandleClickBehaviorForURL: url
      currentDomain: currentDomain
      targetDomain: targetDomain
      navigationAction: navigationAction]) {

    decisionHandler(WKNavigationActionPolicyCancel);
    return;
  }

  decisionHandler(WKNavigationActionPolicyAllow);
}

// Implement a helper method to handle click behavior.
- (BOOL)didHandleClickBehaviorForURL:(NSURL *)url
                       currentDomain:(NSString *)currentDomain
                        targetDomain:(NSString *)targetDomain
                    navigationAction:(WKNavigationAction *)navigationAction {
  if (!url || !currentDomain || !targetDomain) {
    return NO;
  }

  // Check if the navigationType is a link with an href attribute or
  // if the target of the navigation is a new window.
  if ((navigationAction.navigationType == WKNavigationTypeLinkActivated
      || !navigationAction.targetFrame)
      // If the current domain does not equal the target domain,
      // the assumption is the user is navigating away from the site.
      && ![currentDomain isEqualToString: targetDomain]) {

     // 4.  Open the URL in a SFSafariViewController.
    SFSafariViewController *safariViewController =
        [[SFSafariViewController alloc] initWithURL:url];
    [self presentViewController:safariViewController animated:YES
        completion:nil];
    return YES;
  }

  return NO;
}

Test your page navigation

To test your page navigation changes, load

https://webview-api-for-ads-test.glitch.me#click-behavior-tests

into your web view. Click each of the different link types to see how they behave in your app.

Here are some things to check:

  • Each link opens the intended URL.
  • When returning to the app, the test page's counter doesn't reset to zero to validate the page state was preserved.