Using UIStackView with MarkupKit

In iOS 9, Apple introduced a new means of implementing automatic layout management (or "autolayout") in iOS applications: the UIStackView class. This UIView subclass arranges its subviews in either a horizontal or vertical line, optionally resizing or redistributing them as needed in response to device size or orientation changes.

In earlier versions of iOS, view layout had to be performed either manually or via layout constraints, neither of which was particularly convenient. UIStackView significantly simplifies this process. Internally, it uses layout constraints to manage views’ size and position, hiding the details of constraint management and absolving developers of the responsibility of working with them directly.

As of iOS 9, MarkupKit includes support for creating and configuring UIStackView instances in markup. For example, the following document creates a vertical stack view containing three labels:

<UIStackView axis="vertical" alignment="center" spacing="8">
    <UILabel text="One"/>
    <UILabel text="Two"/>
    <UILabel text="Three"/>
    <UIView/>
</UIStackView>

The labels will be horizontally centered within the stack view and will be separated by 8 pixels of vertical space. The empty trailing UIView instance is used to create flexible padding space between the final UILabel and the bottom of the container view, ensuring that the stack view’s content is pinned to the top of the view.

This markup produces the following output on an iPhone 6 running iOS 9:

Stack views can be nested to produce more complex layouts. For example, the following markup creates a vertical stack view containing two horizontal stack views. These, in turn, contain three labels and three image views, respectively. Empty views are also used in this example to create flexible padding space:

<UIStackView axis="vertical" spacing="8">
    <UIStackView axis="horizontal" spacing="8" layoutMargins="12" layoutMarginsRelativeArrangement="true">
        <UILabel text="One"/>
        <UILabel text="Two"/>
        <UILabel text="Three"/>
        <UIView/>
    </UIStackView>

    <UIStackView axis="horizontal" spacing="8" layoutMargins="12" layoutMarginsRelativeArrangement="true">
        <UIImageView image="vw.png"/>
        <UIImageView image="mini.png"/>
        <UIImageView image="chevy.png"/>
        <UIView/>
    </UIStackView>

    <UIView/>
</UIStackView>

The "layoutMargins" attribute is used to create a 12-pixel gap around the horizontal stack views’ content. Since stack views are configured to ignore layout margins by default, the layoutMarginsRelativeArrangement property is set to true to instruct the stack views to respect the specified margin.

This markup produces the following output:

Even though these examples are somewhat trivial, they offer an effective demonstration of UIStackView's potential. Many common layout scenarios can be implemented simply by applying various combinations of nested stack views.

Of course, similar layout behavior can be achieved using MarkupKit’s LMRowView and LMColumnView classes. Like UIStackView, these views use layout constraints internally, hiding the often awkward details of constraint management from developers.

The following markup using row and column views produces output identical to the previous stack view-based example:

<LMColumnView>
    <LMRowView layoutMargins="12">
        <UILabel text="One"/>
        <UILabel text="Two"/>
        <UILabel text="Three"/>
        <LMSpacer/>
    </LMRowView>

    <LMRowView layoutMargins="12">
        <UIImageView image="vw.png"/>
        <UIImageView image="mini.png"/>
        <UIImageView image="chevy.png"/>
        <LMSpacer/>
    </LMRowView>

    <LMSpacer/>
</LMColumnView>

Note that, since LMRowView and LMColumnView are configured to respect layout margins by default, it is not necessary to explicitly set the layoutMarginsRelativeArrangement property. Additionally, row and column views use a default spacing of 8 pixels, so setting the spacing property is also unnecessary.

Also note that, in this example, LMSpacer instances are used to create the flexible space between the final views and the edge of the parent container. LMSpacer is an extremely simple UIView subclass whose sole purpose is to provide such spacing. It overrides UIView's hitTest:withEvent: method to return nil to ensure that it does not prevent touch events from reaching underlying views. LMSpacer can also be used with UIStackView.

MarkupKit’s layout views also offer some additional features not provided by UIStackView. For example, since it is a "non-rendering subclass" of UIView, UIStackView does not allow a caller to set a background color. However, background colors are supported by LMRowView and LMColumnView. This is an important consideration when creating custom view controllers. Because a UIStackView cannot have a background color, pushing a stack view-based controller onto a UINavigationController can create some rendering issues:

A row or column view-based controller with a background color animates smoothly:

Additionally, LMRowView and LMColumnView provide an option to distribute subviews by weight, something that does not currently seem to be possible with UIStackView. Weight-based distribution allows a caller to specify exactly what percentage of the available space within a container should be allocated to a particular subview.

For example, the following markup creates a column view containing two row views. The first row view is assigned a weight of 1 and the second a weight of 2, totaling 3. As a result, the first row will be given 1/3, or about 33% of the available space in the column, and the second row will be given 2/3, or about 66%, of the available space:

<LMColumnView>
    <LMRowView weight="1" backgroundColor="#ffcccc">
        <LMSpacer/>
        <UILabel text="One"/>
        <UILabel text="Two"/>
        <UILabel text="Three"/>
        <LMSpacer/>
    </LMRowView>

    <LMRowView weight="2" backgroundColor="#ccffcc">
        <LMSpacer/>
        <UILabel text="Four"/>
        <UILabel text="Five"/>
        <UILabel text="Six"/>
        <LMSpacer/>
    </LMRowView>
</LMColumnView>

The output of this markup when run in portait mode looks like this:

In landscape, it looks like this:

Finally, UIStackView requires a device to be running iOS 9 or later. UIRowView and UIColumnView are supported on iOS 8 and above.

On the other hand, UIStackView provides some capabilities that are not supported by LMRowView or LMColumnView. For example, it offers several additional distribution options as well as the ability to animate changes to its content and properties. See the UIStackView class documentation for more information on these features.

So, when should you use one or the other? Both view types can be used in the same application as well as within the same view hierarchy. If you need the ability to specify a background color on your container, want to distribute subviews by weight, or need to support iOS 8, use a row or column view. If you need to animate layout changes or want to distribute subviews by other means and you are targeting iOS 9+ exclusively, use a stack view.

By combining the capabilities of UIStackView with those of LMRowView and LMColumnView, you can quickly and easily create complex user interfaces that will automatically adapt to size and orientation changes, providing a much more responsive and engaging interface for your users.

For more information, please see the project documentation.

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