I have basic web browser implemented using a UIWebView. I've noticed that for some pages, none of the UIWebViewDelegate methods are called.
An example page in which this happens is: http://www.youtube.com/user/google. Here are the steps to reproduce the issue (make sure you insert NSLog calls in your controller's UIWebViewDelegate methods):
Load the above youtube URL into the UIWebView
[notice that here, the UIWebViewDelegate methods do get called when the page loads]
Touch the "Uploads" category on the page
Touch any video in that category
[issue: notice that a new page is loaded, but none of the UIWebView delegates are called]
I know that this is not an issue of UIWebView's delegate not being set properly, since the delegate methods do get invoked when loading other links (e.g. if you try clicking on a link that takes you outside of youtube, you'll notice the delegate methods getting called).
My gut feeling initially was that it might be because the page is loaded using AJAX, which may not invoke the delegate method. But then when I checked the iPhone's Safari, it did not exhibit this problem, so it must be something on my side.
I've also noticed that Three20's TTWebController has the exact same issue as I'm having.
But the problem that arises from this issue is that without the delegate methods called, I'm unable to update the UI to enable/disable the back and forward browsing buttons when new requests are loaded.
And idea why this is happening or how can I work around it to update the UI when a new request is made?
This isn't an iOS bug - the page isn't actually reloading. The UIWebView delegates are triggered following new page requests, but that page doesn't do that.
Look very carefully at what happens in desktop Safari when you click the video link on that page as you describe. Make sure you pay attention to the address bar. The address will change, but critically the page will not reload.
This is all handled by JavaScript, not by reloading the page. Simply put, the page never reloads, so there's no reason for the UIWebView delegates to be called.
If you don't believe me, to conclusively prove this try repeating the steps you describe with JavaScript disabled. You'll notice the page behaves completely differently.
this is not good solution but im using NSTimer for updating status of buttons:
- (BOOL)webView:(UIWebView *)_webview shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (!_timer && [request.URL.absoluteString rangeOfString:#"youtube.com"].length != 0) {
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(checkNavigationStatus)
userInfo:nil
repeats:YES];
}
return YES;
}
//....
//....
//....
- (void)checkNavigationStatus
{
// Check if we can go forward or back
backButton.enabled = self.webView.canGoBack;
forwardButton.enabled = self.webView.canGoForward;
}
Looks like this got fixed in iOS 4.2. It works in iOS 4.2.
Related
I have a UIWebView in my view controller and have set its delegate to the view controller via code (i.e. not through IB). I have also setup the appropriate delegate methods: shouldStartLoadWithRequest, webViewDidStartLoad, webViewDidFinishLoad and didFailLoadWithError.
In my view controller's viewDidLoad method I load an appropriate URL with this code:
[self.webView loadRequest:reqURL];
95% of the time everything works great and the page loads in the UIWebView object and displays as expected. Occasionally, however, the page doesn't load.
After stepping through my code I realized that on the times that it doesn't work the shouldStartLoadWithRequest delegate method fires, but webViewDidStartLoad doesn't.
Does anyone have any idea what's going on here? I couldn't find anything on Stack Overflow that specifically addressed this unique issue I'm having and am slowly reaching my breaking point. Thanks in advance!
You should make sure that your shouldStartLoadWithRequest implementation returns YES for all conditions at which you need your webView to load.
I'm trying to add several UIWebViews (on different pages) in my iPhone app, I added the UI part of it, but I can't figure out how to program them to load separate pages. I watched a YouTube video on how to add a webview, and the method works for a single webview, but when I try to add another to the detail view delegate, it tells me that I'm re-defining a couple different items. Anyone know how to fix this?
All the UIWebView delegate methods pass you a reference to the UIWebView instance that the delegate call is relevant for.
So for example if you have two UIWebViews called webView and webView2 then you can do something like this:
- (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (webView == self.webView1) {
// Do something for webView1
}
else if (webView == self.webView2) {
// Do something for webView2
}
}
Same applies to all other delegate methods for UIWebView.
Yes, I know UIWebView has didFinishedLoad & didStartLoad delegate.
However, the didFinishedLoad does not mean the full completion. It may be called when one of the items that the UIWebView is finished loading. i.e., UIWebView may call this delegate several times while loading a single page.
So anyway can tell me how to check whether the UIWebView is fully loaded?
Thanks
J
I have poor experience with DOM but after some searching I found that the document.readyState is the great option.
From w3schools:
Definition and Usage
The readyState property returns the (loading) status of the current document.
This property returns one of four values:
uninitialized - Has not started loading yet
loading - Is loading
interactive - Has loaded enough and the user can interact with it
complete - Fully loaded
So I'm using this to know when UIWebView has loaded the document:
- (void)readyState:(NSString *)str
{ NSLog(#"str:%#",str);
if ([str isEqualToString:#"complete"]||[str isEqualToString:#"interactive"]) {
NSLog(#"IT HAS BEEN DONE");
[pageLoadingActivityIndicator stopAnimating];
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//other code...
[self readyState:[browserWebView stringByEvaluatingJavaScriptFromString:#"document.readyState"]];
}
http://www.codingventures.com/2008/12/using-uiwebview-to-render-svg-files/
on Javascript communicating back with Objective-C code
Maybe use document location hash.
And add in the webview html body:
<body onload="document.location.hash='myapp:myobject:myfunction';">
I know its a little bit hacky but works. And it can be used in ajax based contents, because its up to you when you want to call your ready method. Or it can be used as a complete communication scheme.
What you can do is, display a network indicator on the view which is visible in the status bar. When your page is being loaded the indicator will continue rotating until and unless the page is completly loaded. When the page completely loads, the indicator will stop rotating and will be invisible.
Just have a look at this example : Network indicator
I have an application that uses UIWebViews in several view controllers. The UIWebViews are used to render locally generated html, no slow network access required.
To save memory I only load these on demand as prompted by the viewcontroller viewWillAppear callback. (And unload offscreen instances in response to didReceiveMemoryWarning messages.)
The problem is that the user gets to see the html being rendered, sometimes accompanied by flashes of styling and other assorted unpleasant artifacts. I would much rather the rendering be done offscreen, and reveal the fully rendered view when its ready.
It would be very tidy to be able to have the viewWillAppear not return until the UIWebView is fully rendered. But how?
I tell the UIWebView what to render by sending it a loadHTMLString:baseURL: message. This is asynchronous, and some time (soon) later the webview's delegate gets called back webViewDidFinishLoad.
I experimented with running a runloop inside viewWillAppear, running either the NSDefaultRunLoopMode or UITrackingRunLoopMode. This works in the simulator (it complains to the log
[CATransaction synchronize] called within transaction
but does work) but on a device it deadlocks, with webViewDidFinishLoad never being called.
(Also, it seems like the UIWebView loading property doesn't work. At least, after I call loadHTMLString:baseURL: and before getting the callback it's not true.)
Lots of solutions here I think. A quick one is to load your UIWebView with it's hidden property set to YES. Then set your UIViewController as the UIWebViews delegate and implement:
- (void)webViewDidFinishLoad:(UIWebView *)webView
where you set the property back to NO.
A thing to note is that webViewDidFinishLoad will fire more than once if you have framed/embedded content. So you have to keep track of this. Shouldn't really be a problem if you are loading local content.
I like monowerker's solution best, but another solution would be to hold onto the already-rendered UIWebView all the time (in some more permanent object than the view controller). I'd only do that if the look of monowerker's solution is too disruptive.
Is it possible to start an event when an UIWebView (Iphone) has finished loading the URL.
How can I find out, the current URL of the UIWebView?
Yes, that's possible. Use the UIWebViewDelegate protocol and implement the following method in your delegate:
- (void)webViewDidFinishLoad:(UIWebView *)webView
If you want the URL, you can get the last request using the property request:
webView.request.URL
None of the found solutions worked for me.
Then I found this example which at least works much better than any other solution I found on Google/StackOverflow.
uiwebview-load-completion-tracker
Very simple method:
Step 1: Set delegate UIWebViewDelegate in header file.
Step 2: Add following webViewDidFinishLoad method to get current URL of webview
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(#"Current URL = %#",webView.request.URL);
//-- Add further custom actions if needed
}
Pascal's answer for the "getting the URL" part is fine.
However!
From UIWebViewDelegate's documentation, from Apple:
"webViewDidFinishLoad: Sent after a web view finishes loading a frame."
Frame != Page.
webViewDidFinishLoad is called when the page is "done loading". It can also be called many times before then. Page loads from Amazon.com can generate a dozen calls to webViewDidFinishLoad.
If you control the page source, then you can make a load test for it, and it will work, for that case. If you only care about getting called "after the page is done loading", then webViewDidFinishLoad is adequate.
For arbitrary pages, with arbitrary JavaScript, loading ad banners in perpetuity, or autoscrolling banners, or implementing a video game, the very idea of a page being "done loading" is wrongheaded.