Swift webview.backforwardlist 없애는 방법

Question

This Content is from Stack Overflow. Question asked by Gralex

I’m trying to restore WKWebView navigation after app will restarts. I can inherit from base web view and take more control with it.

class WebViewHistory: WKBackForwardList {
// additional control with history items
// can perform custom init
}

class WebView: WKWebView {

    var history: WebViewHistory

    override var backForwardList: WebViewHistory {
        return history
    }

    init(frame: CGRect, configuration: WKWebViewConfiguration, history: WebViewHistory) {
        self.history = history
        super.init(frame: frame, configuration: configuration)
    }
}

For serialization I need serialize at least one WKBackForwardListItem. So here I face up with troubles.

class WebNavigationItem: WKBackForwardListItem {}

// but I cant create this objects
let item1 = WKBackForwardListItem() // 'init()' is unavailable
let item2 = WebNavigationItem() // 'WebNavigationItem' cannot be constructed because it has no accessible initializers

So how to serialize history of WKWebView?


So for now I see only 1 option: make custom navigation with serialization support and forgot about allowsBackForwardNavigationGestures. And it required more code for implementing all logic of default web view history.

Solution

If you’re targeting iOS 15 or above, you should look into WKWebView.interactionState.

It’s erased to Any? but appears to be a plain old [NS]Data value behind the scenes. Maybe with luck you can serialise and restore that over app launches?

This Question was asked in StackOverflow by Gralex and Answered by Pyry Jahkola It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?


Skip to main content

This browser is no longer supported.

Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.

WKWebView.GoTo(WKBackForwardListItem) Method

  • Reference

Definition

In this article

Navigates to an item from the back-forward list and sets it as the current item.

[Foundation.Export("goToBackForwardListItem:")]
public virtual WebKit.WKNavigation GoTo (WebKit.WKBackForwardListItem item);
abstract member GoTo : WebKit.WKBackForwardListItem -> WebKit.WKNavigation
override this.GoTo : WebKit.WKBackForwardListItem -> WebKit.WKNavigation

Parameters

item WKBackForwardListItem

The item to which to navigate. Must be one of the items in the web view's back-forward list.

Returns

WKNavigation

A new navigation to the requested item, or null if it is already the current item or is not part of the web view's back-forward list.

Attributes

Remarks

Applies to

Get WebKit into your app the easy way

WKWebView is a powerhouse on iOS, providing high-performance web rendering wherever and whenever you need.

In this article I’ve put together 15 of the most common use cases for WKWebView, and provided hands-on code solutions for each of them. So, if you want to solve a specific problem, or if you just want to see what WebKit is capable of, read on!

Sponsor Hacking with Swift and reach the world's largest Swift community!

1. Making a web view fill the screen

Sometimes you’ll see folks add code to viewDidLoad() to create a web view then make it fill all available space. This is inefficient, and also far harder than it needs to be.

A simpler approach is to add a property to your view controller like this:

let webView = WKWebView()

Then overriding the loadView() method to assign that to your view controller’s view, like this:

override func loadView() {
    self.view = webView
}

Having a dedicated webView property is helpful so that you can reference its properties and methods more easily later on.

2. Loading remote content

Given that one of the major uses for WKWebView is to load remote content, it’s weirdly not just one line of code. Instead, you create a URL from a string, wrap that in a URLRequest, then ask the web view to load that:

if let url = URL(string: "https://www.apple.com") {
    let request = URLRequest(url: url)
    webView.load(request)
}

If you intend to load URLs frequently, you might find it easier to wrap that behavior inside an extension:

extension WKWebView {
    func load(_ urlString: String) {
        if let url = URL(string: urlString) {
            let request = URLRequest(url: url)
            load(request)
        }
    }
}

Now you can load a website just by running webView.load("https://www.apple.com").

3. Loading local content

WKWebView can load any HTML stored in your app bundle using its loadFileURL() method. You should provide this with a URL to some HTML file that you know is in your bundle, along with another URL that stores any other files you want to allow the web view to read.

