iOS has a complicated relationship with the web. And it goes back to the very inception of the platform nearly a decade ago.
It's difficult to appreciate just how differently the first iPhone could have turned out. The iconic touchscreen device we know and love today was just one option of the table. Early prototypes explored the use of a physical keyboard and a touch screen + stylus combo, with screen dimensions going up to 5" x 7". Even the iPod click wheel was a serious contender for a time.
But perhaps the most significant early decision to be made involved software, not hardware.
How should the iPhone run software? Apps, like on OSX, or as web pages, using Safari? That choice to fork OS X and build iPhoneOS had widespread implications, and remains a contentious decision to this day.
Consider this infamous line from Steve Jobs' WWDC 2007 keynote:
The full Safari engine is inside of iPhone. And so, you can write amazing Web 2.0 and Ajax apps that look exactly and behave exactly like apps on the iPhone. And these apps can integrate perfectly with iPhone services. They can make a call, they can send an email, they can look up a location on Google Maps.
The web has always been a second-class citizen on iOS (which is ironic, since the iPhone is largely responsible for the mobile web existing as it does today). UIWebView
is massive and clunky and leaks memory. It lags behind Mobile Safari, which has the benefit of the Nitro JavaScript engine.
However, all of this has changed with the introduction of WKWebView
and the rest of the WebKit
framework.
WKWebView
is the centerpiece of the modern WebKit API introduced in iOS 8 & Mac OS X Yosemite. It replaces UIWebView
in UIKit and WebView
in AppKit, offering a consistent API across the two platforms.
Boasting responsive 60fps scrolling, built-in gestures, streamlined communication between app and webpage, and the same JavaScript engine as Safari, WKWebView
is one of the most significant announcements to come out of WWDC 2014.
What was a single class and protocol with UIWebView
& UIWebViewDelegate
has been factored out into 14 classes and 3 protocols in WKWebKit. Don't be alarmed by the huge jump in complexity, though—this new architecture allows for a ton of new features:
WKWebKit Framework
Classes
WKBackForwardList
: A list of webpages previously visited in a web view that can be reached by going back or forward.
WKBackForwardListItem
: Represents a webpage in the back-forward list of a web view.WKFrameInfo
: Contains information about a frame on a webpage.WKNavigation
: Contains information for tracking the loading progress of a webpage.
WKNavigationAction
: Contains information about an action that may cause a navigation, used for making policy decisions.WKNavigationResponse
: Contains information about a navigation response, used for making policy decisions.WKPreferences
: Encapsulates the preference settings for a web view.WKProcessPool
: Represents a pool of Web Content processes.WKUserContentController
: Provides a way for JavaScript to post messages and inject user scripts to a web view.
WKScriptMessage
: Contains information about a message sent from a webpage.WKUserScript
: Represents a script that can be injected into a webpage.WKWebViewConfiguration
: A collection of properties with which to initialize a web view.WKWindowFeatures
: Specifies optional attributes for the containing window when a new web view is requested.
Protocols
WKNavigationDelegate
: Provides methods for tracking the progress of main frame navigations and for deciding load policy for main frame and subframe navigations.WKScriptMessageHandler
: Provides a method for receiving messages from JavaScript running in a webpage.WKUIDelegate
: Provides methods for presenting native user interface elements on behalf of a webpage.
API Diff Between UIWebView
& WKWebView
WKWebView
inherits much of the same programming interface as UIWebView
, making it convenient for apps to migrate to WKWebKit (and conditionally compile as necessary while iOS 8 gains widespread adoption).
For anyone interested in the specifics, here's a comparison of the APIs of each class:
UIWebView | WKWebView |
---|---|
var scrollView: UIScrollView! { get } | var scrollView: UIScrollView! { get } |
var configuration: WKWebViewConfiguration! { get } | |
var delegate: UIWebViewDelegate! | var UIDelegate: WKUIDelegate! |
var navigationDelegate: WKNavigationDelegate! | |
var backForwardList: WKBackForwardList! { get } |
Loading
UIWebView | WKWebView |
---|---|
func loadRequest(request: NSURLRequest!) | func loadRequest(request: NSURLRequest!) -> WKNavigation! |
func loadHTMLString(string: String!, baseURL: NSURL!) | func loadHTMLString(string: String!, baseURL: NSURL!) -> WKNavigation! |
func loadData(data: NSData!, MIMEType: String!, textEncodingName: String!, baseURL: NSURL!) | |
var estimatedProgress: Double { get } | |
var hasOnlySecureContent: Bool { get } | |
func reload() | func reload() -> WKNavigation! |
func reloadFromOrigin() -> WKNavigation! | |
func stopLoading() | func stopLoading() |
var request: NSURLRequest! { get } | |
var URL: NSURL! { get } | |
var title: String! { get } |
History
UIWebView | WKWebView |
---|---|
func goToBackForwardListItem(item: WKBackForwardListItem!) -> WKNavigation! | |
func goBack() | func goBack() -> WKNavigation! |
func goForward() | func goForward() -> WKNavigation! |
var canGoBack: Bool { get } | var canGoBack: Bool { get } |
var canGoForward: Bool { get } | var canGoForward: Bool { get } |
var loading: Bool { get } | var loading: Bool { get } |
Javascript Evaluation
UIWebView | WKWebView |
---|---|
func stringByEvaluatingJavaScriptFromString(script: String!) -> String! | |
func evaluateJavaScript(javaScriptString: String!, completionHandler: ((AnyObject!, NSError!) -> Void)!) |
Miscellaneous
UIWebView | WKWebView |
---|---|
var keyboardDisplayRequiresUserAction: Bool | |
var scalesPageToFit: Bool | |
var allowsBackForwardNavigationGestures: Bool |
Pagination
WKWebView
currently lacks equivalent APIs for paginating content.
var paginationMode: UIWebPaginationMode
var paginationBreakingMode: UIWebPaginationBreakingMode
var pageLength: CGFloat
var gapBetweenPages: CGFloat
var pageCount: Int { get }
Refactored into WKWebViewConfiguration
The following properties on UIWebView
have been factored into a separate configuration object, which is passed into the initializer for WKWebView
:
var dataDetectorTypes: UIDataDetectorTypes
var allowsInlineMediaPlayback: Bool
var mediaPlaybackRequiresUserAction: Bool
var mediaPlaybackAllowsAirPlay: Bool
var suppressesIncrementalRendering: Bool
JavaScript ↔︎ Swift Communication
One of the major improvements over UIWebView
is how interaction and data can be passed back and forth between an app and its web content.
Injecting Behavior with User Scripts
WKUserScript
allows JavaScript behavior to be injected at the start or end of document load. This powerful feature allows for web content to be manipulated in a safe and consistent way across page requests.
As a simple example, here's how a user script can be injected to change the background color of a web page:
letsource="document.body.style.background = \"#777\";"letuserScript=WKUserScript(source:source,injectionTime:.AtDocumentEnd,forMainFrameOnly:true)letuserContentController=WKUserContentController()userContentController.addUserScript(userScript)letconfiguration=WKWebViewConfiguration()configuration.userContentController=userContentControllerself.webView=WKWebView(frame:self.view.bounds,configuration:configuration)
WKUserScript
objects are initialized with JavaScript source, as well as whether it should be injected at the start or end of loading the page, and whether this behavior should be used for all frames, or just the main frame. The user script is then added to a WKUserContentController
, which is set on the WKWebViewConfiguration
object passed into the initializer for WKWebView
.
This example could easily be extended to perform more significant modifications to the page, such as removing advertisements, hiding comments, or maybe changing all occurrences of the phrase "the cloud" to "my butt".
Message Handlers
Communication from web to app has improved significantly as well, with message handlers.
Just like console.log
prints out information to the Safari Web Inspector debug console, information from a web page can be passed back to the app by invoking:
window.webkit.messageHandlers.{NAME}.postMessage()
What's really great about this API is that JavaScript objects are automatically serialized into native Objective-C or Swift objects.
The name of the handler is configured in addScriptMessageHandler()
, which registers a handler conforming to the WKScriptMessageHandler
protocol:
classNotificationScriptMessageHandler:WKScriptMessageHandler{funcuserContentController(userContentController:WKUserContentController!,didReceiveScriptMessagemessage:WKScriptMessage!){println(message["body"])}}letuserContentController=WKUserContentController()lethandler=NotificationScriptMessageHandler()userContentController.addScriptMessageHandler(handler,name:"notification")
Now, when a notification comes into the app, such as to notify the creation of a new object on the page, that information can be passed with:
window.webkit.messageHandlers.notification.postMessage({body:"..."});
Add User Scripts to create hooks for webpage events that use Message Handlers to communicate status back to the app.
The same approach can be used to scrape information from the page for display or analysis within the app.
For example, if someone were to build a browser specifically for NSHipster.com, it could have a button that listed related articles in a popover:
// document.location.href == "http://nshipster.com/webkit"functiongetRelatedArticles(){varrelated=[];varelements=document.getElementById("related").getElementsByTagName("a");for(i=0;i<related.length;i++){vara=elements[i];related.push({href:a.href,title:a.title});}window.webkit.messageHandlers.related.postMessage({articles:related});}
letjs="getRelatedArticles();"self.webView?.evaluateJavaScript(js){(_,error)inprintln(error)}// Get results in previously-registered message handler
If your app is little more than a thin container around web content, WKWebView
is a game-changer. All of that performance and compatibility that you've longed for is finally available. It's everything you might have hoped for.
If you're more of a native controls purist, you may be surprised at the power and flexibility afforded by the new technologies in iOS 8. It's a dirty secret that some stock apps like Messages use WebKit to render tricky content. The fact that you probably haven't noticed should be an indicator that web views actually have a place in app development best practices.