iOS Recipes - Matt Drance [23]
One option is to use a scroll view containing a very large view. If the view being scrolled is large enough, it will seem as if there are no boundaries at all. However, filling a huge view with enough data to give the impression of wrapping poses a problem with memory usage. When you write code for mobile devices, you have the constant need to preserve memory. Even with newer devices that have huge amounts of physical memory, multitasking requires you to consider your app footprint even when it’s inactive.
What you need is a solution that instantiates a very large view and yet uses minimal memory—not quite as impossible as it sounds thanks to CATiledLayer, the class underlying the mapping APIs. Think of the Maps app as having the exact features you are looking for: seemingly endless scrolling and with the view filled with images on demand (see Figure 15, Example of wall of album art ).
The CATiledLayer class breaks up its contents into tiles of fixed size. As one of these tiles scrolls onto the display, it calls the drawRect method of the associated view with the rect parameter set to the size of the image to be drawn. This means that only the tiled areas that are currently visible, or about to be visible, need to be drawn, saving processing time and memory.
We are now a step closer to creating the continuous wrapping effect we are after. Because each tile is drawn in our drawRect method, we can control the image it contains. With a little math we can ensure that when we reach the end of the list of available images we simply start again with the first.
Figure 15. Example of wall of album art
* * *
In this recipe we use a rich source of graphics data that is often overlooked: the iPod library. The only disadvantage is that the Xcode simulator does not give us access to the library, which means we need a little extra code to avoid an access error and to display an alternate image.
The MainViewController class contains the initialization code for the scroll view and an instance of our PRPTiledView class. The scroll view is our window into the tiled album view, so it just needs a frame no larger than the device window. Its contentsize, on the other hand, must be set to the size of the album view—in this case, a very large rect.
We want to steer clear of UIScrollViewDecelerationRateNormal—the default decelerationRate for a scroll view. While providing smooth, fast scrolling, it would cause a visible delay in the appearance of the album art, because the images would need to be constantly refreshed. By using UIScrollViewDecelerationRateFast instead, we can keep the scroll speed in check and ultimately provide a better user experience.
As cool as it is to have a huge virtual view, it would be completely pointless if the view started at the top-left corner, the default, because we would hit an edge almost immediately. So, we need to set the contentOffset property, our current distance from the top-left corner, to the center point of the view. With that set, we could literally scroll around for hours and still not hit a real edge. As with the contentsize, we need to set the frame size of the tiles view to the same very large rect.
InfiniteImages/MainViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
width = self.view.bounds.size.width;
height = self.view.bounds.size.height;
CGRect frameRect = CGRectMake(0, 0, width, height);
UIScrollView *infScroller = [[UIScrollView alloc]
initWithFrame:frameRect];
infScroller.contentSize = CGSizeMake(BIG, BIG);
infScroller.delegate = self;
infScroller.contentOffset = CGPointMake(BIG/2, BIG/2);
infScroller.backgroundColor = [UIColor blackColor];
infScroller.showsHorizontalScrollIndicator = NO;
infScroller.showsVerticalScrollIndicator = NO;
infScroller.decelerationRate = UIScrollViewDecelerationRateFast;
[self.view addSubview:infScroller];