UIWebView going blank when navigating back to the view - iphone

I have an app that loads a UIWebView. And that works well. It is possible for the user to click a link in the web view which then creates a new view controller/web view to load the link, which also works well.
What isn't working so well, is that sometimes (1/4 maybe) when I come back to the initial web view, the view is blank white.
The controller is not being dealloced, and is not receiving memory warning in between. And I'm totally puzzled.
Anyone know what's going on, and more especially, how to fix it?
Here are any methods that have anything to do with the controllers:
Initial Controller:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked)
{
WebView * wv = [[WebView alloc] initWithNibName:#"WebView" bundle:nil];
UIBarButtonItem *backBar = [[UIBarButtonItem alloc] initWithTitle:#"Back to the Article" style:UIBarButtonItemStyleDone target:nil action:nil];
self.navigationItem.backBarButtonItem = backBar;
[backBar release];
[[UIAppDelegate navigationController] pushViewController:wv animated:YES];
[[wv webView] loadRequest:request];
[wv release];
}
return NO;
}
else
return YES;
}
Other controller (WebView):
- (void)viewDidLoad {
[super viewDidLoad];
[webView setDelegate:self];
}

Are you sure it's not receiving a memory warning?
It might not be obvious when this occurs.

I had a similar problem that I resolved by using:
[self.webView loadHTMLString:#"HTML GOES HERE" baseURL:[NSURL URLWithString:#""]];
instead of:
[self.webView loadHTMLString:#"HTML GOES HERE" baseURL:nil];
Apparently the UIWebView was clearing as soon as
[[self navigationController] presentModalViewController:myModalViewController animated:YES];
was called when passing nil for the baseURL: argument of UIWebView's -loadHTMLString:baseURL: method. This bug appeared after upgrading to the iOS 4.3 SDK.

Related

How to return from webview to rootview in iOS?

I am new to iOS and Objective C programming world. I am trying to create a App which will scan the barcode then use that data to go to a website.
Now, the only issue is when I load the website using a webview, I don't know how to return to mainview/rootview. As, I have made this a view based App, I don't know how to implement navigation controller or simply a back button would do.
I have used this SDK: http://zbar.sourceforge.net/iphone/sdkdoc/
The code:
- (void) imagePickerController: (UIImagePickerController*) reader
didFinishPickingMediaWithInfo: (NSDictionary*) info
{
// ADD: get the decode results
id<NSFastEnumeration> results =
[info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results)
// EXAMPLE: just grab the first barcode
break;
// EXAMPLE: do something useful with the barcode data
resultText.text = symbol.data;
CGRect webFrame = CGRectMake(0.0, 0.0, 320.0, 460.0);
UIWebView *webView= [[UIWebView alloc] initWithFrame:webFrame];
[webView setBackgroundColor:[UIColor whiteColor]];
NSString *urlAddress= #"http://www.flipkart.com/search/a/books?query=";
urlAddress =[urlAddress stringByAppendingString:resultText.text];
NSURL *url= [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj= [NSURLRequest requestWithURL:url];
[webView loadRequest:requestObj];
[self.view addSubview:webView];
// [webView release];
/*
// ADD: dismiss the controller (NB dismiss from the *reader*!)
[reader dismissModalViewControllerAnimated: YES];
}
How can I add a back button in the webview so that I can return to main view?
Here you have tutorial how to implement toolbar with UIWebView:
http://iosdeveloperzone.com/2011/05/21/tutorial-building-a-web-browser-with-uiwebview-part-1/
You don't have to implement the navigationBar.
You have create a UIViewController that contains your UIWebView, then you have to put this controller into your current UINavigationCotroller's stack by calling [self.navigationController pushViewController:yourVC animated:YES];, then as #mientus said, call [self.navigationController popToRootViewControllerAnimated:YES]; to back to the root view controller.
So in this case i suggest you to start a new Master-Detail Application project.
If you don't want to do this, in your case just set a tag to your webView and add a UIButton with an action like this:
-(IBAction)removeWebView:(id)sender{
[[self.view viewWithTag:yourWebViewTAG] removeFromSuperView];
}

App crashes at [UIWebView webView:didReceiveTitle:forFrame:]

I am implementing a simple in-app browser. In my home view (UITableViewController), I have something like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
WebViewController *webViewController = [[WebViewController alloc] init];
switch (indexPath.row) {
case 0:
webViewController.stringURL = #"http://www.google.com";
break;
case 1:
webViewController.stringURL = #"http://www.bing.com";
break;
default:
webViewController.stringURL = #"http://stackoverflow.com";
break;
}
[self.navigationController pushViewController:webViewController animated:YES];
[webViewController release];
}
The app crashed after I repetitively navigated back and forth between my home view and webViewControllera few times.
Inside WebViewController class, I have nothing but a [UIWebView *webView] and a [UIActivityIndicatorView *activityIndicator]. Both are with attributes nonatomic, retain. Here is the implementation.
#import "WebViewController.h"
#implementation WebViewController
#synthesize webView, activityIndicator, stringURL;
- (void)dealloc
{
[self.webView release];
self.webView.delegate = nil;
[self.activityIndicator release];
[super dealloc];
}
-(void)loadView {
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
self.view = contentView;
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
webFrame.origin.y = 0.0f;
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.backgroundColor = [UIColor blueColor];
self.webView.scalesPageToFit = YES;
self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.webView.delegate = self;
[self.view addSubview: self.webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.stringURL]]];
self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 30.0, 30.0);
self.activityIndicator.center = self.view.center;
[self.view addSubview: self.activityIndicator];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadView];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
}
#end
I just ran my app in Instruments using the Zombies template, which shows -[UIWebView webView:didReceiveTitle:forFrame:] is the Zombie call. But I still can’t figure out what is actually the problem.
(Please download trace if needed)
Any help is greatly appreciated!
[Update]:
As #7KV7 and #David pointed out, there is an obvious bug in my dealloc function. I should call self.webView.delegate=nil; first before I release self.webView. Sorry about that. Unfortunately, after I fix it, the app still crashes in the same way.
If I delete [webViewController release]; from the first code block, the crash actually is gone. But obviously, there will be memory leak.
First of all, remove that call to loadView in viewDidLoad. The framework will the call the method when it doesn't find a view provided in XIB file. Second, your loadView is filled with memory leaks. You are allocating, initializing and retaining an object every time the method is called. So you are taking ownership twice and releasing it only once in the dealloc.
The objects are not being properly deallocated. You should do something like alloc-init-autorelease to solve this. Next thing is the that every time the controller gets loaded, because of your call to loadView, you end up creating two web view objects and two requests. You lose reference to one of them as you reassign. Herein, lies the problem mentioned in the title. You aren't able to reset the delegate of a web view object that has your controller as a delegate. Imagine a request being completed soon after you leave. Here the message will go to a zombie object. This is a pretty good example for why you need to nil out your delegates.
- (void)dealloc
{
self.webView.delegate = nil;
[self.webView release];
[self.activityIndicator release];
[super dealloc];
}
Try this dealloc. You were releasing the webview and then setting the delegate as nil. You should first set the delegate as nil and then release it. Hope this solves the issue.
I think what's happening is that you are going back while the page is still loading so the controller gets deallocated and then the webview finishes loading.
Try calling [webView stopLoading] in your viewDidUnload method to make sure this isn't happening.
Don't know if it's the cause of your problem, but this is definitely wrong:
[self.webView release];
self.webView.delegate = nil;
You cannot (safely) refer to self.webView after you release it!
Instead of pushing webViewController,add its view to self.view .
Dont call [self loadView]; in viewDidLoad.

Hiding Back Button on Navigation Based iPhone App Fails

My issue is that the back button will not restore its visibility if my web request does not finish before or soon after ViewWillAppear has fired.
I have a navigation based iPhone 4.0 application used a simple Root and Detail view setup.
I am working with data that is returned from a webservice so when I push my detail view in its ViewDidLoad function I call my web service method in a separate thread and the Iphone lifecycle does its thing on the main thread. I must disable/hide the back button until the web request has finished (or failed) so I call self.navigationItem.hidesBackButton = YES; in ViewDidLoad and self.navigationItem.hidesBackButton = NO; in the delegate function which fires once my web request has finished or failed.
I already tried the following:
[self.navigationItem performSelectorOnMainThread:#selector(setHidesBackButton:) withObject:NO waitUntilDone:NO];
[self.navigationItem setHidesBackButton:NO];
[self.view setNeedsDisplay];
[self.navigationController.view setNeedsDisplay];
UINavigationItem *nav = self.navigationItem;
nav.hidesBackButton = NO;
Root View Controller Push Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ArticleViewController *articleViewController = [[ArticleViewController alloc] initWithNibName:#"ArticleViewController" bundle:nil];
NewsArticle *newsArticle = [newsItems objectAtIndex:indexPath.row];
articleViewController.articleID = newsArticle.newsID;
[self.navigationController pushViewController:articleViewController animated:YES];
[newsArticle release];
[articleViewController release];
}
Details View Controller Code:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
id scrollView = [[[self webContent] subviews] objectAtIndex:0];
if([scrollView respondsToSelector:#selector(setBackgroundColor:)] )
{
[scrollView performSelector:#selector(setBackgroundColor:)
withObject:[UIColor blackColor]];
}
[self getNewsArticle];
}
//Fires when the web request has finished
- (void) finish:(NewsArticle *)newsArticleFromSvc {
self.navigationItem.hidesBackButton = NO;
self.newsArticle = newsArticleFromSvc;
[self bindNewsArtice];
}
Any help is GREATLY appreciated I can hardly ##$&^ believe that hiding a button in a UI could cause me this much wasted time.
Try use this method of UINavigationItem :
- (void)setHidesBackButton:(BOOL)hidesBackButton animated:(BOOL)animated
I wasn't able to solve this problem. Instead I tweaked my App Logic to make hiding he back button not necessary.

iPhone SDK - WebView activity indicator

I have a big problem with the UIWebView in iPhone SDK.
I have a TabBarApplication with one WebView on each Tab (except the first).
Because it takes quiet a while to load the views I'd like to show an activity indicator.
Here is the code I'm using in order to do that:
-(void)webViewDidStartLoad:(UIWebView *) portal {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *) portal{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
It doesn't work this way... My WebView in the first tab is called "portal", that's why I entered it above, but the same problem exists if I use WebView.
Any ideas? Can't be true that this is soooo difficult.
I'm searching for a clue quiete a while now and found nothing which helped me to build such a (think it's easy) activityindicator.
Thanks a lot for your effort!
Greets from Germany
Tobias
I typically use a UIActivityIndicatorView.
If you have a Navigation Controller on top of the web view it works perfectly:
-(void)webViewDidStartLoad:(UIWebView *) portal {
UIActivityIndicatorView *actInd = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
UIBarButtonItem *actItem = [[UIBarButtonItem alloc] initWithCustomView:actInd];
self.navigationItem.rightBarButtonItem = actItem;
[actInd startAnimating];
[actInd release];
[actItem release];
}
To get rid of the indicator:
-(void)webViewDidFinishLoad:(UIWebView *) portal{
self.navigationItem.rightBarButtonItem = nil;
}
If you aren't using a Navigation Controller then I would simply use either the larger style, UIActivityIndicatorViewStyleWhiteLarge OR place the view on top of a dark semi-transparent view on the screen and then remove them on load finish.

Throwing webview request to another webview

I have a UIViewController with a UIWebView (webview1) in it. The webview is only a few lines of text but does have a link in it. Rather than open the link, which goes to an external website, in this tiny space, I'd like to send it on to webview2, which will be a full screen. The goal is to keep the web requests within my app rather than Safari. Instead of creating another controller for webview2, I'd like to use webview1's controller.
In Webview1Controller controller, I do this in webViewLoad:
webview1.delegate = self;
Here's where I hand off the web request to webview2, which works fine:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
myapp *delegate = [[UIApplication sharedApplication] delegate];
Webview1Controller *webview1Controller = [[Webview1Controller alloc] initWithNibName:#"webview2" bundle:nil];
//self.view = webview2;'
[delegate.navigationController pushViewController: webview1Controller animated:YES];
[webview1Controller.webview2 loadRequest:request];
[webview1Controller release];
return YES;
}
In Interface Builder, I have webview2.xib File's Owner class set to Webview1Controller. It's "view" and the webview2outlet are connected. I have an IBOutlet in Webview1Controller named webview2outlet.
When i go back to webview1, it has also loaded the same request. Is there a way to stop webview1 from loading anything? If I return NO in the above method, webview1 won't render my content.
One solution is just to reload webview1 content on viewWillAppear, which works. But is there a better way?
Return NO from the delegate method.
Regarding your comment, I think what you want to do is make your delegate method check which webview is calling your controller:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if (webView == webview1) {
Webview1Controller *webview1Controller = [[Webview1Controller alloc] initWithNibName:#"webview2" bundle:nil];
[self.navigationController pushViewController:webview1Controller animated:YES];
[webview1Controller.webview2 loadRequest:request];
[webview1Controller release];
return NO;
}
else {
return YES;
}
}
(Note, also, that UIViewController has a navigationController property so you can use that rather than getting it through your app delegate).
What happens if you load a copy of the request instead of the original, and then return NO?
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
myapp *delegate = [[UIApplication sharedApplication] delegate];
Webview1Controller *webview1Controller = [[Webview1Controller alloc] initWithNibName:#"webview2" bundle:nil];
//self.view = webview2;'
[delegate.navigationController pushViewController: webview1Controller animated:YES];
[webview1Controller.webview2 loadRequest:[[request copy] autorelease]];
[webview1Controller release];
return NO;
}