• invisible

Browsing ‘App Development’


Speeding up Tables with Section Headers

January 22nd, 2010 by eddie

I’ve been working recently on a table with section headers that had really slow scrolling speeds (around 30 FPS). After a lot of digging, I found the problem in a strange place, so I thought I would share in hopes that someone else can save some time.

The gist of the problem is that when you override tableView:viewForHeaderInSection: it ends up being called constantly when you scroll. This in turn calls tableView:titleForHeaderInSection, and if that’s slow, you’re going to see serious lag.

What can unexpectedly make that call slow, however, is the standard:
[[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];

What dramatically speeds it up, however, is declaring your own NSArray *sectionHeaders and setting it in your init as so:
sectionTitles = [[[UILocalizedIndexedCollation currentCollation] sectionTitles] copy];

I found that referencing this instead of the indexed collation dramatically sped up my scrolling speed, and now I’m whisking along at an acceptable speed (60 FPS).


Integrating the iPod in OS 3.0 (2)

September 10th, 2009 by eddie

I have a few more tidbits that might help anyone else trying to integrate the iPod in OS 3.0.

After a lot of experimentation, I’ve determined that there are vastly different times that certain data takes to load into memory. You can use this to your advantage. One example, building off my last post, is that a MPMediaItem’s persistent ID (pID) loads much more quickly than it’s other properties. What this means is that, if you’re forced to load other properties from memory, you can cache the information about each item in a NSMutableDictionary with the keys being pIDs. You can then save this out to disk on applicationWillTerminate, load it in on the next run, grab an item’s pID, check the dictionary, and get a huge load-time decrease if you’ve already seen it.

Another trick that has helped me out a lot is creating an MPMediaItemCollectionWrapper class. I init one of these classes with an MPMediaItemCollection and then, in a thread-safe way, lazy-load absolutely everything. This is made easy by having every necessary property be a read only property with an overridden getter. Here’s an example:

/* Album Title */
- (NSString *) albumTitle {
  @synchronized(self) {
    if(albumTitle == nil) {
      [self.lock lock];
      NSDictionary *appInfo = [self.cache objectForKey:self.pID];
      [self.lock unlock];
    }
    if(appInfo == nil) {
      loadedFromDisk = YES;

      albumTitle = [[self.representativeItem valueForProperty: MPMediaItemPropertyAlbumTitle] retain];
      artist = [[self.representativeItem valueForProperty: MPMediaItemPropertyArtist] retain];

      [self.lock lock];
      [self.cache setObject:[NSDictionary dictionaryWithObjectsAndKeys: albumTitle, @"Title", artist, @"Artist", nil] forKey:self.persistentID];
      [self.lock unlock];
    } else
      albumTitle = [appInfo objectForKey:@"Title"];

  }

  return albumTitle;
  }
}

All of the wrappers share the same lock and cache, so nothing has to unnecessarily access disk. Additionally, I’ve discovered that once you access one of the non-pID properties the others come for free, so I cache both the artists and the album title at the same time. The getter for the artist looks very much the same, so the cache gets created from whichever loads first.

The other huge benefit to having a wrapper is the ability to have that albumTitle selector for sorting purposes. Apple now gives us a UILocalizedIndexCollation class to help us create a table index, but the only way to use it is if the object being sorted has a single selector from which it can be sorted.

I don’t, however, use albumTitle. I instead use this:

/* Sortable Album Title */
- (NSString *) sortableAlbumTitle {
    @synchronized(self) {
    if(sortableAlbumTitle == nil)
        sortableAlbumTitle = [[self stringByRemovingLeadingTheFromString:self.albumTitle] retain];
    return sortableAlbumTitle;
    }
}

Pass that along to your collation and you can easily index all of your MPMediaItemCollections in your table in the same way Apple does (without the leading “The”).

That’s all for now. I hope this ends up helping someone else who ends up trying to recreate swaths of the iPod app in their own application.


Integrating the iPod in OS 3.0

September 1st, 2009 by eddie

One of the OS 3.0 upgrades we were psyched about was iPod integration. I’ve been playing around with it a lot for several of our upcoming projects, and I have some comments that might help people doing the same.

