10
votes

In my iOS app written in Swift 2.1, I use a WKWebView to load a HTML string which is loaded from a wordpress blog using a JSON parser.

I implemented the delegate method func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) { and set the navigation delegate of the WKWebView to self in order to handle link presses.

Now, when I open the ViewController which contains the WKWebView, this delegate method gets called once, which is the behaviour you would expect on load of the webView - so the delegate seems to be set properly.

My problem now is that most time the links which the webView contains are not clickable. You would usually expect that a gray background appears, when you a press a link as you can see in the image below. But most time, when I press a link, the gray background doesn't appear, so when I touch up, the delegate method doesn't get called. This problem certainly doesn't have to do something with a misconfiguration of the WKNavigationDelegate, since sometimes the link selection works properly (about 10 %).

appearance of links when you select them

Do you have any idea why links are randomly sometimes not clickable, and sometimes clickable (10 % of the cases)?


3
Would be good if you could show us your html.adrianokw
Have you forgot importing QuartzCore?return true

3 Answers

2
votes

I going to suppose that what you want is have some description or text in some place and allow inside some part of the text make clickable/tapped and it's for this that you want to use WKWebView.

I solved this problem using WKWebView too in some app I made a long time ago, of course there are several solutions, it's just one of them, nothing else.

As some people said to you you can use the loadHTMLString function to load the HTML string from your server in JSON or anyway you want, but it's very important that the HTML is good formatted without errors.

A very important point about the loadHTMLString function regarding your comment ...You would usually expect that a a gray background appears when you a press a link as you can see in the image below, it not happen if the HTML don't have some kind of CSS style, because if not it have the default style of any of the <a><\a>.

So let's see the following code:

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate {

   var wkWebView: WKWebView!

   override func viewDidLoad() {
       super.viewDidLoad()

       // The part of the HTMl you want to show
       let description = "Let's start to visit the <a href=\"http://www.apple.com/\">Apple</a> website first and then if you want the <a href=\"http://www.w3schools.com\">Developer Apple</a> website."


       // The default HTML with body and styles formatted and the description inside using string interpolation.
       let htmlString = "<html><head><style type=\"text/css\">body {font-family: \"Lato-Regular\";font-size: 35px;color: #333333;margin: 0;}a {text-decoration: none; color: #999;}</style></head><body>\(description)</body></html>"

       let preferences = WKPreferences()
       preferences.javaScriptEnabled = false

       // Configuration for any preferences you want to set
       let configuration = WKWebViewConfiguration()
       configuration.preferences = preferences

       wkWebView = WKWebView(frame: CGRectMake(5, 35, self.view.bounds.width, self.view.bounds.height), configuration: configuration)

       if let theWebView = wkWebView {
          theWebView.loadHTMLString(htmlString, baseURL: NSURL())
          theWebView.navigationDelegate = self
          self.view.addSubview(theWebView)
       }
   }

   override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning()
      // Dispose of any resources that can be recreated.
   }

   func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
      UIApplication.sharedApplication().networkActivityIndicatorVisible = true
   }


   func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
      UIApplication.sharedApplication().networkActivityIndicatorVisible = false
   }

   func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
     let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .Alert)
     alert.addAction(UIAlertAction(title: "Ok", style: .Default) { (UIAlertAction) -> Void in
         UIApplication.sharedApplication().networkActivityIndicatorVisible = false
     })

     presentViewController(alert, animated: true, completion: nil)
   }
}

In the above code I set the variable description with some text with clickable elements inside it and added the two delegate methods didStartProvisionalNavigation and didFinishNavigation to show an networkActivityIndicatorVisible in the status bars to show the some progress about the loading of the page when the clickable text it's tapped. It's good to notice that in the htmlString variable I set some styles to show the <a> with some color and font, it's up to you.

I added too the didFailProvisionalNavigation function to show an alert in some case the url is not valid of something like the new HTTPTransportSecurityLayer added is iOS 9 to only allow https, in some cases you can use it to validate the url and know the reason of the error.

The result is the following text in a normal UIViewController:

enter image description here

And when you tap any gray text the url would be loaded inside the same WKWebView, of course you can personalize this to open in a inside browser you do or something else, it's up to you.

And then you can see the result right away if you tap Apple:

enter image description here

If instead you tap Developer Apple you can see the alert in action because the http protocol give an error in the HTTPTransportSecurityLayer in iOS 9:

enter image description here

I have the project ready to upload to Github if you need it. I hope this help you.

2
votes

The solution is to remove the use of contentInset. Unfortunately, WKWebView isn't designed to handle the use of it (when contentInset is negative), so we must use a workaround by simply adding a empty div to the htmlString, just like that:

<div style='width:100%;height:100px'></div>

If the bottom inset should be changed without reloading the entire page, one could at first insert a huge div at the bottom and then compensate it by adding a positive contentInset. This is exactly what this extension does:

extension WKWebView {

   /// The maximum possible height of the webView bottom content inset
   @nonobjc static let maxBottomHeight: CGFloat = 20000

   /// Provides an easy way to set the real bottom inset of the webView. Due to a bug in WKWebView framework, we first insert an emtpy div with the size WKWebView.maxContentSize on the bottom of the webView and compensate it by setting a positive contentInset (should be positive, otherwise the bug will be there again).
   func set(bottomInset inset: CGFloat) {

       self.scrollView.contentInset.bottom = inset - WKWebView.maxBottomHeight
   }
}

Don't forget to add the div at the end of the html string:

let htmlFooter = "<div style='width:100%;height:\(String(describing: WKWebView.maxBottomHeight))px'></div></body></html>"
1
votes

when you load your content into WKWebView, you might set the wrong baseURL for your webpage, and it leads to invalid link, in order for it working correctly, you should have your baseURL set to the correct http or https URL for your content, for those working link just because they had full url in their href.

here is the html text load into the WKWebView to illustrate the effect:

webView.loadHTMLString(content, baseURL: NSURL(string: ""));

content:

<html lang="en"><head><meta name="viewport" content="width=device-width, initial-scale=1"></head>
    <body>
        <h1> click testing </h1>
        <ul>
            <li><a href="http://www.w3schools.com">Visit W3Schools</a></li>
            <li><a href="/about">Un-Clickable without baseURL</a></li>
            <li><a href="about://about">Clickable without baseURL</a></li>
        </ul>
    </body>
</html>