Using a Static UITableView as a Layout Device: Take Two

While layout constraints and the UIStackView class have made it much easier to create applications that automatically respond to device size and orientation changes, another option for implementing autolayout sometimes gets overlooked: UITableView. Since iOS 8, table views have supported "self-sizing" cells, allowing developers to create complex table-based user interfaces without needing to compute cell heights manually.

As table views often make up a large part of an iOS application's user interface, taking advantage of self-sizing cells can significantly reduce overall development time. Additionally, this capabilitity extends to custom cells – as long as the cell defines constraints that unambiguously define the position of each subview, the cell's contents will be automatically laid out as needed by the table view. No manual size calculation or layout code is required.

A good example of using self-sizing table view cells can be found here. This example, written by Oliver Foggin, uses two table views. The first table view presents a list of animals to the user. Tapping a row in this view presents the second table view, which displays detailed information about the selected animal, including name, Latin name, image, and description, as well an author's name and image.

The detail view uses self-sizing static table view cells along with layout constraints to automatically arrange its contents. The structure of the detail view is defined in a storyboard. I was inspired to try to replicate this layout using MarkupKit. The results are shown below:

(after scrolling)

The markup used to create the layout (defined in a file I named AnimalDetailTableView.xml) is as follows:

<?xml version="1.0" encoding="UTF-8"?>

<LMTableView separator backgroundColor="#ffffff">
    <!-- Name and Latin name -->
    <LMTableViewCell>
        <LMColumnView>
            <UILabel id="titleLabel" font="headline"/>
            <UILabel id="dateLabel" font="footnote"/>
        </LMColumnView>
    </LMTableViewCell>

    <!-- Photo -->
    <LMTableViewCell>
        <UIImageView id="articleImageView" height="200" contentMode="scaleAspectFit"/>
    </LMTableViewCell>

    <!-- Text -->
    <LMTableViewCell>
        <UILabel id="articleTextView" numberOfLines="0"/>
    </LMTableViewCell>

    <!-- Author details -->
    <LMTableViewCell>
        <LMColumnView>
            <LMRowView layoutMarginTop="10" layoutMarginLeft="40" layoutMarginBottom="15" layoutMarginRight="40">
                <UIView height="2" backgroundColor="#666666"/>
            </LMRowView>

            <UIImageView id="authorImageView" height="92" contentMode="scaleAspectFit"/>
            <UILabel id="authorNameLabel" font="footnote" textAlignment="center"/>
        </LMColumnView>
    </LMTableViewCell>
</LMTableView>

LMTableView is a subclass of UITableView that acts as its own data source and delegate, serving cells from a statically-defined collection of table view sections. LMTableViewCell is a subclass of UITableViewCell that provides a vehicle for custom cell content. It automatically applies constraints to its content to enable self-sizing behavior.

LMRowView and LMColumnView are layout views that arrange their subviews in a horizontal or vertical line, respectively. They are similar to UIStackView but have some unique properties, such as the ability to distribute subviews by weight as well as specify a background color. They also work in iOS 8. See this article for more information.

The first cell uses a column view to arrange the name and Latin name labels in a vertical line within the cell. The "id" attribute defines outlets for the labels, using the same outlet names that were used in the original storyboard ("titleLabel" and "dateLabel"). As in the original, the labels use the "headline" and "footnote" dynamic text styles:

<!-- Name and Latin name -->
<LMTableViewCell>
    <LMColumnView>
        <UILabel id="titleLabel" font="headline"/>
        <UILabel id="dateLabel" font="footnote"/>
    </LMColumnView>
</LMTableViewCell>

The second cell contains an image view named "articleImageView" that displays a picture of the animal. As in the original storyboard, the height of the image view is constrained to 200 pixels. Similarly, the third cell contains a label named "articleTextView" that displays a description of the animal:

<!-- Photo -->
<LMTableViewCell>
    <UIImageView id="articleImageView" height="200" contentMode="scaleAspectFit"/>
</LMTableViewCell>

<!-- Text -->
<LMTableViewCell>
    <UILabel id="articleTextView" numberOfLines="0"/>
</LMTableViewCell>

The last cell contains a column view that displays the author's name and picture. The picture's height is constrained to 92 pixels, and the name label uses the "footnote" style, as in the original. The column view also contains an empty UIView that is used to create the 2-pixel wide separator found in the original version. A row view is used to establish the margins around the separator view:

<!-- Author details -->
<LMTableViewCell>
    <LMColumnView>
        <LMRowView layoutMarginTop="10" layoutMarginLeft="40" layoutMarginBottom="15" layoutMarginRight="40">
            <UIView height="2" backgroundColor="#666666"/>
        </LMRowView>

        <UIImageView id="authorImageView" height="92" contentMode="scaleAspectFit"/>
        <UILabel id="authorNameLabel" font="footnote" textAlignment="center"/>
    </LMColumnView>
</LMTableViewCell>

Adding the following method to the AnimalDetailTableViewController class to load the view from markup was all I needed to replace the storyboard-based layout with the MarkupKit version:

- (void)loadView
{
    self.view = [LMViewBuilder viewWithName:@"AnimalDetailTableView" owner:self root:nil];
}

No other changes were required! In fact, the tableView:heightForRowAtIndexPath: method of AnimalDetailTableViewController and the detail view scene in the storyboard can actually be removed, since they are not needed by the markup version.

For more information about MarkupKit, including additional examples, see the project README.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s