11

I have an app where users can navigate a pile of locally stored HTML files. I have a UIWebView, configured up correctly with a UIWebViewDelegate. Usually, when the user follows a link, shouldStartLoadWithRequest is called, followed by webViewDidFinishLoad a bit later.

But, if the link is pointing to an anchor on the same page as the one which is currently displayed, only shouldStartLoadWithRequest is called. webViewDidFinishLoad does not fire.

In a sense, I see that this might be expected behaviour, because in-page navigation should not require a page reload. However, I really need a place to hook into the call stack after in-page navigation is complete. The optimal solution would let me know when any sort of navigation has ended, both from another page, in-page and forward/backward actions.

My best hackaround so far has been to call performSelector: withObject: afterDelay: at the end of my shouldStartLoadWithRequest method, but I'm not happy with this.

Does anyone know how I can solve this correctly? Any insight appreciated!

thomax
  • 9,213
  • 3
  • 49
  • 68
  • I'm not seeing the shouldStartLoadWithRequest getting called for any anchor click, only full page loads. I'm curious if this is behavior that has changed, or if not, I must be doing something wrong. – Jason Jun 24 '14 at 16:31

6 Answers6

8

You can try to use NSURLConnectionDataDelegate, it allows you to handle incoming data. Maybe you can determine if the page is loaded manually by adding a sign to your html files.

NSURLConnectionDataDelegate Reference

Edit: gist.github.com/buranmert/7304047 I wrote a piece of code and it worked, that may not be the way you wanted it to work but maybe it helps. Every time user clicks a URL with anchor, it creates another connection and as connection finishes loading web view loads the data, that marks the point where web view finished loading the page. As you use only local html files, I do not think creating connections will create problems

Mert Buran
  • 2,989
  • 2
  • 22
  • 34
  • Thanks for the answer! The only method on the `NSURLConnectionDataDelegate` which seems to hold any potential in my case is `connectionDidFinishLoading`. Reading the method description, it doesn't sound too promising as it's doubtful if an in-page navigation will trigger a load. I'll give it a try, though. – thomax Nov 04 '13 at 12:04
  • Yay! I got this working! By far the best solution suggested here, in my opinion. Thanks a lot for taking the time to help me. The bounty is yours, good sir! – thomax Nov 11 '13 at 15:59
  • Actually, a callback/hook on `webView.renderingFinished` would have been 100% perfect, but this is close enough. – thomax Nov 11 '13 at 16:08
  • Yes, I thought it first, it would the solution, this is more of a workaround. I'm glad it helped you, buddy – Mert Buran Nov 11 '13 at 16:11
  • Another cool thing about this solution is that `UIWebViewNavigationTypeBackForward` will reveal if back/forward buttons were used. – thomax Nov 11 '13 at 16:13
  • @MertBuran See http://stackoverflow.com/questions/30380848/uiwebviewdelegate-webviewdidfinishload-not-called-for-fragment-tags -- maybe an easy question for you. – Paul Cezanne May 21 '15 at 17:45
6

What you are describing is intended behavior. Just as AJAX or resource requests are never passed to the delegate, only root page changes will ever hit webViewDidFinishLoad:. But I have good news, and it doesn't involve saving a bunch of money on car insurance.

Loads performed within an iFrame DO trigger the full delegate methods and this gives you a solution. You can use this mechanism to post a notification to the native code, just as is often done for console.log() as described in this post. Alternatively, Native Bridge would work well to call into your Objective C code from JavaScript.

Community
  • 1
  • 1
Holly
  • 5,270
  • 1
  • 24
  • 27
  • +1 I'm sure this would work, but it feels more like a hack than the more iOS-ish suggestion by Mert Buran. Thanks a lot for helping out, though! – thomax Nov 11 '13 at 16:03
2

Just check weather u got the delegate function DidStartLoading if it is called no doubt that DidFinish also should get called

Nagaraj
  • 802
  • 9
  • 11
  • Nope, it doesn't get called on in-page navigation with a fragment on the URL. – thomax Nov 11 '13 at 16:14
  • Check weather **webView:shouldStartLoadWithRequest:navigationType:** is returning "NO", and another possibility is webview delegate is set to "nil" – Nagaraj Nov 12 '13 at 04:37
  • 1
    `shouldStartLoadWithRequest`returns `YES` and the delegate is set properly. This is, in fact, the premise for my question. – thomax Nov 12 '13 at 07:53
  • Check weather function call comes to delegate **shouldStartLoadWithRequest** and weather webview is loading the page. If every thing is fine, Then if possible post ht ecode snippet so that i can check. – Nagaraj Nov 13 '13 at 06:21
1

Are you sure your shouldStartLoadWithRequest always returns an YES???

I always add

return YES;

at the end of shouldStartLoadWithRequest implementation.And that works for me. By returning YES, it denotes that the webview has loaded and would call the webViewDidFinishLoad

Shob-Z
  • 891
  • 6
  • 26
1
if([webView isLoading]==1)
{
    //your webview is loading
}
else
{
   //your webview has loaded
}
Unheilig
  • 16,196
  • 193
  • 68
  • 98
nicael
  • 18,550
  • 13
  • 57
  • 90
  • Yes, but polling the webView for it's load state is not an OS callback/hook, but the opposite. – thomax Nov 11 '13 at 16:17
0

Here is a Swift Implementation of Mert Buran's code incase anybody is looking for it. (Although NSURLConnection is deprecated as of iOS 9)

But it does not solve my problem. When i click on a jquery link that popups a video, it does not fire the webViewDidFinishLoad.

class WebViewController: UIViewController, UIWebViewDelegate, NSURLConnectionDelegate {

var menuURL: String?

var response: NSURLResponse?
var data = NSData()

// MARK: Properties
@IBOutlet weak var webView: UIWebView!

override func viewDidLoad() {
    super.viewDidLoad()

    webView.delegate = self

    // From Web
    let url = NSURL (string: menuURL!)
    let urlRequest = NSURLRequest(URL: url!)

    let connection = NSURLConnection(request: urlRequest, delegate: self)
    self.data = NSData()
    connection!.start()

    // if this is false, page will be 'zoomed in' to normal size
    webView.scalesPageToFit = false

}

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    if navigationType == .LinkClicked && request.URL!.fragment != nil {
        let connection = NSURLConnection(request: request, delegate: self)
        connection!.start()
        return false
    }
    return true
}

func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
    self.response = response
}

func connection(connection: NSURLConnection, didReceiveData data: NSData) {
    let oldData = NSMutableData(data: self.data)
    oldData.appendData(data)
    self.data = oldData
}

func connectionDidFinishLoading(connection: NSURLConnection) {
    self.webView.loadData(self.data, MIMEType: self.response!.MIMEType!, textEncodingName: "utf-8", baseURL: self.response!.URL!)
    self.data = NSData()
}


}
GagaBytes
  • 15
  • 8