Each useduiwebview iOS developers are also deeply touched by its many limitations and limited features.Suddenly, this dilemma will change after the introduction of the webkit framework in ios8.In this article I will dive into webkit to experience the benefits it brings us,Also see what's new in the newly added sfsafariviewcontroller in ios9.

Common browsing behavior

The so-called universal browsing behavior can be summarized into the following types:

Page loading progressgo aheadBackRefresh

It would also be cumbersome to make a dedicated controller for every app that uses webview,I used to use other third party packages.

But now, if you use wkwebview it will become very convenient,Speak in code:

class viewcontroller:uiviewcontroller {
  var webview:wkwebview!
  @iboutlet weak var progressview:uiprogressview!
  required init (coder adecoder:nscoder) {
    super.init (coder:adecoder)!
    //instantiate wkwebview
    self.webview=wkwebview (frame:cgrectzero)
  override func viewdidload () {
    super.viewdidload ()
    //programmatically join wkwebview
    view.addsubview (webview)
    view.insertsubview (webview, abovesubview:progressview)
    let widthconstraint=nslayoutconstraint (item:webview, attribute:.width, relatedby:.equal, toitem:view, attribute:.width, multiplier:1, constant:0)
    view.addconstraint (widthconstraint)
    let heightconstraint=nslayoutconstraint (item:webview, attribute:.height, relatedby:.equal, toitem:view, attribute:.height, multiplier:1, constant:-46)
    view.addconstraint (heightconstraint)
    //detect changes in webview object properties
    webview.addobserver (self, forkeypath:"loading", options:.new, context:nil)
    webview.addobserver (self, forkeypath:"title", options:.new, context:nil)
    //Load web page
    let request=nsurlrequest (url:nsurl (string:"http://ray.dotnetage.com")!)
    webview.loadrequest (request)
  override func observevalueforkeypath (keypath:string? ;, ofobject object:anyobject? ;, change:[string:anyobject]? ;, context:unsafemutablepointer<void >>{
    if (keypath == "loading") {
      //check button availability
      stopbutton.image=webview.loading?uiimage (name:"cross"):uiimage (named:"syncing")
    } else if keypath == "title" {
    } else if keypath == "estimatedprogress" {
      progressview.hidden=webview.estimatedprogress == 1
      progressview.setprogress (float (webview.estimatedprogress), animated:true)

I don't think there is much to say about these codes,Except that wkwebview cannot be visually constructed through ib,The above code is at most an optimization of the autolayout part of the code.Write and write, just be an example.

Communicating with javascript

With webkit, there is no need to communicate with the dom through a javascript bridge. In fact, this is not a new technology.As early as windows98, in vb or delphi, you can also communicate with dom through the com interface in a completely similar way.

Not much nonsense,Talk about the basic principles of webkit.The following is a schematic diagram of the communication relationship between the web process of the webkit host and the main app process:

There are two processes here

Execute javascript script

We can include javascript scripts in app bundles as application resources.Inject it into the target web page via webkit for execution at runtime.

First we need to prepare a landing page.Here is an example using my own blog

Now, I will change the text of the headline on the homepage of my blog.The specific code is simple:

$(". page-header h1"). text ("ios injection test");

Then, add a code calledin the ios project inject.js script file,Copy the above code inside.

The javascript script included in the app is best executed once in the browser's console,To ensure that the script itself can be executed correctly.If the script contains potential errors,It cannot be detected in the app.

Then, create ainside the controller's constructor wkwebviewconfiguration instance and pass it as a parameter towkwebview constructor,The specific code is as follows:

import webkit
class viewcontroller:uiviewcontroller {
var webview:wkwebview!
required init (coder adecoder:nscoder) {
super.init (coder:adecoder)!
let configuation=wkwebviewconfiguration ()
configuation.usercontentcontroller.adduserscript (getuserscript ("inject"))
self.webview=wkwebview (frame:cgrectzero, configuration:configuation)
  //read javascript script from resource
  func getuserscript (fromname:string)->wkuserscript {
    let filepath=nsbundle.mainbundle (). pathforresource (fromname, oftype:"js")
    let js=try! string (contentsoffile:filepath !, encoding:nsutf8stringencoding)
    return wkuserscript (source:js, injectiontime:.atdocumentend, formainframeonly:true)

Another thing to note in this snippet is the custom methodgetuserscript () Returnedwkuserscript object. We can passinjectiontime Decide whether to inject the script into the beginning of the html or the end of the document.

Execute the code again,The effect is as follows:

In other words, we can inject javascript through webkit in the app and then manipulate all dom objects in the page arbitrarily!

javascript callback

In addition to injecting code into the browser from the app side,Perform an action.In some cases, we need to do some processing from the webpage.For example, read out some elements in a web page and turn them into a collection of json objects.Pass it back to the app for processing. Or maybe our app wants to read all the images on the page at once after loading a web page,When the user clicks on these images, we use the app's native method to preview in full screen.And so on.In these contexts:

We all have to return objects from the page

That is,To communicate with the app process within the process of the web page,Then we need to use in the script:

webkit.messagehandlers. {messagename} .postmessage ([params]);

This method cannot be executed directly in a standard html5 browser.Such as chrome and safair. This code only appears on pages via webkit hostwebkitobject. This is not difficult to understand,It's just that webkit injectedinto windows after loading the page webkit This example,Make it possible for javascript to send information to the app.

If we were to send a message to the app,Example:After a button on the page is clicked,Execute the code to open the album in the app.Then you have to write this code in javascript:

$("#mybutton"). click (function () {
  webkit.messagehandlers.openphotolibrary.postmessage ();

Watch out foropenphotolibrary This object is not available in swift. When this method is passed back to swift, this is just the name of a message.To receive this information from the browser in Swift, our controller needs to implementwkscriptmessagehandlerThis interface,It has only one method,Let's spend more time directly opening the code of this interface:

/*! a class conforming to the wkscriptmessagehandler protocol provides a
 method for receiving messages from javascript running in a webpage.
 * /
public protocol wkscriptmessagehandler:nsobjectprotocol {
  /*! @abstract invoked when a script message is received from a webpage.
   @param usercontentcontroller the user content controller invoking the
   delegate method.
   @param message the script message received.
   * /
  @available (ios 8.0, *)
  public func usercontentcontroller (usercontentcontroller:wkusercontentcontroller, didreceivescriptmessage message:wkscriptmessage)

Then, we directly implement this interface:

class viewcontroller:uiviewcontroller, wkscriptmessagehandler {
  required init (coder adecoder:nscoder) {
    //... the previous code is the same as above
    configuation.usercontentcontroller.addscriptmessagehandler (self, name:"openphotolibrary")
    self.webview=wkwebview (frame:cgrectzero, configuration:configuation)
  func usercontentcontroller (usercontentcontroller:wkusercontentcontroller, didreceivescriptmessage message:wkscriptmessage) {
    if message.name == "openphotolibrary" {
      //Here you can add the code to open the album

One or two principles can be seen from the code:

Constructingwkwebview Before usingaddscriptmessagehandler method to register a message name with the configuration object,The routine here is "openphotolibrary".Implementwkscriptmessagehandler interface fromusercontentcontroller () <code>of themethodmessage.nameparameter to determine the source of the message,Execute the corresponding code.

Also, if we need to pass objects to the app from a javascript script,Directly inpostmessage () method to enter the object as a parameter,But usually the type of this parameter should be an array or a normal json object, so that the app can use the dictionary object to read it again.

For example, I read the addresses and names of all the menus from the current web page.And generated amenus javascript array object:

var menus=$(". navbar a"). map (function (n, i) {
  return {
    title:$(n) .text,    link:$(n) .attr ("href")
webkit.messagehandlers.didfetchmenus.postmessage (menus);

Skip the interface implementation here,Look directly atusercontentcontroller Method implementation:

var menus:[menus]?
func usercontentcontroller (usercontentcontroller:wkusercontentcontroller, didreceivescriptmessage message:wkscriptmessage) {
  if message.name == "didfetchmenus" {
    if let resultarray=message.body as?[dictionary<string, string>] {
      menus=resultarray.map {menu (dict:$0)}
      //Take out and convert json to swift's menu object
      print (menus)

safair browser in ios9

added in ios9 safariservices This new module,Its role is to provide a full-featured embedded safair.sfsafariviewcontroller can be used like a normal controller.

Here is a simple example

import uikit
import safariservices
class viewcontroller:uiviewcontroller {
  @ibaction func openbrowser (sender:anyobject) {
    let safari=sfsafariviewcontroller (url:nsurl (string:"http://www.apple.com")!)
    self.showviewcontroller (safari, sender:self)

sfsafariviewcontroller The biggest difference betweenand webkit issfsafariviewcontroller There is no controllable method.It's just a controller that can be fully embedded in the app,Avoid jumping out of the current app if you open an external link as before, andsfsafariviewcontroller All features in embedded safari and safari are the same,Features such as 3d touch and page breaks are also supported.And when our app uses an external web account for integrated login,safari can directly get the application context of the current app,There is no need to jump out and log in externally before returning to the app. This undoubtedly greatly enhances the app experience when integrating with safari.

The comments on the choice of webkit and safariservices on Apple's developer website are as follows:

Choose webkit if you need to interact with the webIf you need to have the same experience with safari and do not need to interact with the webpage, we recommend using safariservices

This is indeed a very good update.

  • Previous In-depth analysis of the relationship between prototype and proto in JavaScript
  • Next Summary of Jquery Ajax request methods (worth collecting)