UIImageView weird image property issue - iphone

I'm dealing with a super easy task that somehow introducing some difficulties...
All I'm trying to do is to create a view controller and set its UIImageView's image property to some image.
When I try to that, I get nil =\
GenericViewController *genericViewController = [[GenericViewController alloc] init];
UIImage *image = [UIImage imageNamed:#"Camera.png"];
genericViewController.genericImageView.image = image;
NSLog(#"%#", genericViewController.genericImageView.image);
Output: (null)

I imagine genericImageView is set up either in a nib or in the -loadView method. However, at the point in which you're trying to access the image view, the view for the VC hasn't been loaded yet. The quick fix is to call
(void)genericViewController.view;
before accessing genericImageView. This will force the view to load. The "better" approach would be to give genericViewController an image property that you assign to, then in its setter you can say
- (void)setGenericImage:(UIImage *)image {
if (_genericImage != image) {
[_genericImage release];
_genericImage = [image retain];
if ([self isViewLoaded]) {
self.genericImageView.image = image;
}
}
}
and in -viewDidLoad you can say
- (void)viewDidLoad {
[super viewDidLoad];
self.genericImageView.image = self.genericImage;
}
This method, besides being more modular and architecturally-sound, also has the advantage where if the view controller's view is unloaded (say, another view is pushed onto the nav stack and a memory warning comes along), when it gets re-loaded it will still have the image.

Related

How to update a progressbar during instantiation of timeconsuming ui elements?

I would like to update a progressbar while i am instantiating some ui elements that takes some times. I first create my view during the viewLoad method and add my progress bar there. Once my view appeared in the viewDidAppear method I am making several uikit objects instantiation but i would like to update the progress bar in the mean time. I am not sure how to proceed since everything should happen in the main thread as it is ui elements.
Here is part of my code:
-(void) viewDidAppear:(BOOL)animated
{
// precompute the source and destination view screenshots for the custom segue
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self.progressBar setProgress:.3];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self.progressBar setProgress:.5];
}
In the above code I just need to create two screenshots of views to use them later on. The problem is that i only see the last update (.5) when setting the progress to the progress bar. What is the proper way to do this update?
You can use the performSelectorInBackground:withObject: method in order to instantiate your heavy views. That method (the one that instantiates your views) will have to set your progress bar progress in the main thread.
So your code would look something like this:
- (void)viewDidAppear:(BOOL)animated
{
[self performSelectorInBackground:#selector(instantiateHeavyViews) withObject:nil];
}
- (void)instantiateHeavyViews
{
self.sourceScreenshotView = [[UIImageView alloc] initWithImage:[self.view pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.3f] waitUntilDone:YES];
SCLViewController *rvc = [[SCLViewController alloc] init];
UIView *destinationView = rvc.view;
destinationView.frame = CGRectMake(0, 0, kWidthLandscape, kHeightLandscape);
self.destinationScreenshotView = [[UIImageView alloc] initWithImage:[destinationView pw_imageSnapshot]];
[self performSelectorOnMainThread:#selector(updateMyProgressView:) withObject:[NSNumber numberWithFloat:0.5f] waitUntilDone:YES];
}
- (void)updateMyProgressView:(NSNumber *)progress
{
[self.progressBar setProgress:[progress floatValue]];
}
Edit: of course, it won't animate your progress bar (I don't know if that is what you wanted). If you want it to move on while your views are being created, you should use a delegate to be notified of the progress, and this can be a bit harder. This way you would be able to update the progress bar every time the delegate is notified.

Memory Management, ARC - what to nil?

Background -
I am using automatic reference counting on a project. The root view is a Table View (Master / Detail setup) showing a list of "slide shows". Click on a table cell and you are taken to the detail view which consists of a Scroll view with views (viewController.view) in it (this is the "slide show"). Each slide show has a front cover and back cover (same view controller formatted differently) that sandwich an variable number of pages. Here is the code to load the slide show:
- (void)loadScrollView
{
// The front and back cover are set in Interface Builder because they
// are reused for every slide show, just labels are changed.
[self.scrollView addSubview:self.frontCoverViewController.view];
[self.frontCoverViewController setCoverTitle:_data.name creationDate:_data.creationDate isFrontCover:YES];
[self.pagesArray addObject:self.frontCoverViewController];
for (int i = 0; i < [self getTotalNumberOfComps]; i++)
{
PageViewController *pageView = [[PageViewController alloc] init];
pageView.data = [_compsArray objectAtIndex:i];
[_scrollView addSubview:pageView.view];
pageView.data.imgView = pageView.imageView;
pageView.slideShowViewController = self;
[_pagesArray addObject:pageView];
}
[self.scrollView addSubview:self.backCoverViewController.view];
[self.backCoverViewController setCoverTitle:_data.name creationDate:_data.creationDate isFrontCover:NO];
[self.pagesArray addObject:self.backCoverViewController];
[self.scrollView bringSubviewToFront:_frontCoverViewController.view];
[self setCurrentPage:0];
}
Problem -
So Im trying to reuse this slide show view controller so I need to nil and recreate the pages in the middle because each slide show has a different number of slides. Note a slide [PageViewController] is just a view with an ImageView in it. It has more functionality so we need the controller however the main display of the V.C. is the ImageView. I have created the following method to "empty" the slide show before running loadScrollView again with new data. Here is the empty method:
- (void)saflyEmptyScrollView
{
for (int i = 0; i < [self.pagesArray count]; i++)
{
if (i == 0 && i == ([self.pagesArray count]-1)) {
CoverViewController *cover = (CoverViewController*)[self.pagesArray objectAtIndex:i];
[cover.view removeFromSuperview];
} else {
PageViewController *page = (PageViewController*)[self.pagesArray objectAtIndex:i];
[page.view removeFromSuperview];
page = nil;
}
}
self.pagesArray = nil;
self.pagesArray = [[NSMutableArray alloc] init];
}
Big Question -
My main question is do I need to set the ImageView of each of these pages to nil? Or does setting the page itself to nil also free up the memory used by the ImageView/Labels/etc that are used in that view controller?
I tried adding self.imageView = nil; to the PageViewController's viewDidUnload and viewWillUnload methods (one at a time not in both) and I realized that setting page = nil does not call the pages Unload methods. Am I freeing up memory correctly.
I've read a lot of articles but Im still not sure if Im managing memory in the best way possible. Thanks so much for the help!
Generally, you shouldn't have to set things to nil. And in this specific case, the setting things to nil is doing nothing.
The line page = nil; is redundant, because the variable page goes out of scope immediately afterwards anyway. ARC knows this and doesn't need you to set it to nil.
And self.pagesArray = nil; is redundant because you follow it with self.pagesArray = [[NSMutableArray alloc] init];. The second line on its own will suffice.

Memory problems with UIWebViews

I have a webviewcontroller, which I create one instance of, and then I change the request, and show the webview based on which row is selected in a tableview.
// Replace tableView:didSelectRowAtIndexPath with the following
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
CGRect rect = self.view.frame;
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat height = [UIScreen mainScreen].bounds.size.height;
rect.size.width = width; //self.view.frame.size.width;
rect.size.height = height; //self.view.frame.size.height;
RSSEntry *entry = [_allEntries objectAtIndex:indexPath.row];
page = [[WebViewController alloc] initWithUrl:entry.articleUrl];
page.view.frame = rect;
[self.view addSubview:page.view];
}
My problem is, that after loading a few of these, I get memory warnings and crash.
In another tab, I also have a webview which loads a google map when the tab loads. This crashes the app almost as soon as it is loaded.
Since I am only making one instance of each, I don't see why this problem is happening. Previously I created a new webviewcontroller each time, and then released it, but I had the same problem.
Here is the code to load the Google map:
#interface MapViewController : UIViewController {
IBOutlet UIWebView *mapPage;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
url_string = #"http://maps.google.fr/maps/ms?msa=0&msid=213119412551786463007.0004b3fb7f4c123377402&z=12";
url = [NSURL URLWithString:url_string];
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url_string]];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
mapPage.scalesPageToFit = YES;
mapPage.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
[mapPage loadRequest:request];
[super viewDidLoad];
}
The UIWebView is created in interface builder.
It runs out of memory and crashes right after the map is finished loading, or as soon as you try to interact with the map. It appears to be the very same problem as above, but this time there are no tabs, or no changing the request that the UIWebView uses.
Each time when you select row in UITableView you create a new object of WebViewController and add its view (for some reason?) to current controller's view. I'm not sure that you at least remove that view from superview (you also have to release WebViewController). And of course it will take more and more memory.
And what is the reason to create a whole UIViewController and use it as subview? Why you didn't use UINavigationController?
It turns out the pages were being cached.
In the applicationDidReceiveMemoryWarning method of my application, I added a line to clear the cache. This fixed both of my p
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
/*
Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
*/
[[NSURLCache sharedURLCache] removeAllCachedResponses];
}

