iOS
UIWebView
completion block
loadRequest
Swift programming

iOS how can I add a completion block to UIWebView loadRequest?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

UIWebView's loadRequest(_:) method does not have a built-in completion handler. It fires off the request and returns immediately. To detect when loading finishes, you implement the UIWebViewDelegate protocol and respond to webViewDidFinishLoad(_:) and webView(_:didFailLoadWithError:). You can wrap this delegate pattern in a completion block by storing a closure and calling it from the delegate methods. However, UIWebView was deprecated in iOS 12 — for new code, use WKWebView which has native completion handler support.

The Problem

swift
1let webView = UIWebView()
2let url = URL(string: "https://example.com")!
3let request = URLRequest(url: url)
4
5// loadRequest returns Void — no completion handler
6webView.loadRequest(request)
7
8// How do you know when it's done?

loadRequest starts an asynchronous load but provides no callback. You cannot chain actions after the page loads without additional plumbing.

Solution with UIWebViewDelegate

swift
1import UIKit
2
3class WebViewController: UIViewController, UIWebViewDelegate {
4    let webView = UIWebView()
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        webView.delegate = self
9        webView.frame = view.bounds
10        view.addSubview(webView)
11
12        let url = URL(string: "https://example.com")!
13        webView.loadRequest(URLRequest(url: url))
14    }
15
16    func webViewDidFinishLoad(_ webView: UIWebView) {
17        print("Page loaded successfully")
18        // Perform post-load actions here
19    }
20
21    func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
22        print("Load failed: \(error.localizedDescription)")
23    }
24}

The delegate methods are called by UIWebView when loading completes or fails. This is the standard pattern for pre-iOS 12 code.

Wrapping in a Completion Block

swift
1import UIKit
2
3class CompletionWebView: NSObject, UIWebViewDelegate {
4    private let webView = UIWebView()
5    private var completion: ((Bool, Error?) -> Void)?
6
7    func loadRequest(_ request: URLRequest, completion: @escaping (Bool, Error?) -> Void) {
8        self.completion = completion
9        webView.delegate = self
10        webView.loadRequest(request)
11    }
12
13    func webViewDidFinishLoad(_ webView: UIWebView) {
14        completion?(true, nil)
15        completion = nil
16    }
17
18    func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
19        completion?(false, error)
20        completion = nil
21    }
22}
23
24// Usage
25let loader = CompletionWebView()
26let request = URLRequest(url: URL(string: "https://example.com")!)
27
28loader.loadRequest(request) { success, error in
29    if success {
30        print("Done loading")
31    } else {
32        print("Error: \(error?.localizedDescription ?? "unknown")")
33    }
34}

This wrapper stores the completion closure and calls it from the delegate methods. Setting completion = nil after calling it prevents double invocation.

swift
1import WebKit
2
3class ModernWebViewController: UIViewController, WKNavigationDelegate {
4    let webView = WKWebView()
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8        webView.navigationDelegate = self
9        webView.frame = view.bounds
10        view.addSubview(webView)
11
12        let url = URL(string: "https://example.com")!
13        webView.load(URLRequest(url: url))
14    }
15
16    func webView(_ webView: WKWebView,
17                 didFinish navigation: WKNavigation!) {
18        print("Page loaded")
19    }
20
21    func webView(_ webView: WKWebView,
22                 didFail navigation: WKNavigation!,
23                 withError error: Error) {
24        print("Failed: \(error.localizedDescription)")
25    }
26}

WKWebView replaced UIWebView starting in iOS 8 and is the only supported option from iOS 12 onwards. It uses WKNavigationDelegate for load callbacks and runs content in a separate process for better performance and security.

WKWebView with evaluateJavaScript Completion

swift
1// WKWebView has built-in completion handlers for JavaScript evaluation
2webView.evaluateJavaScript("document.title") { result, error in
3    if let title = result as? String {
4        print("Page title: \(title)")
5    }
6}
7
8// Load HTML string with completion via navigation delegate
9webView.loadHTMLString("<h1>Hello</h1>", baseURL: nil)
10// didFinish delegate method fires when done

WKWebView provides completion handlers for JavaScript evaluation. For load completion, you still use the navigation delegate pattern, but the API is more modern and reliable.

Async/Await with WKWebView (iOS 15+)

swift
1import WebKit
2
3class AsyncWebViewController: UIViewController {
4    let webView = WKWebView()
5
6    func loadPage() async throws -> String {
7        let url = URL(string: "https://example.com")!
8        _ = try await webView.load(URLRequest(url: url))
9
10        // evaluateJavaScript also supports async/await
11        let title = try await webView.evaluateJavaScript("document.title") as? String
12        return title ?? "No title"
13    }
14}

iOS 15 added async/await support to WKWebView, eliminating the need for delegate callbacks entirely in modern code.

UIWebView to WKWebView Migration

FeatureUIWebViewWKWebView
Load completionDelegate onlyDelegate + async/await
JavaScriptstringByEvaluatingJavaScript (sync)evaluateJavaScript (async with completion)
ProcessIn-app processSeparate WebContent process
PerformanceSlower, more memoryFaster, efficient
StatusDeprecated iOS 12Current, supported
App StoreRejected since 2020Required

Common Pitfalls

  • Using UIWebView in new projects: Apple rejects App Store submissions containing UIWebView references since April 2020. Always use WKWebView for new development.
  • Multiple loadRequest calls with one completion: If you call loadRequest again before the first load finishes, the stored completion block gets overwritten. Queue requests or cancel the previous load first.
  • Forgetting to set delegate before loadRequest: If webView.delegate is not set before calling loadRequest, the delegate methods never fire. Always assign the delegate before initiating the load.
  • Retain cycles with completion closures: Storing self in the completion closure while the web view holds a strong reference to the delegate creates a retain cycle. Use [weak self] in closures to break the cycle.
  • Assuming webViewDidFinishLoad fires once: A single page load may trigger webViewDidFinishLoad multiple times due to iframes, redirects, or embedded resources. Track the main frame load separately if you need a single completion event.

Summary

  • UIWebView.loadRequest has no completion handler — use UIWebViewDelegate methods
  • Wrap delegate callbacks in a stored closure for a completion-block API
  • UIWebView is deprecated since iOS 12 and rejected from the App Store since 2020
  • Use WKWebView with WKNavigationDelegate for modern load completion handling
  • iOS 15+ supports async/await on WKWebView for the cleanest completion pattern
  • Always use [weak self] in completion closures to avoid retain cycles

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.