UIWebView, WKWebView and Tying Them Together Using Swift

Maximizing iOS 8's Ability to Use WebKit

Mobile Apps, Mobile Development, Tutorials Comments (0)

As is stated in the earlier blog post, WebKit is a new framework for developers in iOS 8.

Previously, developers had relied on UIWebView, a under-powered yet straightforward view for handling the Web.

The WebKit view that is the counterpart to UIWebView would be WKWebView. For a greater understanding of what this view is, I recommend checking out the Objective-C post How To Use iOS WKWebView with UIWebView Fallback.

This blog post will be slightly different as it is intended for an audience who is at least somewhat familiar with Swift and has an understanding of what was accomplished in the Objective-C article.

As is stated in that post, supporting WKWebKit only would leave all iOS 7 users behind. So how do we make sure to support both? Very much like we did in Objective-C.

Step One: Detection

if (NSClassFromString("WKWebView") != nil) {
  // Use WebKit
} else {
  // Fallback on UIWebView
}

If WKWebView exists, we will undoubtedly want to use it. Otherwise, UIWebView is the backup in this case. Seems pretty familiar, doesn’t it? Swift and Objective-C don’t look too different yet. Let’s expand it much in the same way the Objective-C article did.

Step Two: Protocol

protocol FLWebViewProvider: class {
    func setDelegateViews(viewController: ViewController)

    var request: NSURLRequest? { get }

    func URL() -> NSURL?

    func loadRequestFromString(urlNameAsString: String!)

    func canGoBack() -> Bool

    func evaluateJavaScript(javascriptString: String!, completionHandler: (AnyObject, NSError) -> ())

}

Step Three: Extensions

In Objective-C, we had categories. In Swift we have extensions. Extensions are virtually the same idea but without names. The compiler is smart enough to help us along without needing the extra name. While Objective-C had looked like:

@interface WKWebView (FLWKWebView) <FLWebViewProvider>

- (void) setDelegateViews: (id <WKNavigationDelegate, WKUIDelegate>) delegateView;

@end

Swift would simply become:

extension UIWebView: FLWebViewProvider {
    class func setDelegateViews(viewController: ViewController)
}

And the implementation in Objective-C going from:

@implementation WKWebView (FLWKWebView)

- (void) setDelegateViews: (id <WKNavigationDelegate, WKUIDelegate>) delegateView
{
    [self setNavigationDelegate: delegateView];
    [self setUIDelegate: delegateView];
}

@end

To a slightly different, if not more explicit:

extension WKWebView: FLWebViewProvider {
    func setDelegateViews(viewController: ViewController) {
        self.UIDelegate = viewController as WKUIDelegate
        self.navigationDelegate = viewController as WKNavigationDelegate
    }
}

Step Four: View Controller

Just as it was in Objective-C, the view controller doesn’t need to have much. The heavy lifting is done by the protocol and extensions, which allows for quicker implementation.

import UIKit
import WebKit

class ViewController: UIViewController, UIWebViewDelegate, WKNavigationDelegate, WKUIDelegate {
    var webView: FLWebViewProvider?

    override func viewDidLoad() {
        super.viewDidLoad()

        if (NSClassFromString("WKWebView") != nil) {
            let w = WKWebView(delegateView: self)
            w.frame = self.view.frame
            self.webView = w
            self.view.addSubview(self.webView as WKWebView)
        } else {
            let w = UIWebView(delegateView: self)
            w.frame = self.view.frame
            self.webView = w
            self.view.addSubview(self.webView as UIWebView)
        }

        self.webView?.loadRequestFromString("http://www.floatlearning.com/")
    }
}

Essentially, what this will do is load the proper view, set the frame and add it to the view. The reason there is some double logic here is because Swift doesn’t allow you to make as many assumptions as you might have previously done with Objective-C. All Swift really knows about FLWebViewProvider is that it has certain methods, but not necessarily that is a UIView. There is another option, which would be creating a class called FLWebView, but that may be a topic for another day.

Since the protocol requires loadRequestFromString we know the WebView can do that, however.

Step Five: Expanding