When you use the iPod app, every tab looks amazing and scrolls smoothly. When you first setup your own music browser, however, you’ll see serious lagging and bad scrolling speeds in your UITableView.

The reason for this is that, even though you might have an MPMediaItemCollection, you don’t actually have the MPMediaItems in memory. So when you query a bunch for MPMediaItemPropertyArtist, for example, you’re going to see serious lagging and it loads that data into memory.

Apple, I’m guessing, prefetches and caches everything, so if you want to see smooth scrolling you should probably do the same. Once you load an MPMediaItem into memory once it stays there and every subsequent request to it is lightening fast.

I today came up with an easy prefetch that rests somewhere between hacky and elegant. I put something like this off the main thread after launch.


MPMediaItem *rItem;
NSString *pID, *title, *artist;

for(int i = 0; i < [query.collections count]; i++) {
  rItem = [[query.collections objectAtIndex:i] representativeItem];
  pID = [rItem valueForProperty:MPMediaItemPropertyPersistentID];
  title = [rItem valueForProperty:MPMediaItemPropertyAlbumTitle];
  artist = [rItem valueForProperty:MPMediaItemPropertyArtist];
}

Note that you're not actually storing anything, just calling the item up into memory to query it. What's nice about this solution is that, if your prefetch gets to an item first, your table's request for that information will go lightening fast and thus provide smooth scrolling. If, however, your user gets there first, then the table will lag a little (which is unavoidable) but when the prefetch gets there it won't waste time pulling it into memory again.


NSNotification Troubles

July 18th, 2009 by eddie

We’re big fans of loose coupling and so make use of NSNotifications.

In Shotgun Free / Pro, for example, the achievements class listens for a notification to know when the shotgun has cocked, notes the time, listens for a notification to know when the shotgun has fired, notes the time, and then calculates the fire time as the difference between those two times.

This method was really simple to implement because the achievements class can exist essentially on its own, just listening and reacting without talking to any other classes. Sounds good, right?

Wrong. Unfortunately there are no guarantees on when those notifications are delivered. Everything works fine 99.9% of the time, but then once in a thousand fires the cocking notification won’t be delivered until just before the fire notification, giving you an absurdly fast fire time that sits around on your high score board forever. Oops.


WiFi Edge Case

April 25th, 2009 by eddie

James and I discovered a strange edge case a few days ago that I figured I’d blog about. The long story short is that the iPhone will auto-join hotspots with pay portals, such as at Starbucks, Kinkos, or the airport. The problem is that the iPhone thinks the network connection is active, but it can never get valid data back from the server.

One effect of this you may have noticed is that when you go to get your email in the airport, the spinner just goes indefinitely. This has gotten me a few times, and I have to remember to turn WiFi off.

More importantly, if your program doesn’t handle this effectively, bad things can happen. For an example, with the MobClix ad library you temporarily see the login screen from the ad bar and then after twenty seconds the app crashes. Whoops.

In any case, be careful.


UIScrollView Paging Hack

April 5th, 2009 by james

The paging property in UIScrollViews is pretty handy: for those unfamiliar, it’s the great snapping feature when leafing through images in your photo collection, for example. One unfortunate limitation is that the UIScrollView, as provided, only lets you snap on multiples of the frame size. We wanted to lay out smaller previews of images so you could see the previous and next images as well, something like this:
scroll1.png
It’s easy enough to lay out the images, but if you turn paging on then you end up snapping on every other item. The solution we use is to move our content within the scrollview so that it lines up properly when snapped, like so:
scroll2.png
This method is very simple to handle in the code: just put all the subviews into a container, set yourself as the scrollview delegate, and use the scrollViewDidScroll: callback method to set the CGAffineTransformTranslation of your container view to half the current content offset of the scrollview.

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
  CGFloat offset = scrollView.contentOffset.x/2;
  CGAffineTransform transform = CGAffineTransformMakeTranslation(offset, 0);
  [contentView setTransform:transform];
}


The view no longer scrolls one-to-one with your finger, but flicking works quite well.