WKWebView improvements in iOS 15

Media playback APIs, theme color, async support, downloads and more.

Published: June 17, 2021

With iOS 15 there are another bunch of great improvements to the WKWebView. As a reminder, with iOS 14 we got JavaScript sandboxing, better ways to inject JS into pages and more.

Note: Some of the stuff shown bellow actually sneaked in with iOS 14.5 (and even with 14.3).

Because there are once again a lot of new stuff, I won't go into too much details to focus on the overview.

Controlling media playback

There are new APIs that allow you to manipulate audio & video playback on the page. Previously we would have to reach for JavaScript and manually play or pause specific element.

With new methods, you can pause all the media on a page and also control if autoplay is enabled or not.

For example we can pause all media playback like this:

webView.pauseAllMediaPlayback {
    // completion
}

And since we have async/await, we can do it like this also:

await webView.pauseAllMediaPlayback()

I won't be showing the await variants for other methods to keep focus on the APIs.

There's second method tailor made for cases where you don't want any autoplay to happen.

webView.setAllMediaPlaybackSuspended(true) {
    // done
}

With setAllMediaPlaybackSuspended there won't be any unexpected videos playing, even if you refresh the page. Call it with false to resume the playback.

You can also ask webview for current playback state:

webView.requestMediaPlaybackState { state in
    switch state {
    case .playing:
        print("Playing")
    case .suspended:
        print("Suspended")
    case .paused:
        print("Paused")
    case .none:
        print("🤷")
    }
}

Async support

Since we already touched upon async/await support for the new methods, the older ones were updated as well. So stuff like creating PDF from a page, injecting JavaScript and much more can use the new concurrency APIs to avoid callbacks.

Access website theme color

This is pretty small feature but can be very useful. You can ask the WKWebView to return you theme color of the website if it exists. You can use this color to customize look of your native UI.

The code to observe themeColor isn't the prettiest one, but it gets the job done:

private var themeObservation: NSKeyValueObservation?

override func viewDidLoad() {
    super.viewDidLoad()

    themeObservation = webView.observe(\.themeColor) { [unowned self] webView, _ in
        self.view.backgroundColor = webView.themeColor ?? .systemBackground
    }
}

For simplicity I am changing the background color of root view, but of course you can use it anyway you want.

In HTML the theme color is defined like this:

<meta name="theme-color" content="#e95420">

Computed theme color

What if theme color isn't defined for particular website? You can still use the underPageBackgroundColor on the WKWebView and observe it in similar manner like with themeColor.

This property is not yet available in the Xcode 13 beta 1 (for Swift at least). You can also set this property to define the background color of the webView when user scrolls past the content.

Disabling text interaction

There is another WKPreference option which lets you disable all the standard text interactions on particular webView. This is super useful if you are using webView to "just" play videos. This will help insure that user's don't trigger text selection by accident when trying to press the play button for example.

let preferences = WKPreferences()
preferences.textInteractionEnabled = false

let config = WKWebViewConfiguration()
configuration.preferences = preferences

let webView = WKWebView(frame: .zero, configuration: config)

Automatic HTTPS upgrade

WKWebView will now automatically upgrade known hosts to HTTPS protocol for security reasons. There is also a configuration flag to disable this behavior in cases it causes problems in your app.

let config = WKWebViewConfiguration()
config.upgradeKnownHostsToHTTPS = false

APIs for camera and microphone

There's new functionality to manage user's permissions to access device's camera and microphone. New properties cameraCaptureState and microphoneCaptureState let you easily access the current state.

You can decide when to grant access to this hardware like this:

func webView(_ webView: WKWebView, decideMediaCapturePermissionsFor origin: WKSecurityOrigin, initiatedBy frame: WKFrameInfo, type: WKMediaCaptureType) async -> WKPermissionDecision {
    return .grant
}

This will also remove the second prompt that is otherwise present. When page in WKWebView requests these permissions, the first prompt is related to the app and second one is related to the actual website.

Using this new delegate method, you can allow access to only pages you trust and user doesn't see two prompts.

New class for managing downloads

With WKDownload you can encapsulate and manage file downloads. There are a multiple ways of initiating download. For example in the decidePolicyFor method from WKNavigationAction or WKNavigationResponse.

You can also start download manually by calling startDownload on the instance of WKWebView. This will return an instance of WKDownload and you need to set its delegate property otherwise the download won't happen. There is single required method for the WKDownloadDelegate:

func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {

}

Alternatively there is async variant.

Your job is to return URL there the download should be saved.

If download fails, you can restart it by calling resumeDownload and passing it the Data instance this delegate method gave you:

func download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?) {
    // handle download error
}

For more be sure to check out the WWDC 2021 session.

Uses: Xcode 13 & Swift 5.5

Filip Němeček profile photo

WRITTEN BY

Filip Němeček @nemecek_f

iOS blogger and developer with interest in Python/Django. My recent projects include IndieAppsCatalog and ImpressKit.

iOS blogger and developer with interest in Python/Django. My recent projects include IndieAppsCatalog and ImpressKit.