I have been using webView delegate successfully from long time. But recently I faced strange issue with this delegate. In my current project I am trying to access my router from webview. I am passing username and password inside URL only. Below is load request code.
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://uname:password#192.168.1.1"]]];
This calls webView delegate method (webViewDidFinishLoad and webViewDidStartLoad) 5 times. Is it expected? When I pass simple URL like google.com it works as expected. But with username and password why these delegate methods are called 5 times?
If this behaviour is correct then I need to know why it calls 5 times only. The reason is, in my program - I am calling performSegueWithIdentifier in webViewDidFinishLoad method and in present form it calls segue 5 times. For workaround I can maintain count and will call performSegueWithIdentifier on 5th count only.
Thanks
webViewDidStartLoad/webViewDidFinishLoad are called once per HTML frame. Your content likely has multiple frames in it.
See UIWebViewDelegate docs.
webViewDidStartLoad:
Sent after a web view starts loading a frame.
This Methods works for me... :)
#pragma mark UI Web View Delegate
NSInteger webViewLoads;
//a web view starts loading
- (void)webViewDidStartLoad:(UIWebView *)webView{
webViewLoads++;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeBlack];
}
//web view finishes loading
- (void)webViewDidFinishLoad:(UIWebView *)webView{
webViewLoads--;
[self performSelector:#selector(webViewFinishLoadWithCondition) withObject:nil afterDelay:0.5];
}
//web view handling error
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
webViewLoads--;
NSLog(#"Web View Did Fail Load With Error : %#",error);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[SVProgressHUD dismiss];
}
-(void)webViewFinishLoadWithCondition{
if(webViewLoads==0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[SVProgressHUD dismiss];
}
}
As webViewDidStartLoad/webViewDidFinishLoad are called once per HTML frame, Use an integer to find when the last frame load is finished,
In detail :
Increment the integer in webViewDidStartLoad
Decrement the integer in webViewDidFinishLoad
In webViewDidFinishLoad check when integer is zero, Which means all the frames of web page are loaded, Now call the selector
This is an explanation of #marvin's answer
Best approach:
Check for isLoading in webViewDidFinishLoad and isLoading is false, do what ever u want
-(void)webViewDidFinishLoad:(UIWebView *)webview
{
if (!webview.isLoading)
{
// webview is done loading content.
// `performSegueWithIdentifier` / Your code Here
}
}
Related
Before I present my issue, I want to mention that I tried looking for solutions here and here.
I am creating a hybrid application which uses native UIWebView for rendering the responsive designed web application. Following is the issue description :
1. I have a UITabBarController.
2. Each of the tab has a UIWebView.
3. I have to preload all tabs.
4. I am showing a UIActivityIndicator till the content loads on the first tab.
5. White screen appears for about 8-10 seconds and then the content starts to appear.
I will be happy to see this time become 2-4 seconds.
Following is my implementation :
[self performSelectorOnMainThread:#selector(loadAllTabs) withObject:nil waitUntilDone:YES];
-(void) loadAllTabs
{
for(UIViewController * viewController in self.viewControllers){
if(![[NSUserDefaults standardUserDefaults]boolForKey:#"isSessionExpired"])
{
if((int)[self.viewControllers indexOfObject:viewController] != 4)
{
viewController.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:viewController];
[viewController view];
}
}
}
}
In WebView controller's viewDidLoad I have :
[_tgWebView loadRequest:[NSURLRequest requestWithURL:homeURL]];
I was looking forward to suppressesIncrementalRendering, but since I am preloading all tabs, this does not work.
Since my app supports iOS 7+, thus WKWebView can't be applied here.
I also thought of increasing the launch image duration but learned that is won't be a good practice.
Can this be implemented using GCD?
Please bring out the pitfalls in my implementations so that my application makes better performance.
First, have UIWebView on each tab hidden until it has finished loading, then show it. Underneath the UIWebView you can have some placeholder image to describe it loading. Then using the webViewDidFinishLoad delegate method show the web view when it has finished loading. This approach will be non blocking.
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
if (webview.isLoading)
return;
else
webView.hidden = false;
}
Second, preload the first tab then load the subsequent tabs while displaying the first. You can do that by placing the following code in the first tabs viewDidLoad method:
// Preload the subsquent tabs
for (UIViewController *aVC in self.tabBarController.viewControllers)
if ([aVC respondsToSelector:#selector(view)] && aVC != self)
aVC.view;
This way, the additional tabs web views are loaded in the background in a non blocking manner. You could combine it with hiding the web views while loading in case the user navigates to the additional tabs before their pages load.
I tested this with three tabs and it worked nicely.
So the first view controller could look something like this:
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Load the website
[self loadWebView];
// Preload the subsquent tabs
for (UIViewController *aVC in self.tabBarController.viewControllers)
if ([aVC respondsToSelector:#selector(view)] && aVC != self)
aVC.view;
}
-(void)loadWebView
{
// Create Request
NSURL *url = [NSURL URLWithString:#"http://anandTech.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Load the page
[webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
if (webview.isLoading)
return;
else
webView.hidden = false;
}
EDIT:
Removed GDC as it was crashing when the views had WebViews
This post made my day. I am now using NSNotificationCenter for this.
It's hard to identify the where the problem is directly.
First, I suggest that you check your network connection by loading the page on a desktop machine on the same Wifi to see if it is your server that is too slow.
Also you can load your pages one by one instead of load all pages concurrently, since multiple HTTP request are not processed in a FIFO sequence, they may be processed out of order, and your page may need all resource to be loaded before displaying.
Do you control the web page your self? You can use safari inspector to inspect your web page to see where is the time spent, is it resource loading, javascript processing or rendering.
I'm facing a problem trying to take screenshots form UIWebViews. I need to take some screenshots of my UIWebView and it works but the screenshot is not correct because they are taken in the event webViewDidFinishLoad, but it calls webViewDidFinishLoad when the UIWebView is not loaded fully, I mean, I take the screenshot in the event webViewDidFinishLoad but the UIWebView is not correctly and fully loaded so it takes a screenshot and it makes right but the screenshot is not totally correct because its the event is triggered (webViewDidFinishLoad) but the UIWebView is not totally loaded. Any ideas?
Thank you very much
A user was having a issue with their use of a activity indicator that apears until a page has loaded but theirs would come back after the main content had loaded the question is here UIWebView not finishing loading? and the answer code is used below. After the code I will go into detail about how this could be used for your situation.
(In the code below the web page truly starts loading on //show UIActivityIndicator and truly finishes loading the main content (not the extra content you are struggling with) on //hide UIActivityIndicator)
//Define the NSStrings "lastURL" & "currentURL" in the .h file.
//Define the int "falsepositive" in the .h file. (You could use booleans if you want)
//Define your UIWebView's delegate (either in the xib file or in your code)
- (void)webViewDidFinishLoad:(UIWebView *)webView {
lastURL = [[NSString alloc] initWithFormat:#"%#", webView.request.mainDocumentURL];
if (falsepositive != 1) {
NSLog(#"Loaded");
//hide UIActivityIndicator
} else {
NSLog(#"Extra content junk (i.e. advertisements) that the page loaded with javascript has finished loading");
//This method may be a good way to prevent ads from loading hehe, but we won't do that
}
}
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; {
NSURL *requestURL =[request mainDocumentURL];
currentURL = [[NSString alloc] initWithFormat:#"%#", requestURL]; //not sure if "%#" should be used for an NSURL but it worked...
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
if ([currentURL isEqualToString:lastURL]) {
falsepositive = 1;
NSLog(#"The page is loading extra content with javascript or something, ignore this");
} else {
falsepositive = 0;
NSLog(#"Loading");
//show UIActiviyIndicator
}
}
When a page loads in iOS, webviewdidstart and webviewdidfinish are called but not knowing the difference in your main page/html content objective-c calls this again for extra content such as ads or frames. Using this if I were in your situation I would create a BOOL such as pageIsLoading and set it to true in webviewdidstart and then set it to false in webviewdidfinish. After the BOOL is turned off in webviewdidfinish I would end webviewdidfinish by calling a method that will check after a short delay if the BOOL pageIsLoading == YES and if it is, do nothing because more content is loading. If pageIsloading == no then all content must be loaded and now would be a good time to take your snapshot.
Rather than taking screenshot in the event webViewDidFinishLoad you can try to take the screenshot explicitly using UIButton. Let this button be disabled and you can just set this button's enabled property to YES after web view fully loads its content.
On button click to can implement your code to take screenshot.
Edited : Webview's webViewDidFinishLoad method is called when it finishes loading its content. Might be its possible that it may be taking some time to render some images/content on its view.
If possible you can use NSTimer in your webViewDidFinishLoad method to wait for sufficient time(1 min) so that mean while webview can load its content fully.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target: self
selector: #selector(MethodName:)
userInfo: nil
repeats: NO];
In Selector method you can implement your code of taking screenshot.
Hope this will help you...
I have a UIWebView and want to display an activity indicator while it is loading. And then hide it when the webViewDidFinishLoading. The only problem is that I am getting this NSURLErrorDomain error -999 thing going on. After searching around I found this fix which works to not display any error message but my webViewDidFinishLoading doesn't ever get called to get rid of my activity indicator and other stuff that I have going on. I guess I could just make a call to the didFinishLoading method in my webViewDidFailWithError method if it fails with -999 but that seems super hacky and wrong. Any ideas on how to fix this?
edit*
I have figured out where the webview was being asked to load twice so I was able to get rid of the error -999. However, it seems neither of the delegate methods are being called unless I try to load the webview twice (in which case the webViewDidFinishLoading method is only called once).
- (void)viewDidLoad
{
self.webView.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refresh)
name:#"DidBecomeActive"
object:nil];
[super viewDidLoad];
}
-(void)refresh{
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myapp.com/app/"]]];
self.webView.scrollView.bounces = NO;
}
- (void)webViewDidFinishLoading:(UIWebView *)wv
{
NSLog(#"finished loading");
[self.activityInd stopAnimating];
[self.view bringSubviewToFront:wv];
}
- (void)webView:(UIWebView *)wv didFailLoadWithError:(NSError *)error
{
NSLog(#"Failed: %#", error);
if([error code] == NSURLErrorCancelled){
return;
}
[self.activityInd stopAnimating];
[[[UIAlertView alloc] initWithTitle:#"Can't find server" message:#"Check your internet connection and try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil]show];
}
Did u assigned the UIWebView delegates to its object like the following
webViewObject.delegate = self
Copy/Paste error.
- (void)webViewDidFinishLoading:(UIWebView *)wv
Should be changed to:
- (void)webViewDidFinishLoad:(UIWebView *)wv
implemente the protocol "UIWebViewDelegate" in .h file
In case you ended up here because you're wondering why your delegate methods aren't being called when the bug wasn't due to an incorrectly written delegate method:
Say your webview is normally instantiated via Interface Builder, but you forget to hook it up correctly (meaning it isn't instantiated). You can still set its delegate (e.g., self.webview.delegate = self;) without crashing or exceptions (provided self conforms to the UIWebViewDelegate protocol).
Similarly, you can load HTML into it (e.g., [self.webview loadHTMLString:someHTMLString baseURL:nil];) and the app will continue along happily without complaints. This would take you to an awkward situation where you've set the delegate, written the delegate methods correctly, loaded something into the webview, but your delegate methods will never fire.
So the advice for that situation: make sure you double check that your webview is connected properly (to the correct instance property/variable) in Interface Builder.
I am reloading my UIWebView and for some reason the code in stringByEvaluatingJavaScriptFromString does not seem to have an effect. It works fine on initial view load but doesn't seem to work on reload. Stepping through the debugger I see the webViewDidStartLoad getting called after the reload, so there must be another issue.
- (void)webViewDidStartLoad:(UIWebView *)webView
{
//pass in our default variables into the web view here
[mainWebView stringByEvaluatingJavaScriptFromString:#"var model = {taskId:'xyz', foo:'bar'};"];
}
and my reload method
- (void)refreshView{
[mainWebView reload];
}
I am very new to the whole programming business, and was wondering if there is any way to clear the contents of a UIWebView in iphone programming, so that the loading symbol for the next view is not showing up in front of the last view.
Many Thanks,
Thomas
Try setting the URL to about:blank and reload the page.
Just load an empty html string into it
[self.webView loadHTMLString:#"" baseURL:nil];
Answer extension for documentation purposes to maybe help someone else:
I had the same desire (clear content before loading next url) but had a UIWebView delegate set to receive webviewDidFinishLoad:(UIWebView)webview message and update another part of UI in response.
Problem: the call to clear the content to also called delegate method, so getting false-hits (that is, getting call when clear is done, too, but delegate is coded to expect call only when real content is loaded).
Solution: use a known URL for clear, and have webviewDidFinishLoad: ignore calls made when that URL is finished:
- (void) startLoadOfNextURL:(NSURL*)url
{
// clear:
[self.webView loadHTMLString:#"" baseURL:nil];
// Load real next URL
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(#"WebView finished loading: %#", webView);
if ([self.webView.request.URL.absoluteString isEqualToString:#"about:blank"]) {
NSLog(#" This is Blank. Ignoring as false event.");
}
else {
NSLog(#" This is a real url");
[self updateUIInSomeWay];
}
}
Note: using this:
[self.webView loadHTMLString:#"about:blank" baseURL:nil];
actually causes the words "about:blank" to appear as text in the webview's content pane!
Final complication: In practice, with my two [webview load...] calls so close together, I was finding that instead of a "loaded" event for the clear, the webview was actually canceling it in favor of the second request and calling webView: didFailLoadWithError: for the first load request. Thus, I had to put similar code in that event:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"WebView error on: %#", webView);
NSLog(#"Error is: %#", error);
NSURL* failingURL = [error.userInfo objectForKey:#"NSErrorFailingURLKey"];
if ([failingURL.absoluteString isEqualToString:#"about:blank"]) {
NSLog(#" This is Blank. Ignoring.");
}
else {
NSLog(#" This is a real URL.");
[self doSomethingAboutError];
}
}
Swift, Xcode 7 beta 5
webView.loadRequest(NSURLRequest(URL: NSURL(string: "about:blank")!))
Swift 4.0 , XCODE 9
webView.loadRequest(URLRequest.init(url: URL.init(string: "about:blank")!))
Same answer in Swift 4.2, xCode 10
if let clearURL = URL(string: "about:blank") {
myWebView.loadRequest(URLRequest(url: clearURL))
}