webViewDidFinishLoad not working? - ios

In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}

Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}

At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)

I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad

Related

UIWebView content sometimes not rendered

Briefly, my problem is: some PDFs loaded into some web views don't show up, while others do, as shown in the following pictures (please note that I edited the images placing fake pdf content, the original cannot be published).
Three PDFs loaded correctly into three UIWebViews
Second UIWebView height is right and comes from the PDF height, but its content is not rendered
My app has to show very short PDFs into a series of web views (assume that this is a mandatory requirement and that I cannot switch to anything else. The PDFs are LaTex generated and contain text which cannot be displayed into UILabel and so on. Also, they are about 4-5 rows length).
The layout is as following: there is a vertical scroll view which has a view (content view) as direct child. A single UIWebView (yellow background) is embedded inside a custom class that inherits from UIView (PDFView, blue background), so I can assign that class as the webview's delegate and perform the webview initialization+constraints settings in just one place. The webview userInteraction is disabled. Into the content view, I dynamically add one PDFView per PDF, setting up the constraints and calling loadData:MIMEType:textEncodingName:baseURL: to load the PDF (which comes as NSData from a file on disk, the white frame). Since PDFView is the delegate of its UIWebView, when it finishes to load its content (the pdf), I change the webview height constraint to match the webview.scrollview.contentsize.height. This allows me to resize the webview so that it displays the entire PDF. Autolayout takes care of setting the scrollview contentsize.height so the user can scroll the entire list of pdf.
Everything seems to work fine but sometimes, on some webviews, the content is not rendered. When the call for webViewDidFinishLoad: triggers, the webView.scrollView.contentSize.height value is correct (in fact, the webview has the right height) and after autolayout finishes his work, the frame of the webview whose content has not been rendered has the correct dimensions. This leads me to think that it must be some sort of rendering problem, since it seems to happen randomly on different webviews and on different pdfs. Sometimes I have to manually push and pop the controller like 10 times before the problem arises, sometimes it appears just on the first push. Memory consumption seems to be fine and there aren't any memory warnings.
I checked every data and view: no one is nil. Also the delegate webView:didFailLoadWithError: is never invoked so the webview always succeeds in loading its content (webViewDidFinishLoad: is called every time, even for the webviews that don't display any content). The PDF data is fine since I loaded it into memory, saved again in another pdf file and opened with a PDF viewer. I'm not using storyboards but xibs. I'm targeting iOS 7+.
Code is as following:
PDFView.h
#interface PDFView : UIView
-(instancetype) initWithData:(NSData *)pdfData at:(NSInteger)position;
#end
PDFView.m
#import "PDFView.h"
#interface PDFView() <UIWebViewDelegate>
#property (strong, nonatomic) UIWebView * contentUIWV;
#property (strong, nonatomic) NSLayoutConstraint * contentHeightNSLC;
#end
#implementation PDFView
-(instancetype) initWithData:(NSData *)pdfData at:(NSInteger)position
{
self = [super init];
if (self)
{
[self initViews];
[self setConstraints];
[self fillViews:pdfData];
self.tag = position;
}
return self;
}
-(void) initViews
{
_contentUIWV = [[UIWebView alloc] init];
[_contentUIWV setContentHuggingPriority:250 forAxis:UILayoutConstraintAxisVertical];
_contentUIWV.userInteractionEnabled = NO;
_contentUIWV.delegate = self;
_contentUIWV.scalesPageToFit = YES;
_contentUIWV.backgroundColor = [UIColor yellowColor];
[self addSubview:_contentUIWV];
}
-(void) setConstraints
{
_contentUIWV.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary * viewsNSD = #{ #"contentUIWV":_contentUIWV };
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-8-[contentUIWV]-8-|" options:0 metrics:nil views:viewsNSD]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-8-[contentUIWV]-8-|" options:0 metrics:nil views:viewsNSD]];
NSArray * constraintsNSA = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[contentUIWV(==1)]" options:0 metrics:nil views:viewsNSD];
self.contentHeightNSLC = constraintsNSA.firstObject;
[self.contentUIWV addConstraints:constraintsNSA];
}
-(void) fillViews:(NSData *)pdfData
{
#ifdef DEBUG
NSLog(#"PDFView::fillViews: %ld %#", (long)self.tag, pdfData?#"NOT null":#"NULL");
#endif
[_contentUIWV loadData:pdfData MIMEType:#"application/pdf" textEncodingName:#"utf-8" baseURL:nil];
}
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
return YES;
}
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(#"PDFView::webView:didFailLoadWithError: %#", error.localizedDescription);
#endif
}
-(void) webViewDidFinishLoad:(UIWebView *)webView
{
#ifdef DEBUG
NSLog(#"PDFView::webViewDidFinishLoad: %ld new height (%ld)", (long)self.tag, (long)webView.scrollView.contentSize.height);
#endif
self.contentHeightNSLC.constant = webView.scrollView.contentSize.height;
}
#end
Controller.m
#import "Controller.h"
#import "PDFView.h"
#interface Controller ()
// This Mutable Array stores the NSData of each PDF (PDF are loaded form disk and stored as NSData)
#property (strong, nonatomic) NSMutableArray * exerciseDataNSMA;
#property (weak, nonatomic) IBOutlet UIView *mainUIV;
#property (weak, nonatomic) IBOutlet UIScrollView *scrollUISV;
#property (weak, nonatomic) IBOutlet UIView *contentUIV;
#end
#implementation Controller
- (void)viewDidLoad
{
[super viewDidLoad];
// ... omitted code inits self.exerciseDataNSMA and stores pdf content in it
[self loadContentView];
}
#pragma mark - Private Methods
-(void) loadContentView
{
// Subviews by name (used for constraint bindings)
NSMutableDictionary * subviews = [[NSMutableDictionary alloc] init];
// String in Visual Language Format to create vertical scrollview's contentview constraint
NSMutableString *strVerticalConstraint = [[NSMutableString alloc] initWithString:#"V:|"];
for (NSInteger i=0; i<self.exerciseDataNSMA.count; i++)
{
PDFView * pdfView = [[PDFView alloc] initWithData:self.exerciseDataNSMA[i] at:i];
pdfView.delegate = self;
pdfView.backgroundColor = [UIColor blueColor];
pdfView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentUIV addSubview:pdfView];
[self.contentUIV addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[pdfView]|" options:0 metrics:nil views:#{#"pdfView":pdfView}]];
NSString * exerciseUIVName = [NSString stringWithFormat:#"pdfView%ld", (long)i];
[strVerticalConstraint appendString:[NSString stringWithFormat:#"[%#]-8-", exerciseUIVName]];
subviews[exerciseUIVName] = pdfView;
}
[strVerticalConstraint appendString:#"|"];
// Add content view constraints
[self.contentUIV addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:strVerticalConstraint options:0 metrics:nil views:subviews]];
}
#end
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Without the cache disabled, the webviews do not resize their height
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"WebKitDiskImageCacheEnabled"];
[[NSUserDefaults standardUserDefaults] synchronize];
// ... unrelated stuff
return YES;
}
Constraints on Controller are as following:
Note that the height constraint is removed at runtime.
Any idea why this happens? Any idea on how to solve it?
Here what I already tried:
Reproduced the problem on both iOS 7 and iOS8, on iPhone and on iPad. The number of webviews whose content is not rendered is greater on low specs devices (like iPhone 4s) than those with better resources (as the iPad 4);
Executing loadContentView without loading pdfData into the webview. After the controller has appeared, load the pdfData into the webviews;
Load the pdfdata sequentially, meaning that i do the same as B) and then I call loadData:MIMEType:textEncodingName:baseURL: for the first webview, wait for webViewDidFinishLoad: and then repeat this sequence for the second webview and so on;
Force every webview to reload its content (since I don't have a way to detect a webview that doesn't render its content. This results in some webviews rendering their content again, some others not);
Use javascript to resize the webview (it didn't work at all);

UIWebView still active after pressing back button

I'm using a Navigationcontroller which pushes a new Viewcontroller containing a Webview every time a link is clicked. One of the URLs has a javascript function, which calls another function every 60 seconds. It works fine, but when I hit the back button once after being on that web view (containing the javascript), the webview stays active and keeps doing the javascript calls, while going back to the previous webview flawlessly. I can see it doing things through NSLog. Why is that and how can I avoid this?
EDIT:
I have declared my subclass of UIWebView in my Viewcontroller like this:
#property (strong, nonatomic) AFXTWebView *wv;
and this is how I instantiate it (in ViewDidLoad):
[self setWv:[[AFXTWebView alloc] initWithVC:self andURLString:[self.pageConfig objectForKey:xAFXTPageLink]]];
[self.view addSubview:self.wv];
[self.wv loadWebView];
I have also tried setting the webView to nil in viewWillDisappear:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.wv = nil;
}
You have to use the viewWillAppear & viewWillDisappear methods
-(void)viewWillAppear:(BOOL)animated
{
_webView=[[UIWebView alloc]init];
......//do your stuff
}
-(void)viewWillDisappear:(BOOL)animated
{
[_webView removeFromSuperView];
_webView=nil;
}
In your viewWillDisappear load a blank page into the web view.
This sounds like your web view and view controller are not being released, i.e. you have a leak. You should check your code for any unreleased retained/strong references to your view controller or web view.
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
}

Common webViewDidStartLoad by category

I think implement completely same method to every viewController with UIWebView (and delegate=self)is not smart. So tried to setup common loading method with all UIWebView. But it did not work.
Is it wrong to achieve with category?
UIWebView+Loading.m
-(void)webViewDidStartLoad:(UIWebView*)webView
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
ViewController.h
#interface ViewController : UIViewController <UIWebViewDelegate>
ViewController.m
#import "UIWebView+Loading.h"
//abbr...
-(void)viewWillAppear:(BOOL)animated
{
UIWebView *someWebView = [[UIWebView alloc] init];
someWebView.delegate = self;
//and HTTP request
NSURLRequest *req = (abbr);
[someWebView loadRequest:req];
}
The method webViewDidStartLoad: is not being called because it is a part of UIWebViewDelegate protocol, not a method of UIWebView class itself. You have to implement it in your ViewController.m file.
Also, you may want to read about delegation pattern in iOS.
Dont need a category like UIWebView+Loading.m for this purpose.
The webview delegate methods will get executed when the loading stats and the delegate method
- (void)webViewDidStartLoad:(UIWebView *)webView {
NSLog(#"Started loading");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"Finshed loading");
}
will get executed.The code you written is enough.Since the delegate is set to self .In the viewcontroller define these methods and thats it .You will have it working

Another (!) Activity Indicator Issue

Well, after reading a bunch of SO posts on this issue, I still can't fix problems with my activity indicator. This indicator is in a view in a tab under the control of its own view controller. It has a view with a UIWebView which loads a local html page just fine. The view is loaded with initWithNibName and then awakeFromNib. Here's the part I think is relevant:
#implementation HelpViewController
#synthesize webView = _webView;
#synthesize back = _back;
#synthesize forward = _forward;
#synthesize aI = _aI;
- (void) viewDidLoad {
_aI = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_aI stopAnimating];
// Now add the web view
NSString *filePath = [[NSBundle mainBundle]
pathForResource:#"Help"
ofType:#"html"];
[self.view addSubview:_webView];
_webView.delegate = self;
NSURL* fileURL = [NSURL fileURLWithPath:filePath];
NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
[_webView loadRequest:request];
[super viewDidLoad];
}
-(void) webViewDidStartLoad:(UIWebView *)webView {
[_aI startAnimating];
_back.enabled = NO;
_forward.enabled = NO;
}
-(void) webViewDidFinishLoad:(UIWebView *)webView {
[_aI stopAnimating];
if (webView.canGoBack) {
_back.enabled = YES;
_back.highlighted = YES;
}
if (webView.canGoForward) {
_forward.enabled = YES;
_forward.highlighted = YES;
}
}
The navigation buttons work fine. The activity indicator was placed in the nib, but in the main view, not on/over/under the webView. In the attributes, I have Hides When Stopped checked. If I check Animating the indicator is always visible and animated, no matter how I navigate through the UIWebView.. If I uncheck Animating, Hidden is automatically checked for me. In this case, the indicator never shows up. So it's either always on or always off.
I've read quite a bit about cases where you need to have the indicator on a different thread. I'm not sure, but I don't think that applies here (I load a local html page but allow users to navigate off and then back to the local page). But, I seem to have some disconnect; perhaps it is the fact that the indicator is in the main view but the pages are in the webView? Or I'm not calling things in the right methods. Or who knows... Thanks for any help!
You overwrite _aI in your viewDidLoad and never place it in your view hierarchy, so the object you are sending the messages to is never visible and the activity indicator placed in interface build will never change its state, so thats why its either always animating or hidden.

UIWebView. Badaccess when reloading in viewDidDisappear

I am trying to reload content from local file of UIWebView in viewDidDisappear of UIViewController. Badaccess is caught. But if I write the same code in viewWillDisappear, it works.
What might be the reason?
Once I've heard that UIWebView can't reload its content when it is not visible (not sure about it).
My code (hope it'll be enough):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
ExtendedWebView * webView;
}
#property (nonatomic, retain) ExtendedWebView * webView;
#end
//WebViewController implementation
- (void)loadView
{
[super loadView];
WebViewCachingSingleton * webViewSingleton = [WebViewCachingSingleton sharedService];
ExtendedWebView * newWebView = [webViewSingleton getAvailableWebViewResource];//here I get ExtendedWebView. it works =)
newWebView.frame = CGRectMake(0, 0, 320, 400);
newWebView.delegate = self;
[self.view addSubview:newWebView];
self.webView = newWebView;
}
- (void)viewDidDisappear:(BOOL)animated
{
[[WebViewCachingSingleton sharedService] makeWebViewUnused:self.webView];
}
//WebViewCachingSingleton:
- (void) makeWebViewUnused : (ExtendedWebView *) aWebView
{
aWebView.isFree = YES;
[aWebView reload];
}
It will not work because viewDidDisappear is called when the view is disappeared, so all the subviews are released. The viewWillDisappear is called just before releasing all the objects associated with that view.
So you are trying to call the reload method of a UIWebView that has been already released. That is basically the reason why it crashes.
Hope it helps
The reload can be performed even if not displayed.
I tried to make an example of code to put in your state and have not had any problems.
Try to debug and enable NSZombieEnabled to see what actually happens to your application.
Try to post on any piece of code that might help us give you more details.

Resources