You are currently viewing How to Hijack WKWebView Navigation Actions

How to Hijack WKWebView Navigation Actions

When working on a hybrid mobile app, it is extremely important to ensure that our users have a seamless transition experience between web views and native views. One very common technique we can use to provide this kind of experience is by hijacking the WKWebView navigation actions.

In this article, I would like to show you how you can hijack the WKWebView navigation actions to trigger custom actions of your choice. Here’s the sample app that showcases what you will learn from this article:

How to Hijack WKWebView Navigation Actions to provide a seamless transition experience between web views and native views
Seamless transition experience between web views and native views

Wanted to know more? Read on!


The Sample App

As you can see from the animated GIF, the sample app is a simple web view that displays a static HTML page. When a user taps on the “Present Yellow Screen” hyperlink, the app will present a view controller with a yellow background.

Whereas when the “Push Red Screen” hyperlink is tapped, the app will push a red color view controller to the navigation stack.

Following is the HTML being displayed within the web view:

<h1>Swift Senpai Demo</h1>
<h2>This is a web view</h2>

<ul>
    <li><a href="navigate://present?r=255&g=255&b=0" target="_blank">Present Yellow Screen</a></li>
    <li><a href="navigate://push?r=255&g=0&b=0" target="_blank">Push Red Screen</a></li>
</ul>

Notice that the href value of the hyperlink is a custom-made URL that follows a specific format. This is how our sample app knows what action it should take when a user taps on a specific hyperlink. In the next section, I will explain to you how the custom formatted URL should be interpreted.

Note:

If you would like to learn how to style static HTML within a web view using CSS injection, so that it fits your app’s design theme, check out my article “Injecting JavaScript Into Web View In iOS“.


Understanding the URL Format

In order to make our sample app able to identify which hyperlink is tapped by the users, we must define a custom formatted URL that contain the following information:

  • Action type — The action our sample app should take when a user taps on the hyperlinks. For our case, we will use navigate to indicate a navigation action.
  • Transition type — The kind of screen transition to use, mainly present or push.
  • RGB value — The background color of the screen that is being shown.

For our sample app, we will use the following format:

<action_type>://<transition_type>?<rgb_value>

Here’s an example of the URL that represents a navigation action that presents a view controller with a yellow background.

navigate://present?r=255&g=255&b=0

Note:

The above URL format is designed specifically for the sample app. Therefore, feel free to modify the URL’s formatting and parameters based on your needs.

With that in mind, we can now define 2 enums to represent the action type and transition type:

enum ActionType: String {
    case navigate
}

enum TransitionType: String {
    case present
    case push
}

We will use both of these enums in the next section.


Hijacking WKWebView Navigation Action

The way to hijack the WKWebView navigation action is pretty straightforward. We can use the following WKWebView delegate method:

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

    // Hijacking logic here
    // ... ...
    // ... ...

    decisionHandler(.allow)
}

This delegate method will trigger every time a user triggers a navigation action, which makes it the best place to hijack the WKWebView navigation actions.

Note:

If the delegate method is not triggering, make sure you have imported the WebKit module as well as set your view controller as the web view’s delegate.

Notice the decisionHandler provided by the delegate method. It is a completion handler block to let the web view know whether you want to cancel or allow the navigation action. Make sure to call this handler at the end of every code path, or else you will get the following run-time error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Completion handler passed to -[SwiftSenpaiDemo.HijackViewController webView:decidePolicyForNavigationAction:decisionHandler:] was not called'

Within the delegate method, we first make sure that the navigation action is triggered by a hyperlink. After that, we will extract the hyperlink’s URL from the navigation action.

if
    navigationAction.navigationType == WKNavigationType.linkActivated,
    let url = navigationAction.request.url {

    // Process the URL & trigger hijack logic here
    // ... ...
    // ... ...

    // Ask the web view to ignore the original navigation request
    decisionHandler(.cancel)
    return
}

After obtaining the URL, we first extract the action type from the URL and check to see if it is .navigate. If it is not .navigate then we will load the URL as usual.

if
    let scheme = url.scheme,
    let actionType = ActionType(rawValue: scheme),
    actionType == .navigate {
    
    // Extract transition type & RGB value from URL
    // ... ...
    // ... ...
    
} else {
    // Load URL as usual
    let req = URLRequest(url: url)
    webView.load(req)
}

After confirming that the navigation type is .navigate, we can proceed to extract the transition type and RGB value from the URL like so:

 // Extract transition type
guard
    let host = url.host,
    let transitionType = TransitionType(rawValue: host) else {
        return
    }

// Extract RGB from query items
guard
    let component = URLComponents(string: url.absoluteString),
    let r = component.queryItems?.first(where: { $0.name == "r" })?.value,
    let g = component.queryItems?.first(where: { $0.name == "g" })?.value,
    let b = component.queryItems?.first(where: { $0.name == "b" })?.value else {
        return
    }

// Convert rgb String to Double (Only available in Swift 5.5)
guard
    let red = Double(r),
    let green = Double(g),
    let blue = Double(b) else {
        return
    }

With all the information in place, we can now perform the custom navigation based on the transition type we obtained from the URL.

let viewController = ColorViewController()
let screenColor = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
viewController.screenColor = screenColor

// Perform navigation based on transition type
switch transitionType {
case .present:
    present(viewController, animated: true)
case .push:
    navigationController?.pushViewController(viewController, animated: true)
}

That’s it! You can now run the sample code to see everything in action! If you find any difficulty executing the sample code above, feel free to get the full sample code here.


Wrapping Up

I hope this article gives you a good idea of how to seamlessly transition between web views and native views. If you like this article, you might want to check out my previous article “Injecting JavaScript Into Web View In iOS“.

Feel free to follow me on Twitter and subscribe to my monthly newsletter so that you won’t miss out on any of my upcoming iOS development-related articles.

Thanks for reading. 👨🏻‍💻


👋🏻 Hey!

While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.

  • Bartender: Superpower your menu bar and take full control over your menu bar items.
  • CleanShot X: The best screen capture app I’ve ever used.
  • PixelSnap: Measure on-screen elements with ease and precision.
  • iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.