views:

485

answers:

2

I have a small application for displaying several UIImageViews in a UIScroller in a similar fashion to the Photo app. I have a TableView which, when i select an item, parses an XML document of photos (added to an array) and adds a UIViewController which displays the images.

The problem is I have a tab bar controller which, upon clicking a tab bar item should go back to the TableView after remove the View that displays the images and dealloc all used memory. The problem is I can't work out how to achieve this. Whether it's a lack of understanding the memory rules I don't know.

Here's the process I'm having issues with.

Table View Controller

This is called after parserDidEndDocument:. backToMenu is called from the tab bar item.

@interface AlbumsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
    AlbumViewController *albumViewController;
}
@property (nonatomic, retain) AlbumViewController *albumViewController;


- (void)displayPhotos {
    albumViewController = [[AlbumViewController alloc] initWithNibName:@"AlbumView" bundle:nil];

    albumViewController.parentView = self;
    albumViewController.arrPhotos = self.arrCurrentSetOfPhotos;

    [self.view addSubview:albumViewController.view];

    [albumViewController populateScroller];
}

- (void)backToMenu {
    [albumViewController.view removeFromSuperview];
}

Album View Controller

This is a UIViewController containing a UIScroller and UIPageControl. It adds several ImageViewControllers.

- (void)populateScroller {

    imagesScroller.pagingEnabled = YES;
    imagesScroller.contentSize = CGSizeMake(imagesScroller.frame.size.width * [self.arrPhotos count], 380);
    imagesScroller.showsHorizontalScrollIndicator = NO;
    imagesScroller.showsVerticalScrollIndicator = NO;
    imagesScroller.scrollsToTop = NO;
    imagesScroller.delegate = self;
    imagesScroller.backgroundColor = [UIColor blackColor];

    pageControl.numberOfPages = [self.arrPhotos count];
    pageControl.currentPage = 0;
    pageControl.backgroundColor = [UIColor blackColor];

    NSMutableArray *controllers = [[NSMutableArray alloc] init];
    for (int i = 0; i < [self.arrPhotos count]; i++) {

        CGRect frame = imagesScroller.frame;
        frame.origin.x = frame.size.width * i;
        frame.origin.y = 0;

        NSString *strImagePath = [self.arrPhotos objectAtIndex:i];

        ImageViewController *imageViewController = [ImageViewController alloc];
        imageViewController.localImage = YES;
        [imageViewController initWithPhotoName:strImagePath];
        [controllers addObject:imageViewController];

        imageViewController.view.frame = frame;
        [imagesScroller addSubview:imageViewController.view];

        [imageViewController release];

    }

    self.viewControllers = controllers;
    [controllers release];

}

ImageViewController

This has one method. It adds an subclassed UIView called ImageView which handles adding a UIImageView.

- (void)loadImage {

    NSString *strRootURL = @"http://www.marklatham.co.uk";

    CGRect rect = CGRectMake(0.0, 0.0, 320.0, 480.0);

    ImageView *imageView = [ImageView alloc];
    imageView.strURL = [strRootURL stringByAppendingString:self.strThisPhoto];
    imageView.strTmpPrefix = (self.localImage) ? @"_tmp_rockphotothumb_" : @"_tmp_rockphotolarge_";
   [imageView initWithFrame:rect];

   [self.view addSubview:imageView];

   [imageView release];
}

The backToMenu function is supposed to remove/dealloc AlbumViewController and call delloc on ALL child subviews freeing up the used memory.

Where am going wrong? Any help would be appreciated.

Thanks.

+2  A: 

Your AlbumViewController is still holding a reference to the view, so it won't get released until you release that reference. But, this is a good thing.

Don't worry about freeing memory every time the user changes screens. It'll just slow things down as they flip back and forth between different views. If the system gets low on memory, it'll call didReceiveMemoryWarning on all of your view controllers, which will cause them to release their views if they aren't being displayed.

You just want to be a bit more clever about how you allocate/release the albumViewController

- (void)displayPhotos {
    ////// Only create this view controller if it doesn't exist yet
    if(albumViewController == nil) {
        albumViewController = [[AlbumViewController alloc] initWithNibName:@"AlbumView" bundle:nil];
    }

    albumViewController.parentView = self;
    albumViewController.arrPhotos = self.arrCurrentSetOfPhotos;

    [self.view addSubview:albumViewController.view];

    [albumViewController populateScroller];
}

- (void)backToMenu {
    [albumViewController.view removeFromSuperview];
    //// if you don't do this, you'll have a retain cycle between the two
    //// view controllers and neither will ever get released
    albumViewController.parentView = nil;
}

- (void)didReceiveMemoryWarning {
    ///// if the albunViewController isn't in use, go ahead and free its memory
    if([self.albumViewController isViewLoaded] && self.albumViewController.view.superview == nil) {
        self.albumViewController = nil;
    }
    [super didReceiveMemoryWarning];
}

Also, instead of calling populateScrollers from AlbumsViewController, call it in the setArrPhotos method of AlbumViewController, but only if arrPhotos has changed. For example:

- (void)setArrPhotos:(NSArray *)newArrPhotos {
    if(newArrPhotos != arrPhotos)
    {
        [arrPhotos release];
        arrPhotos = [newArrPhotos retain];
        [self populateScrollers];
    }
}

This way, if the user goes to view the same album over and over again, you won't recreate the same view hierarchy over and over.

Jacques
Cheers for this. I implemented your changes and they do make sense but there still seems to be an issue with didReceiveMemoryWarning.I added NSLog(@"here"); to it to check when it's hitting it. The problem is as soon as it does the app crashes. Am I right in thinking that it should dealloc albumViewController and it's child views?
Lee M
You never want to call dealloc from your code. It gets called automatically when an object's retain count reached 0. You do want to release albumViewController, but only if it its view isn't currently being shown (thus the check for of the superview in the code above). Also, you don't need to dealloc the child views. Once the albumViewController's retain count reaches 0, it'll automatically release its view, which should in turn release to its view's children being released.Feel free to post your new code if you're still having issues.
Jacques
I'm not actually calling dealloc directly, only release and setting retained pointers to nil. I've also done a bit of refactoring but I'm still having issues with displaying lots of images i.e displaying album after album it calls didReceiveMemoryWarning on both views but still crashes upon displaying the next image. Is it possible you could take a look at the project and see if you can see where I'm failing? There's a copy here http://www.gretanova.com/misc/Rock_Photography_v2.zip. Thanks again for your help.
Lee M
Link is broken. http://www.mediafire.com/?nztrd1yhzoo
Lee M
A: 

After convincing my client I managed to get around this by going with the three20 framework.

Lee M