Display a page of HTML in a WKWebView


A WKWebView is like a browser. See Web Views in the iOS Human Interface Guidelines. The new class WKWebView supersedes the old class UIWebView because WKWebView runs WebGL faster. WK stands for WebKit.

The WKWebView in this app displays the page
http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9236/src/html/preview.html
But a WKWebView is a browser with no back button or place to type a URL. If you tap on the links to www.filmratings.com or www.mpaa.org, there will be no way to get back to the green page (called a “green band” in the film industry).

Source code in WebView.zip

  1. Class AppDelegate: unchanged.
  2. Class ViewController: created loadView(); added code to viewDidLoad().
  3. preview.html (not used until the exercise where we call the init(contentsOfFile:encoding:) method of class String)
  4. Main.storyboard
  5. Class Info.plist: added properties we saw in IBM example of Hello.

Create the project

I wanted to tell Main.storyboard to create a WKWebView, but Main.storyboard doesn’t know about this class. Instead, I told the view controller to create a WKWebView, by overriding the view controller’s loadView() method.

To add the file preview.html to the project, save the file on your Mac Desktop. Then, using the Mac finder, drag the file into the WebView folder in the WebView folder that contains your project. Then select the WebView folder in the Xcode Project Navigator.
File → "Add Files to WebView"…
Select preview.html and press Add.

To launch the app in Landscape Left orientation, select the WebView project at the top of the Project Navigator. In the center panel of Xcode, under Deplayment Info → Device Orientation, uncheck everything except Landscape Left.

Three sources from which to get the page of HTML

  1. Download it from the web. The following code in the viewDidLoad() method of class ViewController downloaded a file of HTML from the web into the WKWebView named webView.
    		let url: NSURL? = NSURL(string: "http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9236/src/html/preview.html");
    		if url == nil {
    			print("could not create url");
    			return;
    		}
    
    		let request: NSURLRequest = NSURLRequest(URL: url!);
    		let webView: WKWebView = view as! WKWebView;
    		webView.loadRequest(request);
    
    		//Begin with the green band faded out.
    		view.alpha = 0;
    
    		//Fade in the green band.
    		UIView.animateWithDuration(2,
    			delay: 1,
    			options: UIViewAnimationOptions.CurveEaseInOut,
    			animations: {
    				self.view.alpha = 1;
    			},
    			completion: nil
    		);
    
  2. Get the HTML from a file in the app’s bundle. Replace the above code with the following. The plain text file preview.html has already been added to the project.
    		let bundle: NSBundle = NSBundle.mainBundle();
    
    		let filename: String? = bundle.pathForResource("preview", ofType: "html");
    		if filename == nil {
    			print("could not find file preview.html");
    			return;
    		}
    		
    		print("filename! = \(filename)");
    
    		do {
    			let html: String?  = try String(contentsOfFile: filename!,
    				encoding: NSUTF8StringEncoding);
    			let webView: WKWebView = view as! WKWebView;
    			webView.loadHTMLString(html!, baseURL: nil);
    		} catch {
    			print("could not load file: \(error)");
    			return;
    		}
    

    On iPhone 6s Plus simulator:

    filename! = /Users/myname/Library/Developer/CoreSimulator/Devices/4248FDA4-DE7B-4D16-8A71-4BBF066C98F4/data/Containers/Bundle/Application/A9E543BB-A1A3-4994-88DE-43125F825F59/WebView.app/preview.html
    
  3. Hardcode the HTML into one of the .swift files of the app. Replace the above code with the following.
    		let html: String =
    			  "<HTML>"
    			+ "<HEAD>"
    			+ "<META NAME = \"viewport\" CONTENT = \"width = device-width\">"
    			+ "<STYLE TYPE = \"text/css\">"
    			+ "<!--"
    			+ "SPAN.big  {font-size: 125%; font-weight: bold; padding: .33em;}"
    			+ "A:link    {font-size:  75%; color: white; text-decoration: none;}"
    			+ "A:visited {font-size:  75%; color: white; text-decoration: none;}"
    			+ "-->"
    			+ "</STYLE>"
    			+ "</HEAD>"
    
    			+ "<BODY STYLE = \"background-color: green; margin: 0px;\">"
    
    			+ "<TABLE STYLE = \""
    			+ "\tcolor: white;"
    			+ "\tmargin: auto;"
    			+ "\tfont: medium/200% Helvetica, sans serif;"
    			+ "\ttext-shadow: black .2em .2em 0;"
    			+ "\">"
    
    			+ "<TR>"
    			+ "<TD COLSPAN = \"2\""
    			+ "STYLE = \"padding-top: 7em; padding-bottom: 7em; text-align: center;\">"
    			+ "THE FOLLOWING"
    			+ "<SPAN CLASS = \"big\">PREVIEW</SPAN>"
    			+ "HAS BEEN APPROVED FOR"
    			+ "<BR><SPAN CLASS = \"big\">ALL AUDIENCES</SPAN>"
    			+ "<BR>BY THE MOTION PICTURE ASSOCIATION OF AMERICA, INC."
    			+ "</TD>"
    			+ "</TR>"
    
    			+ "<TR>"
    			+ "<TD ALIGN = \"LEFT\">"
    			+ "<A HREF = \"http://www.filmratings.com/\">www.filmratings.com</A>"
    			+ "</TD>"
    			+ ""
    			+ "<TD ALIGN = \"RIGHT\">"
    			+ "<A HREF = \"http://www.mpaa.org/\">www.mpaa.org</A>"
    			+ "</TD>"
    			+ "</TR>"
    			+ "</TABLE>"
    			+ ""
    			+ "</BODY>"
    			+ "</HTML>";
    
    		let webView: WKWebView = view as! WKWebView;
    		webView.loadHTMLString(html, baseURL: nil);
    

