54

I have some code that needs to run after the a UIWebView finishes loading a document. For that I've set the UIWebView's delegate to my controller, and implemented the webViewDidFinishLoading method.

This gets called multiple times, depending on the type of page to load. I'm not sure if it's because of ajax requests, requests for images, or maybe even iframes.

Is there a way to tell that the main request has finished, meaning the HTML is completely loaded?

Or perhaps delay my code from firing until all of those events are done firing?

Cezar
  • 55,636
  • 19
  • 86
  • 87
Ben Scheirman
  • 40,531
  • 21
  • 102
  • 137
  • 3
    3 yrs late to the game, but have you found a dependable solution? You have selected an answer, claiming that passed `webView` gives you *actual current request URL* which totally differs from my observation. I do always get the original `[UIWebView loadRequest]` URL, hence no clue what's being started/loaded. I also made [my own summarising post](http://stackoverflow.com/questions/15343516/ios-uiwebview-page-load-observability) with no answer so far. – Pavel Zdenek Mar 12 '13 at 14:10

9 Answers9

54

You can do something like this to check when loading is finished. Because you can have a lot of content on the same page you need it.

- (void)webViewDidFinishLoad:(UIWebView *)webview  {
    if (webview.isLoading)
        return;
    // do some work
}
Jens Erat
  • 37,523
  • 16
  • 80
  • 96
Scott Densmore
  • 1,459
  • 10
  • 10
  • ok this sort of worked (and in fact much cleaner than the NSTimer abomination I had previously concocted). It does unfortunately get to the //do some work section more than once, most likely because there is a delay between requests. Any ideas on how to ensure that it only gets called once? – Ben Scheirman Dec 04 '09 at 15:14
  • When you say "delay between requests", are you making multiple requests with an NSUrl? You could use the request property of webview if you need to correlate the requests. If you want direct msg me on twitter and we can figure this out then update this post. – Scott Densmore Dec 04 '09 at 15:59
  • yeah, eventually that if(webview.loading) returns false, and I execute my code (show an alert, say). The alert box is shown twice, because apparently something else starts loading after the main request is finished... I'm not sure. – Ben Scheirman Dec 04 '09 at 22:28
  • @Ari have you actually seen that any time? For me didStartLoad always gives webView.loading==true and didFinishLoad gives webView.loading==false. I wonder what the flag would be for if the logic was so simple minded. – Pavel Zdenek Mar 12 '13 at 13:55
49

It could be enlightening (if you haven't gone this far yet) to NSLog a trace of load starts and finishes.

    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
       NSLog(@"Loading: %@", [request URL]);
       return YES;
    }


    - (void)webViewDidFinishLoad:(UIWebView *)webView {
       NSLog(@"didFinish: %@; stillLoading: %@", [[webView request]URL],
            (webView.loading?@"YES":@"NO"));
    }


    - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
       NSLog(@"didFail: %@; stillLoading: %@", [[webView request]URL],
            (webView.loading?@"YES":@"NO"));
    }

I just watched the calls to all three in one of my projects which loads a help page from my bundle and contains embedded resources (external css, YUI!, images). The only request that comes through is the initial page load, shouldStartLoadWithRequest isn't called for any of the dependencies. So it is curious why your didFinishLoad is called multiple times.

Perhaps what you're seeing is due to redirects, or as mentioned, ajax calls within a loaded page. But you at least should be able balance calls to shouldStartLoad and either of the other two delegate functions and be able to determine when the loading is finished.

devios1
  • 36,899
  • 45
  • 162
  • 260
