title: PageControl Optimization
body: <p>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.</p>
  
  <pre><code>// ReviewViewController.h
  @interface ReviewViewController : UIViewController &lt;UIActionSheetDelegate, UIScrollViewDelegate&gt; {
  
   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 &lt; 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 &gt; 0) {
    [self loadScrollViewWithPage:page - 1];
   }
  
   [self loadScrollViewWithPage:page];
   [self loadScrollViewWithPage:page + 1]; 
  }
  
  - (void)checkForDirtyPages {
   for (unsigned i = 0; i &lt; [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 &lt; 0) return;
      if (page &gt;= 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 &lt; 0) return;
      if (page &gt;= 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;
  }
  
  - (void)dealloc {
      //[pageNumberLabel release];
   //[card release];
   [scrollView release];
      [super dealloc];
  }
  
  // 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];
  }
  </code></pre>
  
  <p>Sorry this is so long, but I wanted to make sure it was all in there.</p>