UIWebView
WKWebView
iOS Development
Apple
WebView Migration

Migrating from UIWebView to WKWebView

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Migrating from UIWebView to WKWebView is not just a find-and-replace exercise. WKWebView has a different architecture, different delegate APIs, and more explicit handling for JavaScript, cookies, and message passing. The good news is that the migration is usually straightforward once you map the old responsibilities to the newer WebKit APIs.

Why the Migration Matters

UIWebView is deprecated and removed from modern iOS development workflows. WKWebView is the supported replacement because it is faster, safer, and more feature-rich.

A few important differences:

  • rendering happens in a separate process
  • JavaScript evaluation is asynchronous
  • navigation and UI delegates are more explicit
  • script message handling is built in

Those differences improve behavior, but they also mean old UIWebView assumptions usually need cleanup.

Creating a WKWebView

A basic WKWebView setup looks like this:

swift
1import UIKit
2import WebKit
3
4final class WebViewController: UIViewController, WKNavigationDelegate {
5    private var webView: WKWebView!
6
7    override func viewDidLoad() {
8        super.viewDidLoad()
9
10        let config = WKWebViewConfiguration()
11        webView = WKWebView(frame: .zero, configuration: config)
12        webView.navigationDelegate = self
13        webView.translatesAutoresizingMaskIntoConstraints = false
14
15        view.addSubview(webView)
16        NSLayoutConstraint.activate([
17            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
18            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
19            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
20            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
21        ])
22
23        let url = URL(string: "https://example.com")!
24        webView.load(URLRequest(url: url))
25    }
26}

If your old code created a UIWebView, loaded a request, and implemented delegate callbacks, this is the first shape of the replacement.

Delegate Mapping Changes

Many UIWebViewDelegate responsibilities move into WKNavigationDelegate and WKUIDelegate.

swift
1extension WebViewController {
2    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
3        print("Started loading")
4    }
5
6    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
7        print("Finished loading")
8    }
9
10    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
11        print("Load failed: \(error)")
12    }
13}

If the old code used shouldStartLoadWith, the equivalent decision point is decidePolicyFor navigationAction.

swift
1func webView(
2    _ webView: WKWebView,
3    decidePolicyFor navigationAction: WKNavigationAction,
4    decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
5) {
6    if navigationAction.request.url?.host == "blocked.example.com" {
7        decisionHandler(.cancel)
8        return
9    }
10    decisionHandler(.allow)
11}

JavaScript Is Asynchronous Now

One of the biggest migration differences is JavaScript execution. stringByEvaluatingJavaScript from UIWebView was synchronous. WKWebView uses evaluateJavaScript with a completion handler.

swift
1webView.evaluateJavaScript("document.title") { result, error in
2    if let error = error {
3        print("JS error: \(error)")
4        return
5    }
6    print(result ?? "no title")
7}

That changes control flow. Code that assumed an immediate result now needs to continue inside the completion block or use higher-level async orchestration.

JavaScript-to-Native Messaging

WKWebView has a stronger bridge model through WKUserContentController.

swift
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "nativeBridge")

Then implement the handler:

swift
1extension WebViewController: WKScriptMessageHandler {
2    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
3        if message.name == "nativeBridge" {
4            print(message.body)
5        }
6    }
7}

This is usually cleaner than the hacks older apps used with custom URL schemes.

Cookies and State Need Attention

WKWebView manages web data differently from UIWebView. If the app relied on shared cookies or authentication state, test that path carefully. In modern WebKit, cookie handling usually involves WKWebsiteDataStore and WKHTTPCookieStore rather than assuming the old global storage behavior.

That is one of the most common reasons a migration compiles but still breaks login flows.

Common Pitfalls

  • Replacing the view type but forgetting to migrate old delegate logic to WKNavigationDelegate or WKUIDelegate.
  • Assuming JavaScript evaluation is synchronous when evaluateJavaScript is asynchronous.
  • Keeping old custom URL-scheme bridges instead of using script message handlers.
  • Migrating basic page loads successfully but failing to test cookies, authentication, and cached web state.
  • Treating WKWebView as a drop-in replacement even though its configuration and lifecycle are more explicit.

Summary

  • 'WKWebView is the supported replacement for UIWebView and has a different architecture.'
  • Create it with a WKWebViewConfiguration and wire the correct delegates.
  • Update navigation decisions and load callbacks to the WKNavigationDelegate model.
  • Migrate JavaScript code to evaluateJavaScript and script message handlers.
  • Test cookies and authentication flows carefully because web state handling changed.

Course illustration
Course illustration

All Rights Reserved.