General-Block 56, 1024, 8, 244, 24 Memory Leaks using UIWebView loadRequest - iphone

I'm getting memory leaks that i can't figure out through leaks, build/analyze or overall inspection how to repair. I have a very strong notion that it's due to the loadRequest command from my UIWebview loading javascript, but I can't figure out what's wrong.
Here's my setup:
I have a tabbarcontroller that i programatically create with 4 view controllers: a table view, a mapview, another table view and a webview, respectively.
the 1st tableview shows static data.
the mapview shows annotations that when clicked populates a variable of the 4th tab, the webview, and switches to the webview to display a locally stored html/javascript file that is a 3d model
the 2nd tableview is a listing of all the same information each annotation in the mapview represents, just in table form. it has the same functionality in terms of populating the variable, switching and displaying the html/javascript file in the webview
the webview displays the html/javascript file loaded, if not it shows a UIAlertView to select one from the mapview or the list
when i finally get to executing the loadRequest() from the webview, initially everything works fine. however, when i begin to switch between tabs and view different 3d models (i.e. go to the map view click an annotation to show say model x, watch it load in the webview, then switch to the listing table and click a row to show model y (or x too) and watch it load in the webview) i start receiving memory leaks on the ipad, not the simulator. these memory leaks are almost always: General-Block 56, 1024, 8, 244 & 24 and have no responsible frames, libraries or stack traces to follow up with.
in the webview if i comment out the loadrequest line or do a loadrequest with the request object = nil, i have no memory leaks, but when i switch to using a url object that points to the local files (i even tried pointing it to google.com, which has javascript) it erupts in memory leaks.
i've attempted to do each of these:
[self.webView loadRequest:nil];
[self.webView loadHTMLString:#"" baseURL:nil];
[self.webView stopLoading];
in viewWillDisappear of my webViewController in attempt to completely clean out the webview for reuse in the future, but it doesn't seem to be doing much help. i've also attempted doing this prior to doing the loadRequest but i get the same memory leaks.
fyi, i'm running the latest xcode 4.0.2 and the ipad has the latest update, 4.3.2
from a previous post i commented on and haven't heard back from (general-block memory leak) i believe the problem has to do with the fact that webview doesn't know how to fully rid itself of the javascript when i switch views
i did a test with a simple html file with no javascript and the general-block memory leaks are gone. however, i get other cfnetwork / http message memory leaks but that's a different story and not of my concern at the moment.
below is example code of when one of the annotations on the mapView is clicked which triggers the loadModel function in the webViewController:
- (void) mapCallOutPressed: (UIView *) sender {
NSInteger selectedIndex = sender.tag;
MyLocation *selectedObject = [_mapView.annotations objectAtIndex:selectedIndex];
MyModel *model = selectedObject.model;
NSLog(#"Model selected from mapview = %#", model.description);
// Get reference to the webview from the tabBarController (viewController's index=3)
WebViewController *webViewController = [self.tabBarController.viewControllers objectAtIndex:3];
webViewController.model = model;
[webViewController loadModel];
// Switch tabs to the webView
self.tabBarController.selectedIndex = 3;
[self.tabBarController.selectedViewController viewDidAppear:YES];
below is my webViewController.h & .m:
WebViewController.h:
WebViewController : UIViewController <UIWebViewDelegate> {
UIWebView *webView;
NSString *templateRunFilePath;
MyModel *model;
NSString *templateRunTitle;
#property (nonatomic, retain) UIWebView *webView;
#property (assign) MyModel *model;
#property (nonatomic, copy) NSString *templateRunFilePath;
#property (nonatomic, copy) NSString *templateRunTitle;
-(void) loadModel;
WebViewController.m:
- (void) viewDidLoad {
[super viewDidLoad];
NSLog(#"in webview did load");
self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
self.webView.delegate = self;
self.webView.autoresizingMask=(UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
self.webView.scalesPageToFit = YES;
[self.view addSubview:self.webView];
}
- (void) viewWillAppear:(BOOL)animated {
if (self.model) {
self.tabBarController.navigationItem.title = [NSString stringWithFormat: #"%# - %#", self.tabBarController.navigationItem.title, self.model.description];
}
else {
UIAlertView *msg = [[UIAlertView alloc] initWithTitle:#"No Model Selected"
message:#"Please select a model from the map or list tab to view."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[msg show];
[msg release];
}
}
- (void) loadModel {
NSString *localURLPath = [NSString stringWithFormat:#"%#/%#/%#", self.templateRunFilePath, self.model.folderName, self.model.modelFileName];
NSURL *url = [NSURL fileURLWithPath:localURLPath];
NSURLRequest *requestObj = [NSURLRequest requestWithURL: url];
[self.webView loadRequest:requestObj];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:#"WebKitCacheModelPreferenceKey"];
}
-(void)viewWillDisappear:(BOOL)animated {
NSLog(#"webview view will disappear");
[super viewWillDisappear:animated];
//[self.webView loadRequest:nil];
//[self.webView loadHTMLString:#"" baseURL:nil];
self.webView.delegate = nil;
}
- (void)dealloc {
self.webView.delegate = nil;
[self.webView release];
[super dealloc];
}
if you have any advice or corrections, i'd greatly appreciate it. i have < 1 week to figure this out and would highly thank you if you could give me any insight.
thanks!
-Mike

Mike, I am not sure whether you are doing following intentionally or by mistake?
- (void) loadModel {
[self.webView loadHTMLString:#"" baseURL:nil]; // here you are loading... or you have tried with commenting it also?
NSString *localURLPath = [NSString stringWithFormat:#"%#/%#/%#", self.templateRunFilePath, self.model.folderName, self.model.modelFileName];
NSURL *url = [NSURL fileURLWithPath:localURLPath];
NSURLRequest *requestObj = [NSURLRequest requestWithURL: url];
[self.webView loadRequest:requestObj]; // again here you are also loading?
}

Related

Press a Button and open a URL in another ViewController

I am trying to learn Xcode by making a simple app.
But I been looking on the net for hours (days) and I cant figure it out how I make a button that open a UIWebView in another ViewController :S
first let me show you some code that I have ready:
I have a few Buttons om my main Storyboard that each are title some country codes like UK, CA and DK.
When I press one of those Buttons I have an IBAction like this:
- (IBAction)ButtonPressed:(UIButton *)sender {
// Google button pressed
NSURL* allURLS;
if([sender.titleLabel.text isEqualToString:#"DK"]) {
// Create URL obj
allURLS = [NSURL URLWithString:#"http://google.dk"];
}else if([sender.titleLabel.text isEqualToString:#"US"])
{
allURLS = [NSURL URLWithString:#"http://google.com"];
}else if([sender.titleLabel.text isEqualToString:#"CA"])
{
allURLS = [NSURL URLWithString:#"http://google.ca"];
}
NSURLRequest* req = [NSURLRequest requestWithURL:allURLS];
[myWebView loadRequest:req];
}
How do I make this open UIWebview on my other Viewcontroller named myWebView?
please help a lost man :D
Well, you've firstViewController already designed and coded, now you've to create secondViewController with a UIWebView binded in IB it self, also it have a setter NSString variable like strUrl that you need to pass at the time of pushing or presenting secondViewController, and assign it to UIWebView in viewDidLoad of secondViewController. See my answer about how to pass a NSString? Also UIWebView has its delegates method (What's delegate & datasource methods? - Apple Doc) Which you can use to handle URL request.
These are the delegate of UIWebView, if you'll going to use it, you need to give UIWebView delegate to self.
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
firstViewController.m
- (IBAction)ButtonPressed:(UIButton *)sender {
NSURL* allURLS;
//get your URL
secondViewControlelr *secondView=[[secondViewControlelr alloc]init];
second.urlToLoad=allURLS;
[self.navigationController pushViewController:secondView animated:YES];
}
secondViewControlelr.h
//declare url and set property
NSURL *urlToLoad;
secondViewControlelr.m
- (void)viewDidLoad
{
myWebView=[[UIWebView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
[self.view addSubview:myWebView];
NSURLRequest *urlReq=[NSURLRequest requestWithURL:self.urlToLoad cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:10];
[myWebView loadRequest:urlReq];
}

MWFeedParser in-app web browser INSTEAD of safari

So i'm using the MWFeedParser, which I think is a beautiful RSS feed app, but when clicking on a link it goes to Safari. I fear that a good portion of iPhone users are unaware of the fact that they can double-click on the iPhone's one and only button to view all open apps and go back to the RSS feed, and will thus get stuck. Therefore I'd rather use an in-app browser.
In other words, currently when I click on a link from MWFeedParser it goes to Safari; I prefer it goes to an in-app browser.
I have the browser class set up called WebViewController.
basically with [detailWebView loadRequest:[NSURLRequest requestWithURL:detailURL]];
So "detailURL" is what it will look for
What do I put in the DetailTableViewController instead of the sharedApplication code???
bummer nobody was able to answer...in case others are looking for how to do it, change the contents of didSelectRowAtIndexPath to
if (_webViewController == nil) {
self.webViewController = [[[WebViewController alloc] initWithNibName:#"WebViewController" bundle:[NSBundle mainBundle]] autorelease];
}
MWFeedItem *entry = [parsedItems objectAtIndex:indexPath.row];
_webViewController.entry = entry;
[self.navigationController pushViewController:_webViewController animated:YES];
// Deselect
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
Add a WebViewController;
the .m has
#import "WebViewController.h"
#import "MWFeedItem.h"
#implementation WebViewController
#synthesize webView = _webView;
#synthesize entry = _entry;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (void)viewWillAppear:(BOOL)animated {
NSURL *url = [NSURL URLWithString:_entry.link];
[_webView loadRequest:[NSURLRequest requestWithURL:url]];
}
- (void)viewWillDisappear:(BOOL)animated {
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[_entry release];
_entry = nil;
[_webView release];
_webView = nil;
[super dealloc];
}
And the .h has
#class MWFeedItem;
#interface WebViewController : UIViewController {
UIWebView *_webView;
MWFeedItem *_entry;
}
#property (retain) IBOutlet UIWebView *webView;
#property (retain) MWFeedItem *entry;
(Basically I just took the webview part of Ray Wenderlich's RSS feeder and threw it in to this one.)

How to load a UIWebView with a close button on top?

I currently have the following code that loads a UIWebView from another View. Now is there anyway I can have a close button?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIWebView *webView=[[UIWebView alloc] initWithFrame:CGRectMake(0,0,320,480)];
[self.view addSubview:webView];
NSURLRequest *urlRequest;
NSURL *urlforWebView;
urlforWebView=[NSURL URLWithString:#"http://www.google.com"];
urlRequest=[NSURLRequest requestWithURL:urlforWebView];
[webView loadRequest:urlRequest];
}
I am going to load a page built using jquery mobile, so a close button inside the page would also work fine. But on a navigation bar would be ideal. Btw, my application does not have a UINavigationBar
I would create a new sub class of UIViewController, say WebViewController with a nib. Then I would add an UINavigationBar with a close button and an UIWebView. Then to show your web view controller you can do something like:
WebViewController *webViewController = [[WebViewController alloc] init];
webViewController.loadURL = [NSURL URLWithString:#"http://www.google.com"];
[self presentModalViewController:webViewController animated:YES];
[webViewController release];
In your WebViewController you can define:
#property (nonatomic, retain) IBOutlet UIWebView *webView;
#property (nonatomic, retain) NSURL *loadURL;
- (IBAction)close:(id)sender;
and implement something like this:
- (void)viewDidLoad {
[super viewDidLoad]
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:self.loadURL];
[self.webView loadRequest:urlRequest];
}
- (IBAction)close:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
Whenever you load the UIWebView, you could run a javascript snippet on the content first.
[webView stringByEvaluatingJavaScriptFromString:#"window.close = function() { window.location = 'app://close-webview'; };"];
That just hijacks the normal behavior of window.close() and gives you chance to catch the call in your Objective-C.
Then in your UIWebViewDelegate you could listen for whatever you chose as the close url.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if([request.URL.absoluteString isEqualToString:#"app://close-webview"]) {
webView.hidden = YES;
// or whatever you want to do to remove the UIWebView...
return NO;
}
return YES;
}
A bit hackish, but it would allow the web developer to control the look and feel of the close button from within the HTML content.

getting HTML source code from webView

I'm making an iphone app...can I get the source from it in objective-c?
Thanks!
Elijah
You can get anything from the web view that JavaScript can return using stringByEvaluatingJavaScriptFromString:. Try something along the lines of
document.lastChild.outerHTML
The document will likely have two child nodes; the first will be the DOCTYPE, and the second the <html> element.
If you need to get the contents of the current URL in your web view you could have something like this in a UIViewController. I didn't compile this code, so there may be some syntax errors, but this should be a solid starting point for your problem.
#interface aViewController : UIViewController <UIWebViewDelegate>
UIWebView *webView;
NSString *htmlSource;
#end
#implementation aViewController
-(void) viewDidLoad {
CGRect webFrame = [[UIScreen mainScreen] applicationFrame];
self.webView = [[UIWebView alloc] initWithFrame:webFrame];
self.webView.delegate = self;
NSURL *aURL = [NSURL URLWithString:#"http://www.myurl.com"];
NSURLRequest *aRequest = [NSURLRequest requestWithURL:aURL];
//load the index.html file into the web view.
[self.webView loadRequest:aRequest];
}
//This is a delegate method that is sent after a web view finishes loading content.
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSLog(#"finished loading");
self.htmlSource = [[NSString alloc] initWithData:[[webView request] HTTPBody] encoding:NSASCIIStringEncoding];
}
#end

How to delete the cache from UIWebview or dealloc UIWebview

Every time I load a new page with UIWebView the before loaded page is shown for a short time.
How can I clear that cache? Another possibility would be to dealloc UIWebview. I tried that but than my UIWebView is always "empty". How should the alloc and dealloc be done in this case?
I noticed that the UIWebView is consuming about 10 MB RAM. Now the UIWebView is loaded together with the ViewController. And the view is autoreleased as well as the UIWebView is autoreleased. Wouldn't it be better to dealloc the WebView each time?
Solution:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CGRect frame = CGRectMake(0, 0, 320, 480);
self.webView = [[[UIWebView alloc]initWithFrame:frame] autorelease];
self.webView.scalesPageToFit = YES;
[self.view addSubview:self.webView];
}
- (void) viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.webView removeFromSuperview];
self.webView = nil;
}
I had nearly the same problem. I wanted the webview cache to be cleared, because everytime i reload a local webpage in an UIWebView, the old one is shown. So I found a solution by simply setting the cachePolicy property of the request. Use a NSMutableURLRequest to set this property. With all that everything works fine with reloading the UIWebView.
NSURL *url = [NSURL fileURLWithPath:MyHTMLFilePath];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[self.webView loadRequest:request];
Hope that helps!
My solution to this problem was to create the UIWebView programmatically on viewWillAppear: and to release it on viewDidDisappear:, like this:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.webView = [[[UIWebView alloc]initWithFrame:exampleFrame] autorelease];
self.webView.scalesPageToFit = YES;
self.webView.delegate = self;
self.webView.autoresizingMask = webViewBed.autoresizingMask;
[exampleView addSubview:self.webView];
}
- (void) viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.webView removeFromSuperview];
self.webView.delegate = nil;
self.webView = nil;
}
If you do this and your UIWebView doesn't get released you should check it's retain count and understand who is retaining it.
If you want to remove all cached responses, you may also want to try something like this:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
If I had to make a guess your property for webView is
#property (nonatomic, retain) IBOutlet UIWebView *webView;
Correct? The important part is the retain.
self.webView = [[UIWebView alloc]initWithFrame:frame];
The above code allocates a UIWebView (meaning retainCount = 1) and then adds it to self.webView, where it is retained again (retainCount = 2).
Change the code to the following, and your webView should be deallocated.
self.webView = [[[UIWebView alloc]initWithFrame:frame] autorelease];
addSubview: can only be called on UIViews.
//[self.navigationController addSubview:self.webView];
should be
[self.navigationController.view addSubview:self.webView];
I did something simpler, I just set the webView's alpha to 0 on loadRequest and 1 on webViewDidFinishLoad.
thewebview.alpha=0.0;
thewebview.backgroundColor=[UIColor whiteColor];
[thewebview loadRequest:[NSURLRequest requestWithURL:url(str)]];
and
- (void)webViewDidFinishLoad:(UIWebView *)webView {
thewebview.alpha=1.0;
}
(Actually I have a fade-in, as rendering f.ex. a PDF can take 1/5 seconds or so.)
You could also load #"about:blank" after the webview disappears, then it will be empty the next time you access it.