Things to try

  1. After you have faded the green band in, fade it out. In the viewDidLoad() method of class ViewController, change the completion: parameter nil to the following closure.
    			{(b: Bool) -> Void in
    				UIView.animateWithDuration(2,
    					delay: 2,
    					options: UIViewAnimationOptions.CurveEaseInOut,
    					animations: {
    						self.view.alpha = 0; //fade back out
    					},
    					completion: nil
    				);
    			}
    
  2. [Visit Westhampton.] In the viewDidLoad method of class ViewController, change
    "http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9236/src/html/preview.html"
    to
    "http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9236/src/html/evacuation.html"
    or to
    "http://oit2.scps.nyu.edu/~meretzkm/swift/html/manhattan.html" (go back to portrait). The evacuation and manhattan examples contain JavaScript. See this Android app.

  3. In the viewDidLoad() method of class ViewController, change preview.html to crawl.html. (Look at crawl.html with Safari. In Firefox, remove the -webkit- prefixes, and change 40° to 25°.) Comment out the fade-in; start with the default alpha level of 1.

  4. To put an IMG in the web page, add the image file to the project the way we added the file preview.html to the project. Then specify a base URL:
    		let bundle: NSBundle = NSBundle.mainBundle();
    		let path: String = bundle.bundlePath;
    		let baseURL: NSURL? = NSURL.fileURLWithPath(path);
    		if baseURL == nil {
    			print("could not create URL");
    			return;
    		}
    		webView.loadHTMLString(html, baseURL: baseURL!);
    

  5. If you want the links to work (www.filmratings.com and www.mpaa.org), add the following property to Info.plist.
    	<key>NSAppTransportSecurity</key>
    	<dict>
    		<key>NSAllowsArbitraryLoads</key>
    		<true/>
    	</dict>
    

  6. Display a navigation bar with a Back button above the WKWebView. Get rid of the fade in and fade out. Append the following code to viewDidLoad().
    		navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back",
    			style: UIBarButtonItemStyle.Plain,
    			target: view,
    			action: #selector(WKWebView.goBack));
    
    In Main.storyboard, create a UINavigationController instead of a ViewController. Insert the following code immediately before the return statement in the application(_:didFinishLaunchingWithOptions:) method of the app delegate.
    		if window == nil {
    			print("window is nil");
    		} else if window!.rootViewController == nil {
    			print("window!.rootViewController is nil");
    		} else {
    			let navigationController: UINavigationController = window!.rootViewController! as! UINavigationController;
    			let viewController: ViewController = ViewController(nibName: nil, bundle: nil);
    			navigationController.pushViewController(viewController, animated: false);
    		}
    

    If necessary to prevent the two links from disappearing off the bottom of the screen, make the padding-bottom in the .html file smaller.

    Can you display the title (or at least the URL) of the current page in the navigation bar? Can you let the user type a URL into a UITextField?

  7. The Back button should be initially disabled, and enabled only when there is a page to go back to. Add the following statement immediately after creating the button in viewDidLoad().
    		navigationItem.leftBarButtonItem!.enabled = false;
    

    Let the view controller act as the delegate of the web view:

    class ViewController: UIViewController, WKNavigationDelegate {
    

    Change the body of loadView() to the following.

    		let webView: WKWebView = WKWebView();
    		webView.navigationDelegate = self;
    		view = webView;
    

    Add the following method to the delegate (which is also the view controller).

    	//Called when the WKWebView arrives at a new page.
    
    	func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
    		if navigationItem.leftBarButtonItem != nil {
    			if webView.canGoBack {
    				navigationItem.leftBarButtonItem!.enabled = true;
    			} else {
    				navigationItem.leftBarButtonItem!.enabled = false;
    			}
    		}
    	}
    
    You can write it more simply like this:
    	func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
    		if navigationItem.leftBarButtonItem != nil {
    			navigationItem.leftBarButtonItem!.enabled = webView.canGoBack;
    		}
    	}
    

  8. Add the following method to the UIWebViewDelegate.
    	func webView(webView: UIWebView, didFailLoadWithError error: NSError) {
    		print("web view failed to load page: \(error)");
    	}
    

  9. Orson downloaded a Wikipedia article from @"http://en.m.wikipedia.org/wiki/The_Third_Man", where the first m stands for “mobile”. Remove the m. and send a User-Agent HTTP header in the HTTP request instead.
    	NSURL *url = [NSURL URLWithString: @"http://en.wikipedia.org/wiki/The_Third_Man"];
    
    	NSMutableURLRequest *request =
    		[NSMutableURLRequest requestWithURL: url];
    
    	//Download the web page in mobile format.
    	[request setValue: @"Mozilla/5.0 (iPhone)"
    		forHTTPHeaderField: @"User-Agent"];
    
    	NSURLResponse *response;
    	NSError *error;
    	NSData *data = [NSURLConnection sendSynchronousRequest: request
    		returningResponse: &response error: &error];
    
    	if (data == nil) {
    		NSLog(@"could not load URL %@: %@", url, error);
    	} else {
    		[webView loadData: data MIMEType: @"text/html"
    			textEncodingName: @"NSUTF8StringEncoding" baseURL: url];
    	}
    
    

    Here are five examples of this header.

    1. When I point iPhone Safari on the Simulator at the URL http://oit2.scps.nyu.edu:3001/ (specifying TCP/IP port number 3001 instead of the default 80), here is what the Safari says to the web server:

      GET / HTTP/1.1
      Host: oit2.scps.nyu.edu:3001
      User-Agent: Mozilla/5.0 (iPhone Simulator; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
      Accept-Language: en-us
      Accept-Encoding: gzip, deflate
      Connection: keep-alive
      
      
    2. When I point iPhone Safari on an actual iPhone at the URL http://oit2.scps.nyu.edu:3001/ (specifying TCP/IP port number 3001 instead of the default 80), here is what the Safari says to the web server:

      GET / HTTP/1.1
      Host: oit2.scps.nyu.edu:3001
      User-Agent: Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_10 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8E600 Safari/6533.18.5
      Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
      Accept-Language: en-us
      Accept-Encoding: gzip, deflate
      Cookie: __utma=3868456.363797723.1320369206.1320369206.1320369206.1; __utmz=3868456.1320369206.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
      Connection: keep-alive
      
    3. When I point a NSData object at the URL http://oit2.scps.nyu.edu:3001/ here is what the NSData object says to the web server:
      GET / HTTP/1.1
      Host: oit2.scps.nyu.edu:3001
      User-Agent: Orson/1.0 CFNetwork/548.0.3 Darwin/10.8.0
      Accept: */*
      Accept-Language: en-us
      Accept-Encoding: gzip, deflate
      Connection: keep-alive
      
      
    4. When I point the web browser on my Android MB612 phone at the URL http://oit2.scps.nyu.edu:3001/ here is what the browser says to the web server:
      GET / HTTP/1.1
      Host: oit2.scps.nyu.edu:3001
      Accept-Encoding: gzip
      Accept-Language: en-US
      x-wap-profile: http://device.sprintpcs.com/Motorola/MOTOMB612/KRNSX41110.rdf
      User-Agent: Mozilla/5.0 (Linux; U; Android 2.2.2; en-us; MB612 Build/KRNS-X4-1.1.10) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1
      Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
      Accept-Charset: utf-8, iso-8859-1, utf-16, *;q=0.7
      
      
    5. When I point the Safari on a desktop Mac at the URL http://oit2.scps.nyu.edu:3001/ here is what the browser says to the web server:
      GET / HTTP/1.1
      Host: oit2.scps.nyu.edu:3001
      User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
      Accept-Language: en-us
      Accept-Encoding: gzip, deflate
      Cookie: amlbcookie=724216000.36895.0000; AMAuthCookie=AQIC5wM2LY4SfcwPauQA9hv-LEaKnjfRHUzNtCk76c8jb6I.*AAJTSQACMDIAAlNLAAoxMjYyOTI3OTQwAAJTMQACMDY.*
      Connection: keep-alive