views:

78

answers:

1

In my iPhone app, views will often load slowly when transitioning, like if a user clicks a button on the Tab Bar Controller. This happens more if the phone is low on memory. It doesn't really come up on 3GS phones, but it's a big problem on 3G phones.

I suspect that I'm not following some best practices for creating UIViewControllers. I think I might be doing too much in the init functions, not using the viewDidLoad function, or something. It seems to affect all my views, so I think it's a problem with my style in general, not some particular snippet.

Can anyone tell me what i might be doing wrong? Here is some sample code, from a UIViewController subclass:

EDIT: In response to the question: "where is this being called?"

This function gets called in this case when the user clicks a marker on the map:

if(marker.label.tag == SavedBookmarkTag) {

  SavedDetailScreen *savedBookmark = [[[SavedDetailScreen alloc] initBookmarkView:
    [(NSDictionary *)marker.data objectForKey:@"bookmark"]]autorelease];
    [savedBookmark showMap];
    [self.navBar pushViewControllerWithBackBar:savedBookmark];
    return;

}

END EDIT

-(id)initBookmarkView: (Bookmark *)bm {

    self = [self initView];
    self.bookmark = bm;

    primaryLabel.text = [bm title];
    secondaryLabel.text = [self getLineWithLat:[bm lat] AndLon:[bm lon] AndDate:[bm timeCreated]];
    return self;

}


- (id)initView {

  self = [super init];
  self.isWaypoint = NO;

  UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"238-beveled-background.png"]];

  bg.frame = CGRectMake(0, 0, 320, 376);
  [self.view addSubview:bg];
  bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"small-label.png"]];
  [self.view addSubview:bg];
  [bg release];

  self.primaryLabel = [[UILabel alloc]init];
  primaryLabel.font = TITLE_FONT;
  primaryLabel.backgroundColor = [UIColor clearColor];
  primaryLabel.textColor = LIGHT_BLUE;

  self.secondaryLabel = [[UILabel alloc]init];
  secondaryLabel.font = TEXT_FONT;
  secondaryLabel.backgroundColor = [UIColor clearColor];
  secondaryLabel.textColor = LIGHT_BLUE;
  secondaryLabel.lineBreakMode = UILineBreakModeClip;

  self.thirdLabel = [[UILabel alloc]init];
  thirdLabel.font = TEXT_FONT;
  thirdLabel.backgroundColor = [UIColor clearColor];
  thirdLabel.textColor = LIGHT_BLUE;
  thirdLabel.lineBreakMode = UILineBreakModeCharacterWrap;

  [self.view addSubview:primaryLabel];
  [self.view addSubview:secondaryLabel];
  [self.view addSubview:thirdLabel];    

  self.loadingBackground = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"stats-box.png"]];
  loadingBackground.frame = CGRectMake(0, 115, loadingBackground.frame.size.width, loadingBackground.frame.size.height);
  [self.view addSubview:loadingBackground];
  [self.view sendSubviewToBack:loadingBackground];

  AnimatedGif *animatedGif = [[[AnimatedGif alloc] init] autorelease];
  NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"35" ofType:@"gif"]]; 
  [animatedGif decodeGIF: data];

  UIImageView *loadingImage = [animatedGif getAnimation];
  loadingImage.frame = CGRectMake(150,150,loadingImage.frame.size.width,loadingImage.frame.size.height);

  [loadingImage startAnimating];    
  [loadingBackground addSubview:loadingImage];
  [loadingImage release];

  [self layoutSubviews];

  return self;

}   


- (void) layoutSubviews {

    self.view.frame = CGRectMake(0,0,320,372);
    primaryLabel.frame = CGRectMake(30, 30, 260, 18);
    secondaryLabel.frame = CGRectMake(30 ,52, 260, 16);
    thirdLabel.frame = CGRectMake(30, 72, 260, 16);

}
+1  A: 

The slowness that you're seeing can probably be attributed to the fact that constructing objects and reading data from the flash are both expensive processes. Look for opportunities to reuse existing objects rather than constructing them multiple times, and consider deferring especially expensive operations until after the view gets displayed.

In this case, I would start with a couple changes:

  1. Make savedBookmark a member variable so that you can construct it once and reuse it. Replace your initBookmarkView: method with a setBookmarkView: method that you can call after this member variable is constructed to reconfigure your labels for the specific bookmark being displayed.

  2. Take the subview creation code out of initView and put it in loadView. This is the most appropriate place to construct your own view hierarchy programmatically. UIViewController implements lazy loading on its view property to defer construction as long as possible. The view property is nil until the first time it's requested. At that point UIViewController calls loadView to set the property. The default implementation loads the view from a nib file if one is defined. Otherwise it just constructs an empty UIView and makes that the main view. Note that you'll have to construct the container view and set the view property yourself.

In other apps you may get some improvement by moving some initialization code into viewDidLoad:, which gets called after the view property is loaded, whether programmatically or from a nib. If you ever have an especially slow operation like loading images from a remote URL, you might want to start loading the data asynchronously in viewDidLoad:, and then update your subviews once the data finishes loading. In some cases you may also want to defer some code until viewDidAppear:. Just be aware that this method gets called every time the view appears, unlike loadView and viewDidLoad:, which only get called once.

cduhn