iOS Recipes - Matt Drance [42]
The ‑installShadows method invoked by ‑commonInit initializes the four shadow views to use one of two shadow images. Both images are 1 pixel wide and safely stretchable to the left or right without any modifications. Each shadow view is then set up by the ‑installShadow: method for use in our table view. This step makes the shadows adaptable to any screen—iPhone, iPad, or whatever else comes along in the future.
ShadowedTables/Classes/PRPShadowedTableView.m
UIImage *upShadow = [UIImage imageNamed:@"shadowUp.png"];
UIImage *downShadow = [UIImage imageNamed:@"shadowDown.png"];
ShadowedTables/Classes/PRPShadowedTableView.m
- (void)installShadow:(UIImageView *)shadowView {
shadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
CGRect contentFrame = shadowView.frame;
contentFrame.size.width = self.frame.size.width;
shadowView.frame = contentFrame;
[self repositionShadow:shadowView];
}
Now that the shadow subviews are installed, we can work on positioning them. The easiest shadow to manage is the shadow above the top of the table’s content. This shadow should freely move with the rest of the content but stay above the top while not actually affecting the content size. We accomplish this by setting a negative Y origin on the shadow. Since this never changes, we need to do it only once, so it’s done early on from ‑installShadows.
ShadowedTables/Classes/PRPShadowedTableView.m
if (contentTopShadow == nil) {
contentTopShadow = [[UIImageView alloc] initWithImage:upShadow];
[self installShadow:contentTopShadow];
CGRect topShadowFrame = contentTopShadow.frame;
topShadowFrame.origin.y = -topShadowFrame.size.height;
contentTopShadow.frame = topShadowFrame;
}
The fixed shadows at the top and bottom of the table view require some more work. When the user scrolls a scroll view or table view, all of the subviews move accordingly, unless we do something special in ‑layoutSubviews. Our ‑layoutSubviews implementation first passes the message on to super to preserve the default UITableView behavior and then sends ‑updateShadows to adjust the other three shadow views as needed.
ShadowedTables/Classes/PRPShadowedTableView.m
- (void)layoutSubviews {
[super layoutSubviews];
[self updateShadows];
}
First we update the fixed shadow at the top of the table. Because this subview would normally scroll with the rest of the content, we need to actively reposition it based on the scroll offset. We add an optimization to change the top shadow’s position only if it’s visible—that is, if the table’s content offset is negative. A negative contentOffset.y value means we are pulling down on the table past the top edge.
ShadowedTables/Classes/PRPShadowedTableView.m
BOOL topShowing = (self.contentOffset.y < 0);
if (topShowing) {
CGRect topFrame = self.topShadow.frame;
topFrame.origin.y = self.contentOffset.y;
self.topShadow.frame = topFrame;
[self repositionShadow:self.topShadow];
self.topShadow.hidden = NO;
[self repositionShadow:self.contentTopShadow];
self.contentTopShadow.hidden = NO;
} else {
self.topShadow.hidden = YES;
self.contentTopShadow.hidden = YES;
}
The next step, adjusting the bottom shadows, is a little trickier. Because table views receive ‑layoutSubviews so frequently, we only want to bother adjusting the shadows if they’re showing. How do we know if the bottom shadows are exposed? We need to find out where the bottom of the table content is. “That’s easy,” you might be thinking. “Just get the last cell in the last section and get its frame; if it’s nil, then the bottom clearly isn’t showing.” But what if the last section has no rows? What if we have twenty sections and the last three sections are empty? We could iterate backward until we find the row that is definitively last in the table, but doing this inside every call to ‑layoutSubviews is excessive.
OK, so using the “last cell” may not be reliable. What about the table’s contentSize? If the table runs off the screen,