I'm developing for iPhone, SDK 3.1. I have about 150 images that I need to display to the user for him to page through. I've copied the code from the PageControl sample from Apple but once I load it onto the iPhone the application crashes if I scroll through quickly. I tried to write some optimization to conserve memory but it doesn't help much. I was wondering if anyone could tell me whether my optimization needs improvement or if I have some other issue. The relevant code is below.
// ReviewViewController.h @interface ReviewViewController : UIViewController {
NSMutableArray *reviewArr; NSMutableArray *viewControllers; IBOutlet UIScrollView *scroller; BOOL dirty; NSInteger pageCount; }
@property (retain, nonatomic) NSMutableArray *reviewArr; @property (retain, nonatomic) NSMutableArray *viewControllers; @property (assign, nonatomic) UIScrollView *scroller;
//ReviewViewController.m
-(void)clearScroller { NSArray *subviews = [[NSArray alloc] initWithArray:scroller.subviews]; for (UIView *subview in subviews) { //NSLog(@"DEBUG - view %d", subview.tag); [subview removeFromSuperview]; } [subviews release]; [scroller setContentOffset:CGPointMake(0,0) animated:NO]; }
(void)initialize:(int)page { if([reviewArr count] == 0) { NSLog(@"No more cards on stack"); UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 70, 420, 45)]; label.text = @"You have no review cards in your stack"; label.numberOfLines = 2; label.font = [UIFont systemFontOfSize:20]; [scroller addSubview:label]; [label release]; [back setHidden:YES]; [forward setHidden:YES]; [mathFactAction setHidden:YES]; return; }
NSLog(@"Initializing..%d", page);
[self setHeading]; [self clearScroller]; [mathFactAction setHidden:NO];
NSMutableArray *controllers = [[NSMutableArray alloc] init]; for (unsigned i = 0; i < reviewArr.count; i++) { [controllers addObject:[NSNull null]]; } self.viewControllers = controllers; [controllers release];
// a page is the width of the scroll view scroller.pagingEnabled = YES; scroller.contentSize = CGSizeMake(scroller.frame.size.width * reviewArr.count, scroller.frame.size.height); scroller.showsHorizontalScrollIndicator = NO; scroller.showsVerticalScrollIndicator = NO; scroller.scrollsToTop = NO; scroller.delegate = self;
// pages are created on demand // load the visible page // load the page on either side to avoid flashes when the user starts scrolling if(page > 0) { [self loadScrollViewWithPage:page - 1]; }
[self loadScrollViewWithPage:page]; [self loadScrollViewWithPage:page + 1]; }
(void)checkForDirtyPages { for (unsigned i = 0; i < [self.viewControllers count]; i++) { if(i == pageCount-1) continue; else if(i == pageCount) continue; else if(i == pageCount+1) continue; else unloadPage:i; } }
(void)unloadPages { dirty = FALSE; [self unloadPage:pageCount-3]; [self unloadPage:pageCount-4]; [self unloadPage:pageCount+3]; [self unloadPage:pageCount+4]; }
(void)unloadPage:(int)page { if (page < 0) return; if (page >= reviewArr.count) return;
// replace the placeholder if necessary FactViewController *controller = [viewControllers objectAtIndex:page]; if ((NSNull *)controller != [NSNull null]) {
/*NSArray *subviews = [[NSArray alloc] initWithArray:controller.view.subviews]; for (UIView *subview in subviews) { //NSLog(@"DEBUG - view %d", subview.tag); [subview removeFromSuperview]; } [subviews release]; */ // remove teh innerscroller from viewControllers to conserve memory [self.viewControllers replaceObjectAtIndex:page withObject:[NSNull null]]; } }
(void)loadScrollViewWithPage:(int)page { if (page < 0) return; if (page >= reviewArr.count) return;
// replace the placeholder if necessary FactViewController *controller = [viewControllers objectAtIndex:page]; //UIScrollView *innerScroller = [self.viewControllers objectAtIndex:page]; if ((NSNull *)controller == [NSNull null]) { controller = [[FactViewController alloc] initWithReviewNumber:[reviewArr objectAtIndex:page]]; [self.viewControllers replaceObjectAtIndex:page withObject:controller]; [controller release]; }
if (nil == controller.view.superview) { CGRect frame = scroller.frame; frame.origin.x = frame.size.width * page; frame.origin.y = 0; controller.view.frame = frame; [scroller addSubview:controller.view]; //[self unloadPages]; }
}
(void)scrollViewDidEndDecelerating:(UIScrollView *)sender { if(dirty) [self unloadPages]; //[self resetScroller:pageCount-1]; //[self resetScroller:pageCount+1]; }
(void)scrollViewDidScroll:(UIScrollView *)sender { // We don't want a "feedback loop" between the UIPageControl and the scroll delegate in // which a scroll event generated from the user hitting the page control triggers updates from // the delegate method. We use a boolean to disable the delegate logic when the page control is used. // Switch the indicator when more than 50% of the previous/next page is visible CGFloat pageWidth = scroller.frame.size.width; int page = floor((scroller.contentOffset.x - pageWidth / 2) / pageWidth) + 1; // pageControl.currentPage = page;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling) [self loadScrollViewWithPage:page - 1]; [self loadScrollViewWithPage:page]; [self loadScrollViewWithPage:page + 1];
// if we are on a new page if(page != pageCount) { dirty = TRUE; pageCount = page; [self setHeading]; }
// A possible optimization would be to unload the views+controllers which are no longer visible }
(void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview // Release anything that's not essential, such as cached data [self checkForDirtyPages]; }
// FactViewController.m
@interface FactViewController : UIViewController { IBOutlet UIScrollView *scrollView; //IBOutlet UILabel *pageNumberLabel; NSInteger review_id; }
@property (nonatomic, retain) UIView *scrollView; //@property (nonatomic, retain) UILabel *pageNumberLabel; @property (assign, nonatomic) NSInteger review_id;
-(void)unloadImage; - (id)initWithReviewNumber:(NSNumber *)reviewNumber; - (int)getMathFactId;
@end
// Load the view nib and initialize the pageNumber ivar. - (id)initWithReviewNumber:(NSNumber *)reviewNumber { if (self = [super initWithNibName:@"FactViewController" bundle:nil]) { self.review_id = [reviewNumber intValue]; } return self; }
// Set the label and background color when the view has finished loading. - (void)viewDidLoad { NSLog(@"(FactVC) view did load"); [self showImage]; [super viewDidLoad];
}
-(void)unloadImage { [scrollView release]; }
-(void)showImage { NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setMinimumIntegerDigits:2]; NSLog(@"Loading image (%d)", self.review_id);
NSString *img_file = [NSString stringWithFormat:@"%@", [numberFormatter stringForObjectValue:[NSNumber numberWithInt:self.review_id]]]; [numberFormatter release];
NSString *fileLocation = [[NSBundle mainBundle] pathForResource:img_file ofType:@"gif"]; NSData *imageData = [NSData dataWithContentsOfFile:fileLocation];
UIImage *image = [UIImage imageWithData:imageData];
//UIImage *image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:img_file ofType:@"gif"]]; UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; // inDirectory:@"Resources/questions"
[scrollView addSubview:imageView]; [scrollView setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
[imageView release]; }
Sorry this is so long, but I wanted to make sure it was all in there. Any ideas would be appreciated.
http://www.stackoverflow.com/questions/1481741/
Posted: September 26, 2009 at 5:49 PM by: Ken
have you find any solution because I ran in to same memory issue when loading more than 100 images in page control apples example. Can you please help?
On December 21, 2009 at 5:34 PM by: ashish