Pushing View Controller - viewDidAppear not called

I have this piece of code to push a view controller:
// Setup the animation
[self.navigationController pushViewController:self.productView animated:YES];
self.productView.imageURL = [product imageURL];
// Set the title of the view to the product's name
self.productView.title = [product name];
// Set the label text of all the labels in the view
[self.productView.caloriesL setText:[product calories]];
[self.productView.fatL setText:[product fat]];
[self.productView.saturatesL setText:[product saturates]];
[self.productView.sugarL setText:[product sugar]];
[self.productView.fibreL setText:[product fibre]];
[self.productView.saltL setText:[product salt]];
But the delegate method viewDidAppear does not get called when the productView appears. I looked up the problem on google and theres a lot of different solutions, none of which I could apply to my problem.. I had a similar problem in a previous solution but I got around it by manually calling viewDidApear in the viewDidLoad method. Unfortunately in this case I can't do that as viewDidLoad is called only once (on the first push). Does anyone know how to fix this?
Thanks,
Jack Nutkins
EDIT:
Here is the viewDidAppear method in the productView (and selector):
- (void)viewDidAppear:(BOOL)animated{
//Start animating the activity indicator
[indicator startAnimating];
//Perform this method in background
[self performSelectorInBackground:#selector(loadImage) withObject:nil];
}
- (void) loadImage {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Load the animals image into a NSData boject and then assign it to the UIImageView
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
self.imageView.image = image;
//Stop animating the activity indicator
[indicator stopAnimating];
[pool drain]; //see comment below
}
First: You definitely don't want to be calling any of the standard viewWillLoad, viewDidLoad, viewWillAppear, etc. methods manually. Let the OS do it for you.
Second: Can you show us how your viewDidAppear method is implemented in your self.productView instance? (Just a hunch, you're not expecting this method to be called on your navigation controller, right?) I just want to make sure your method signature is exactly correct. If it's not (due to a mispelling, improper args, etc.) then it definitely won't be called.
Third: I would move your pushViewController: call to after the rest of the code you provided. You don't want the view to be pushed on the screen (so the user can see it) and then have a bunch of on-screen values immediately change. Set your ivars and title property first, then push the view controller. This eliminates any weird flickering.
I solved it, though it doesn't seem conventional, can't believe I didn't try it earlier :
I put this line :
[self.productView viewDidAppear:YES];
Underneath :
// Setup the animation
[self.navigationController pushViewController:self.productView animated:YES];
I also moved the code to set the labels text to run before the above line. (As well as changing my code to send strings to the pushed controller rather that accessing its UI elements.)
Thanks for everyones help,
Jack

Change View background in iPhone app based on property

I need to change the background of one of my Views based on a property that will be set when the View is initialized.
Is this possible?
I've got the background set as a UIImageView and if I set it to an Image in Interface Builder it works, but I can't change the background programatically.
Here is the code I'm using (I based it off a tutorial that loaded an image into a UITableCell):
+ (void) initialize {
/* The threat level images are cached as part of the class,
so they need to be explicitly retained.
*/
redLevel = [[UIImage imageNamed:#"red.png"] retain];
yellowLevel = [[UIImage imageNamed:#"yellow.png"] retain];
greenLevel = [[UIImage imageNamed:#"green.png"] retain];
}
-(id) initWithThreatLevel:(NSInteger) threatLevel {
if (self = [super initWithNibName:#"DetailView" bundle:[NSBundle mainBundle]]) {
switch (threatLevel) {
case 1:
self.threat_levelImageView.image = greenLevel;
break;
case 2:
self.threat_levelImageView.image = yellowLevel;
break;
case 3:
self.threat_levelImageView.image = redLevel;
break;
default:
self.threat_levelImageView.image = greenLevel;
break;
}
}
return self;
}
You should move these assignments to your awakeFromNib method. At initXXX time, NIB connections are usually not yet set up. In awakeFromNib, you're guaranteed that all your connections have been set up.
Make sure threat_levelImageView is an IBOutlet that is connected in Interface Builder. And yes, don't use +initialize for loading the images.