Online Book Reader

Home Category

iOS Recipes - Matt Drance [43]

By Root 255 0
contentSize.y is a valid metric. But it turns out that, depending on a table’s contents, its contentSize may be the height of the table itself—even if the actual content is much smaller. So if we have a 460-pixel-high table with a search bar and a single 44-pixel row, contentSize.y could be reported as 460, not 44 as we might expect. See Figure 24, Determining a table’s content height to understand the problem at hand.

A table view’s content height is always at least the height of the view itself. This table’s contentSize.height is not 309 pixels as you might expect but rather 416. This makes determining the bottom shadow placement a little more difficult.

Figure 24. Determining a table’s content height

* * *

It turns out UIKit already performs this measurement for us when it positions the table footer. If we have a table footer, we can just query its frame to find out the table’s bottom Y coordinate.

What if we don’t have a table footer? Easy: we insert one by overriding the ‑tableFooterView getter as a lazy initializer. If a footer is already installed, we just use that by messaging the superclass. If a footer is not installed, we insert a hidden, zero-height view as the footer. The setter is unchanged, so our view controller can replace the placeholder with a custom footer at any time. This gives us a dependable reference for the table’s proper content height under any circumstances. If we need the placeholder, it’s created only once, and not until the first ‑layoutSubviews message is received. This gives the calling code a chance to set a custom footer before the placeholder is created unnecessarily. Setting a footer also prevents placeholder separator lines from being drawn to the end of the view. You can see the effects in Figure 25, Table footers to the rescue .

The presence of a table footer gives us reliable information on the end of a table’s content so we know where to place our shadow. It also eliminates the “filler” separator lines drawn by plain table views.

Figure 25. Table footers to the rescue

* * *

ShadowedTables/Classes/PRPShadowedTableView.m

- (UIView *)tableFooterView {

UIView *footer = [super tableFooterView];

if (footer == nil) {

if (self.placeholderFooter == nil) {

CGRect footerFrame = self.frame;

footerFrame.size.height = 0;

placeholderFooter = [[UIView alloc] initWithFrame:footerFrame];

}

self.placeholderFooter.hidden = YES;

footer = self.tableFooterView = self.placeholderFooter;

}

return footer;

}

Once we know where the bottom of the table is, we decide whether to show or hide the bottom shadows and place them according to the gap between the table’s static bottom and the bottom of the table’s content.


Editable Tables

The table in this recipe is intended for read-only table views. Adding or deleting rows, either programmatically or in response to user-editing actions, is not addressed by the recipe. Properly handling edits requires you to anticipate the “new bottom” of the table, which adds a significant amount of complexity as this chapter suggests. We made a conscious decision for this book to keep the code simple and clean by only solving the noneditable case, which is still very common.

ShadowedTables/Classes/PRPShadowedTableView.m

CGFloat footerMaxY = CGRectGetMaxY(self.tableFooterView.frame);

CGFloat bottomY = footerMaxY - self.contentOffset.y;

BOOL bottomShowing = (bottomY < self.frame.size.height);

if (bottomShowing) {

CGFloat tableBottom = CGRectGetMaxY(self.frame);

CGRect bottomFrame = self.bottomShadow.frame;

CGFloat yOffset = (bottomFrame.size.height - self.contentOffset.y);

CGFloat bottomY = tableBottom - yOffset;

bottomFrame.origin.y = bottomY;

self.bottomShadow.frame = bottomFrame;

[self repositionShadow:self.bottomShadow];

self.bottomShadow.hidden = NO;

CGRect cbFrame = self.contentBottomShadow.frame;

cbFrame.origin.y = footerMaxY;

self.contentBottomShadow.frame = cbFrame;

[self repositionShadow:self.contentBottomShadow];

self.contentBottomShadow.hidden = NO;

} else {

self.bottomShadow.hidden = YES;

self.contentBottomShadow.hidden

Return Main Page Previous Page Next Page

®Online Book Reader