NEW! Master Swift design patterns with my latest book! >>

< Previous: Setting up   Next: Choosing a website: UIAlertController action sheets >

Creating a simple browser with WKWebView

In our first two projects we used Interface Builder for a lot of layout work, but here our layout will be so simple that we can do the entire thing in code. You see, before we were adding buttons and images to our view, but in this project the web view is going to take up all the space so it might as well be the view controller's main view.

So far, we've been using the viewDidLoad() method to configure our view once its layout has loaded. This time we need to override the actual loading of the view because we don't want that empty thing on the storyboard, we want our own code. It will still be placed inside the navigation controller, but the rest is up to us.

iOS has a few different ways of working with web views, but the one we’ll be using for this project is called WKWebView. It’s part of the WebKit framework rather than the UIKit framework, but we can import it by adding this line to the top of ViewController.swift:

import WebKit

When we create the web view, we need to store it as a property so we can reference it later on. So, add this property to the class now:

var webView: WKWebView!

Finally, add this new method before viewDidLoad():

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

That code will trigger a compiler error for now, but we’ll fix it in a moment.

Note: You don’t need to put loadView() before viewDidLoad(), and in fact you could put it anywhere between class ViewController: UIViewController { down to the last closing brace in the file. However, I encourage you to structure your methods in an organized way, and because loadView() gets called before viewDidLoad() it makes sense to position the code above it too.

Anyway, there are only three things we care about, because by now you should understand why we need to use the override keyword. (Hint: it's because there's a default implementation, which is to load the layout from the storyboard!)

First, we create a new instance of Apple's WKWebView web browser component and assign it to the webView property. Third, we make our view (the root view of the view controller) that web view.

Yes, I missed out the second line, and that's because it introduces new concept: delegation. Delegation is what's called a programming pattern – a way of writing code – and it's used extensively in iOS. And for good reason: it's easy to understand, easy to use, and extremely flexible.

A delegate is one thing acting in place of another, effectively answering questions and responding to events on its behalf. In our example, we're using WKWebView: Apple's powerful, flexible and efficient web renderer. But as smart as WKWebView is, it doesn't know (or care) how our application wants to behave, because that's our custom code.

The delegation solution is brilliant: we can tell WKWebView that we want to be informed when something interesting happens. In our code, we're setting the web view's navigationDelegate property to self, which means "when any web page navigation happens, please tell me – the current view controller.”

When you do this, two things happen:

  1. You must conform to the protocol. This is a fancy way of saying, "if you're telling me you can handle being my delegate, here are the methods you need to implement." In the case of navigationDelegate, all these methods are optional, meaning that we don't need to implement any methods.
  2. Any methods you do implement will now be given control over the WKWebView's behavior. Any you don't implement will use the default behavior of WKWebView.

Before we go any further, it’s time to fix the compilation error. When you set any delegate, you need to conform to the protocol that matches the delegate. Yes, all the navigationDelegate protocol methods are optional, but Swift doesn't know that yet. All it knows is that we're promising we're a suitable delegate for the web view, and yet haven't implemented the protocol.

The fix for this is simple, but I'm going to hijack it to introduce something else at the same time, because this is an opportune moment. First, the fix: find this line:

class ViewController: UIViewController {

…and change it to this:

class ViewController: UIViewController, WKNavigationDelegate {

That's the fix. But what I want to discuss is the class bit, because I've been using words like "data type", "component" and "instance" so far, without really being clear – and I promise you there are developers out there that are absolutely seething as a result. Hello, haters!

There are two types of complex data types in Swift: structures ("structs") and classes. They can seem quite similar in Swift, and really there are only two differences likely to matter to you at this stage, or indeed any stage over the next six months or so.

The first difference is that one class can inherit from another. We already talked about this in project 1, where our view controller inherited from UIViewController. This class inheritance means you get to build on all the amazing power when you inherit from UIViewController, and add your own customizations on top.

The second difference is that when you pass a struct into a method, a copy gets passed in. This means any changes you make in the method won't affect the struct outside of the method. On the other hand, when you pass an instance of a class into a method, it's passed by reference, meaning that the object inside the method is the same one outside the method; any changes you make will stay.

In terms of which is which: Int, Double, Float, String and Array are all structs, UIViewController and any UIView are all classes. In practice, this means that whenever you pass an array into a method, it gets copied. That might sound grossly inefficient, particularly if the array contains a huge amount of data, but don't fret about it: Swift will avoid any performance penalty as best it can using a technique called copy on write.

Back to our code: all this is important, because I want you to understand exactly what the line of code does. Here it is again:

class ViewController: UIViewController, WKNavigationDelegate {

As you can see, the line kicks off with "class", showing that we're declaring a new class here. The line ends with an opening brace, and everything from that opening brace to the closing brace at the end of the file form part of our class. The next part, ViewController, is the name of our class. Not a great name in a big project, but for a Single View App template project it's fine.

The interesting stuff comes next: there's a colon, followed by UIViewController, then a comma and WKNavigationDelegate. If you're feeling fancy, this part is called a type inheritance clause, but what it really means is that this is the definition of what the new ViewController class is made of: it inherits from UIViewController (the first item in the list), and promises it implements the WKNavigationDelegate protocol.

The order here really is important: the parent class (superclass) comes first, then all protocols implemented come next, all separated by commas. We're saying that we conform to only one protocol here (WKNavigationDelegate) but you can specify as many as you need to.

So, the complete meaning of this line is "create a new subclass of UIViewController called ViewController, and tell the compiler that we promise we're safe to use as a WKNavigationDelegate."

This program is almost doing something useful, so before you run it let's add three more lines. Please place these in the viewDidLoad() method, just after the super call:

let url = URL(string: "https://www.hackingwithswift.com")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true

The first line creates a new data type called URL, which is Swift’s way of storing the location of files. You’re probably already familiar with URLs as being used online, like with https://www.hackingwithswift.com, but they are just as important for storing local filenames too – they are flexible little things!

Even though we’re used to URLs being strings of text, Swift stores URLs in a specific URL data type that adds a lot of extra functionality. So, that first line of code creates a new URL out of the string “https://www.hackingwithswift.com”. I'm using hackingwithswift.com as an example website, but please change it to something you like.

Warning: you need to ensure you use https:// for your websites, because iOS does not like apps sending or receiving data insecurely. If this is something you want to override, I wrote an article specifically about App Transport Security: https://www.hackingwithswift.com/example-code/system/how-to-handle-the-https-requirements-in-ios-9-with-app-transport-security.

The second line does two things: it creates a new URLRequest object from that URL, and gives it to our web view to load.

Now, this probably seems like pointless obfuscation from Apple, but WKWebViews don't load websites from strings like www.hackingwithswift.com, or even from a URL made out of those strings. You need to turn the string into a URL, then put the URL into an URLRequest, and WKWebView will load that. Fortunately it's not hard to do!

Warning: Your URL must be complete, and valid, in order for this process to work. That means including the https:// part.

The third line enables a property on the web view that allows users to swipe from the left or right edge to move backward or forward in their web browsing. This is a feature from the Safari browser that many users rely on, so it's nice to keep it around.

It’s time to run the app, so please select the iPhone 8 simulator by going to the Product menu and choosing Destination > iPhone 8. Now press Cmd+R to run your app, and you should be able to view your website. Step one done!

Just by embedding a web view into the app, we can now render any website content – win!

Test your Swift today

Think you know Swift? My book Swift Coding Challenges will put your Swift skills to the test – it's perfect for job interviews and more!

< Previous: Setting up   Next: Choosing a website: UIAlertController action sheets >
MASTER SWIFT NOW
Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>