wkw
  • 3,865
  • 3
  • 25
  • 24
  • This ultimately helped me figure it out. I was using my own webView ivar to determine the request URL, but that never changed. The webView passed into this method contained the actual current request URL and I could see all of the additional content loading (iframes, etc). Thanks for the help! – Ben Scheirman Dec 11 '09 at 12:57
  • 7
    Shouldn't it be (webView.loading?@"YES":@"NO") instead of (webView.loading?@"NO":@"YES") – Vibhor Goyal Aug 19 '10 at 03:45
  • wkw, I've got the same problem. Can you please help me; I just can't figure out the way how'd you 'balance' calls to these delegate functions? – wh1t3cat1k Dec 16 '10 at 08:25
  • 1
    - (void)webViewDidFinishLoad:(UIWebView *)webView{ self.loadBalance--; if (self.loadBalance == 0) // ACTUAL page finished load } } - (void)webViewDidStartLoad:(UIWebView *)webView{ self.loadBalance++; } – Mike S Jul 12 '11 at 06:50
  • I'm seeing the page redirects give me two shouldStart calls and one didFinish call. Anybody know how to deal with this? – Colin Jan 09 '12 at 17:17
  • Thx @MikeS but you should probably also watch for didFailLoadWithError: – jd291 Aug 22 '12 at 10:31
  • @VibhorGoyal You are right; I edited the answer. That was clearly confusing. – devios1 Aug 15 '14 at 17:31
  • very useless answer. I load local html with iframe (from youtube). If I use your example then `webViewDidFinishLoad:` is executer for 1st time and fails for next 2 calls. Do you really think it is a correct behaviour? – Vyachaslav Gerchicov Feb 20 '17 at 13:26
  • @VyachaslavGerchicov the answer isn't useless, but it is "dated" now. It was written in 2009. It's important to consider the age of SO posts and comments when evaluating their content. Just saying... – wkw Mar 01 '17 at 19:20
12

Check this one it so simply and easy way to achieve no need to write too much code:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
        if ([[webView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]) {
            // UIWebView object has fully loaded.
        }
    }
Vinod Singh
  • 1,374
  • 14
  • 25
  • How would you transfer this to Swift? Since `stringByEvaluatingJavascript(from:)` returns a `String?`, I am testing against `nil`, but it **never fails**. Perhaps I should test against the empty string? – Nicolas Miari Jul 19 '18 at 05:38
9

This question is already solved, but I see it lacks an answer that actually explains why multiple calls to webViewDidFinishLoad are actually expected behavior

The aforementioned method is called every time the webview finishes loading a frame. From the UIWebViewDelegate protocol documentation:

webViewDidFinishLoad:
Sent after a web view finishes loading a frame.

In fact, this is also true for all the other methods that comprise the UIWebViewDelegate protocol.

Cezar
  • 55,636
  • 19
  • 86
  • 87
7

Try this it will work fine

 -(void)webViewDidFinishLoad:(UIWebView *)webview  
    {
       if (webview.isLoading)
           return;
       else
       {
           // Use the code here which ever you need to run after webview loaded 
       }
    }
Yogeesh H T
  • 2,777
  • 21
  • 18
3

This happens because the callback method is called every time a frame is done loading. In order to prevent this set the "suppressesIncrementalRendering" property of the webview to true. this will prevent the webview from rendering until the entire data is loaded into the memory. This did the trick for me

2

I have notice something similar and it was a confusion: I have a UITabBarController, it seems to preload all ViewControllers linked to its tabs on launching the App (in spite of showing just first_Tab_ViewController), so when several tabs have ViewController with WebView their respective webViewDidFinishLoad are called and if I have copied pasted:

NSLog(@"size width %0.0f height %0.0f", fitingSize.width, fittingSize.height); 

in several, I get several output in console that appears to be a double calling when they really are single calling in two different UIWebViews.

Justin Boo
  • 10,132
  • 8
  • 50
  • 71
Luis
  • 21
  • 1
1

You could check the loading and request properties in the webViewDidFinishLoad method

nico
  • 1,778
  • 1
  • 9
  • 8
  • I've checked the Content-Type HTTP header and the URL and they don't give me any useful information. What information are you suggesting to look at? – Ben Scheirman Dec 04 '09 at 04:13
-1

Possibly related to this issue is a property on UIWebView introduced in iOS6: suppressesIncrementalRendering.

Alfie Hanssen
  • 16,964
  • 12
  • 68
  • 74
  • Hi Joris, perhaps my contribution should have been a comment rather than an answer. Either way, I believe it does directly relate to the question asked. – Alfie Hanssen May 22 '13 at 21:05
  • This does not solve the issue. See the Apple docs: https://developer.apple.com/reference/uikit/uiwebview/1617984-suppressesincrementalrendering – Ross Gibson Apr 10 '17 at 13:29