Because of the differences, we need to account for this slightly inside of the protocol. Now, Swift handles things slightly differently so there are a couple of different ways you can go. There is a new override keyword in Swift that we can use to let the compiler know we are overriding a piece of the original class. I’ve left things fairly simple to keep the basic premise of this very clean.

protocol FLWebViewProvider: class {

    func setDelegateViews(viewController: ViewController)

    var request: NSURLRequest? { get }

    func URL() -> NSURL?

    func loadRequestFromString(urlNameAsString: String!)

    func canGoBack() -> Bool

    func canGoForward() -> Bool

    func evaluateJavaScript(javascriptString: String!, completionHandler: (AnyObject, NSError) -> ())
}

Just as in the Objective-C example, we’re reconciling the differences between the two approaches. URL will be added to UIWebView; request will be added to WKWebView. We actually don’t need to do any overrides here as we intend for the original usage in both cases.

In order to conform to these protocols, we will need to expand on our extensions a bit. First, the UIWebView:

    func setDelegateViews(viewController: ViewController) {
        delegate = viewController
    }

    func canGoBack() -> Bool {
        return self.canGoBack
    }

    func canGoForward() -> Bool {
        return self.canGoForward
    }

    func loadRequestFromString(urlNameAsString: String!) {
        loadRequest(NSURLRequest(URL: NSURL(string: urlNameAsString)!))
    }

    func URL() -> NSURL? {
        return self.request?.URL
    }

    func evaluateJavaScript(javascriptString: String!, completionHandler: (AnyObject, NSError) -> ()) {
        var string = stringByEvaluatingJavaScriptFromString(javascriptString)

        completionHandler(string!, NSError())
    }

In most places this is fairly straightforward, simply connecting the dots between UIWebView and WKWebView, as the protocol lays out.

We will do this for the other side of it, as well.

extension WKWebView: FLWebViewProvider {

    func associatedObjectKey() -> String {
        return "kAssociatedObjectKey"
    }

    var request: NSURLRequest? {
        get {
            return objc_getAssociatedObject(self, associatedObjectKey()) as? NSURLRequest
        }
        set(newValue) {
            objc_setAssociatedObject(self, associatedObjectKey(), newValue, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
        }
    }

    convenience init(delegateView: AnyObject) {
        self.init()
        self.UIDelegate = delegateView as? WKUIDelegate
        self.navigationDelegate = delegateView as? WKNavigationDelegate
    }

    func setDelegateViews(viewController: ViewController) {
        self.UIDelegate = viewController as WKUIDelegate
        self.navigationDelegate = viewController as WKNavigationDelegate
    }

    func URL() -> NSURL? {
        return self.URL
    }

    func canGoBack() -> Bool {
        return self.canGoBack
    }

    func canGoForward() -> Bool {
        return self.canGoForward
    }

    func loadRequestFromString(urlNameAsString: String!) {
        self.loadRequest(NSURLRequest(URL: NSURL(string: urlNameAsString)!))
    }

    func evaluateJavaScript(javascriptString: String!, completionHandler: (AnyObject, NSError) -> ()) {
        self.evaluateJavaScript(javascriptString, completionHandler: { (AnyObject, NSError) -> Void in

        })
    }
}

If you followed along with the Objective-C post, you would know that swizzling was employed. In this particular case we don’t need to, but if you feel inclined to follow that same path, there is a great article on Method Swizzling in Swift over at Swift Studies.

Final Thoughts

WKWebView is a large step forward from UIWebView, and if you’re already learning Swift, it is certainly worth checking out. There is a sample project located in a GitHub repository (MIT) here for you to check out and get started.

If you have any more questions, feel free drop a line in the comments below. If you’ve spotted a problem with our code (it happens!) or want to see more, feel free to open an issue or pull request on GitHub. And, if your organization would like to develop a secure, custom app using Swift, don’t hesitate to contact us.

Follow Float
The following two tabs change content below.

ebusch

Latest posts by ebusch (see all)

» Mobile Apps, Mobile Development, Tutorials » UIWebView, WKWebView and Tying Them...
On December 29, 2014
By
, , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

« »