For example, if you wanted to load a file called "help.html" you might use code like this:

if let url = Bundle.main.url(forResource: "help", withExtension: "html") {
    webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}

That url.deletingLastPathComponent() part tells WebKit it can read from the directory that contains help.html – that’s a good place to put any assets such as images, JavaScript, or CSS.

4. Loading HTML fragments

You can generate HTML in code and feed that directly to WKWebView. For example, this displays a heading message:

let html = """
<html>
<body>
<h2>Hello, Swift!</h2>
</body>
</html>
"""

webView.loadHTMLString(html, baseURL: nil)

Note the baseURL parameter to loadHTMLString(). If you are referencing assets in your bundle such as images or CSS, you should specify Bundle.main.resourceURL there so they can be loaded. For example:

webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)

5. Controlling which sites can be visited

By default WKWebView allows access to all available websites, but it’s easy to lock down the list to any criteria of your choosing.

First, make something conform to WKNavigationDelegate – it could be your view controller, but it doesn’t have to be. For example:

class ViewController: UIViewController, WKNavigationDelegate {

Second, make that object the navigation delegate of your web view. If you were using your view controller, you’d write this:

webView.navigationDelegate = self

Finally, implement the decidePolicyFor method, adding any logic you want to decide whether the page should be loaded. As an example, this implementation allows the user to visit the Apple homepage and nothing else:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let host = navigationAction.request.url?.host {
        if host == "www.apple.com" {
            decisionHandler(.allow)
            return
        }
    }

    decisionHandler(.cancel)
}

It’s common to want to handle some links inside your app and others externally, and this doesn’t take much work thanks to the WKNavigationDelegate protocol.

First, make an object conform to the protocol – it might be your view controller, but it doesn’t need to be. For example:

class ViewController: UIViewController, WKNavigationDelegate {

Second, set that object as your web view’s navigation delegate. If you were using your view controller you would write this:

webView.navigationDelegate = self

Finally, implement the decidePolicyFor method with whatever logic should decide whether the page is loaded internally or externally. For internal loads make sure you call the decisionHandler() closure with .cancel so the load halts, while also calling UIApplication.shared.open() to have the URL open in the external browser.

As an example, this implementation will load all links inside the web view as long as they don’t go to the Apple homepage:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    if let url = navigationAction.request.url {
        if url.host == "www.apple.com" {
            UIApplication.shared.open(url)
            decisionHandler(.cancel)
            return
        }
    }

    decisionHandler(.allow)
}

7. Monitoring page loads

Loading one web page means fetching some HTML, downloading any JavaScript and CSS it uses, downloading any images, and so on.

To help keep your users informed, it’s a good idea to monitor page loads and show some sort of user interface updates so they know something is happening. This can be done by observing the estimatedProgress property, like this:

webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)

You should now implement the observeValue(forKeyPath:) method, which will pass you a string saying what changed. If that’s set to “estimatedProgress” then we can do something interesting with the web view’s latest estimatedProgress property:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "estimatedProgress" {
        print(Float(webView.estimatedProgress))
    }
}

8. Reading a web page’s title as it changes

You can use webView.title to read the current page title, but because page titles change as the user browses around you’re likely to want to be notified whenever it changes.

To do that, first register to receive notifications when the title changes:

webView.addObserver(self, forKeyPath: #keyPath(WKWebView.title), options: .new, context: nil)

Now implement the observeValue(forKeyPath:) method. This will pass you a string saying what changed, and if that’s “title” then you can do something with it.

To get you started, this code just prints the title whenever it changes:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "title" {
        if let title = webView.title {
            print(title)
        }
    }
}

9. Reading pages the user has visited

If you’re serious about building a useful browser experience, you’ll probably want to read the list of sites the user has visited. This is all available inside the backForwardList property of web views, which itself contains the array backList and forwardList.

Inside each of those arrays you can read the URL for each page that was visited, along with the title that was used. For example, you can print out a list of all sites the user has already visited using this loop:

for page in webView.backForwardList.backList {
    print("User visited \(page.url.absoluteString)")
}

10. Injecting JavaScript into a page

Once your web view has loaded some content, you can execute any JavaScript you like inside that rendered page by using the evaluateJavaScript() method. All you need to do is give it some JavaScript to execute – reading some value, for example – along with a closure to run when execution has finished.

As an example, if you had a page that contained <div id="username">@twostraws</div> and you wanted to read the “@twostraws” part, you would use this:

webView.evaluateJavaScript("document.getElementById('username').innerText") { (result, error) in
    if let result = result {
        print(result)
    }
}

11. Reading and deleting cookies

You can read through the complete list of cookies associated with a website by using the httpCookieStore property of your web view. This is buried under the configuration.websiteDataStore property, but once you find it you can call getAllCookies() to get an array of cookies, or call delete() to delete a specific cookie.

As an example, this code loops over all cookies, and when it finds one called “authentication” deletes it – all other cookies are just printed out:

webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
    for cookie in cookies {
        if cookie.name == "authentication" {
            self.webView.configuration.websiteDataStore.httpCookieStore.delete(cookie)
        } else {
            print("\(cookie.name) is set to \(cookie.value)")
        }
    }
}

12. Providing a custom user agent

User agents let your web servers identify the type of web browser that is accessing a page, and are commonly used to enable or limit which features are available.

If you’re reading pages from your own server, you can adjust the user agent to your own string so that you can identify users of your app. For example:

webView.customUserAgent = "My Awesome App"

Note: There's nothing stopping you from changing the user agent when accessing other resources, but keep in mind that some sites might read the user agent string and get confused if it doesn't match what they expect.

13. Showing custom UI

WKWebView is a bit like having one lone tab in the iOS Safari app, which means the user can’t open and close new windows to browse multiple pages, and it won’t even show alerts or confirmation requests triggered by JavaScript.

Fortunately, you can change that using the WKUIDelegate protocol: set an object to be the UI delegate of your web view and you can show custom alerts, manage your own tabs, and more.

First, make some object such as your view controller conform to it:

class ViewController: UIViewController, WKUIDelegate {

Second, assign your view controller to the uiDelegate property of your web view:

webView.uiDelegate = self

Finally, implement as many of the optional methods of WKUIDelegate as you want. For example, you can make WKWebView show a custom alert controller when any web page uses the alert() JavaScript function:

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    let ac = UIAlertController(title: "Hey, listen!", message: message, preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    present(ac, animated: true)
    completionHandler()
}

There’s also runJavaScriptConfirmPanelWithMessage for showing confirm and deny UI, runJavaScriptTextInputPanelWithPrompt for requesting user text input, and so on.

Note: You must call the completion handler when you’re finished. JavaScript’s alerts are blocking, which means JavaScript execution will not continue until the alert finishes. As a result, WebKit will complain if you don’t let it know when you’re done.

14. Snapshot part of the page

Although you can use the regular drawHierarchy() method for converting a view to an image, WebKit comes with its own takeSnapshot() method that lets you crop and size the image as needed.

For example, this will produce a 150x50 image from the top-left of the web view:

let config = WKSnapshotConfiguration()
config.rect = CGRect(x: 0, y: 0, width: 150, height: 50)

webView.takeSnapshot(with: config) { image, error in
    if let image = image {
        print(image.size)
    }
}

If you don’t want a cropped image - i.e. you want the whole thing – just use nil instead of config.

15. Detecting data

Web views have built-in data detector support, which means they can make things like phone numbers, calendar events, and flight numbers into tappable links.

These are all disabled so the default behavior is to render web pages as they were designed, but it’s trivial to override – just create your web view using a custom WKWebViewConfiguration object.

For example, this instructs the web view to detect all possible types of data:

let config = WKWebViewConfiguration()
config.dataDetectorTypes = [.all]
let webView = WKWebView(frame: .zero, configuration: config)

Sponsor Hacking with Swift and reach the world's largest Swift community!