iOS Recipes - Matt Drance [40]
header.backgroundColor = [self demoBackgroundColor];
[header release];
}
This looks great—until we pull down on the table. That smooth gradient ends abruptly, and the lighter table background reappears above it. How do we make that darker color persist at the top while keeping the lighter color everywhere else? A table footer view won’t help for the same reason a header didn’t. We need to get a little more creative than that.
A UITableView subclass called PRPTwoToneTableView solves this problem. It declares topColor and bottomColor properties to be set by the caller—our view controller, for example. Using these top and bottom properties, plus the backgroundColor property, we can even make a three-tone table view if we want.
TwoToneTables/Classes/PRPTwoToneTableView.h
@interface PRPTwoToneTableView : UITableView {}
@property (nonatomic, retain) UIColor *topColor;
@property (nonatomic, retain) UIColor *bottomColor;
@end
These “colors” are really two “stretcher” subviews that our table adds automatically. These views let us customize the edge colors while still using our own table header and footer views. Note that we’re still using the gradient view for a table header just as we were before. We declare them as properties in a private class extension and tie them to the public color properties in custom setter methods.
TwoToneTables/Classes/PRPTwoToneTableView.m
@interface PRPTwoToneTableView ()
@property (nonatomic, retain) UIView *topStretcher;
@property (nonatomic, retain) UIView *bottomStretcher;
@end
TwoToneTables/Classes/PRPTwoToneTableView.m
- (void)setTopColor:(UIColor *)color {
if (self.topStretcher == nil) {
topStretcher = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:self.topStretcher];
}
if (self.topStretcher.backgroundColor != color) {
self.topStretcher.backgroundColor = color;
}
}
How do these new subviews solve the problem? How do they not interfere with normal table view behavior? The answer lies in the ‑layoutSubviews method, which any UIView subclass can implement. UITableView already does a great deal of work in this method, and PRPTwoToneTableView simply builds on that.
Our custom ‑layoutSubviews method simulates a different “background” color on either end of the table by stretching the new subviews to fill the space left when scrolling past either end. We adjust the appropriate stretcher view according to the table’s current contentOffset—think “scroll point”—and we’re done. This works because each incremental scroll affects a scroll or table view’s bounds, which produces a ‑layoutSubviews message for our table.
First things first: when overriding layoutSubviews on a class like UITableView, don’t forget to call [super layoutSubviews] before doing your own work. If you prevent UITableView from performing its own layout operations, you won’t have much of a table view: comment out the message to super in -[PRPTwoToneTableView layoutSubviews], and you’ll notice that none of the cells appears.
When we pull down on a table that’s already at the top, the table’s contentOffset.y value becomes negative. We check for this when deciding to show and adjust our top stretcher to fill the gap. This produces the top “background” color.
TwoToneTables/Classes/PRPTwoToneTableView.m
- (void)layoutSubviews {
[super layoutSubviews];
if (self.topStretcher) {
if (self.contentOffset.y > 0) {
self.topStretcher.hidden = YES;
} else {
self.topStretcher.frame = CGRectMake(0, self.contentOffset.y,
self.frame.size.width,
-self.contentOffset.y);
self.topStretcher.hidden = NO;
}
}
We handle the bottom stretcher in a similar fashion, but it’s a little more complicated. The bottom of the view is not exactly a fixed value like the top is, so first we have to find out whether the bottom is on-screen. From there, we show the bottom stretcher if appropriate and adjust it to fill the gap.
TwoToneTables/Classes/PRPTwoToneTableView.m
CGFloat contentBottom = (self.contentSize.height - self.contentOffset.y);
CGFloat bottomGap = self.frame.size.height