Over a million developers have joined DZone.

Building Responsive iOS Applications Using Markup

A tutorial on using the UIStackView class in iOS 9 to build responsive mobile applications.

· Mobile Zone

Working with auto layout in iOS has historically been difficult. Initially, the platform provided no support for auto layout at all, requiring developers to either hard-code view sizes and positions or write a lot of complex manual layout code. Layout constraints, introduced in iOS 6, were a step in the right direction, but were still cumbersome to use.

With the introduction of the UIStackView class in iOS 9, it seemed as though Apple finally "got it". Using stack views, it is possible to create complex view hierarchies that automatically adjust to device size and orientation changes without needing to resort to manual size calculations or constraint gymnastics.

Unfortunately, although UIStackView allows developers to implement auto layout-driven, or "responsive", applications more easily than ever before, it is still not quite as convenient as it could be. As with other view types, stack views must be created either interactively using storyboards or programmatically in code. Other platforms, including Android and Windows, allow developers to create user interfaces declaratively using an XML-based markup language that parallels the view hiearchy of the application. Building an interface in markup makes it easy to visualize the resulting output as well as recognize differences between revisions. It is also a metaphor that many developers are comfortable with, thanks to the ubiquity of HTML and the World Wide Web.

MarkupKit is an open-source project that brings this capability to iOS. Using MarkupKit, developers can declare the structure of a user interface rather than using a GUI builder or writing the UI code by hand. For example, the following markup creates an instance of UILabel and sets the value of its text property to "Hello, World!":

<UILabel text="Hello, World!"/>

The output produced by this markup is identical to the output of the following Swift code:

let label = UILabel()
label.text = "Hello, World!"

Simple Stack View Example

Apple's developer documentation includes a collection of tutorials that illustrate how to incorporate auto layout into an iOS application. Two of these examples focus on UIStackView. The first one, called "Simple Stack View", demonstrates the use of a vertical stack view to automatically arrange a label, image view, and button:

Image title

In Apple's example, the developer is instructed to create a storyboard containing the user interface elements using Interface Builder. The same results can be achieved in markup as follows:

<LMLayerView backgroundColor="#ffffff">
    <UIStackView axis="vertical" layoutMarginsRelativeArrangement="true" layoutMarginTop="20" layoutMarginBottom="20" spacing="8">
        <UILabel text="Flowers" font="body" textAlignment="center"/>

        <UIImageView image="flowers" contentMode="scaleAspectFit"
            horizontalContentHuggingPriority="250"
            verticalContentHuggingPriority="249"
            horizontalContentCompressionResistancePriority="750"
            verticalContentCompressionResistancePriority="749"/>

        <UIButton style="systemButton" normalTitle="Edit"/>
    </UIStackView>
</LMLayerView>

The following simple controller code is used to load the markup and provide navigation to the next example:

import UIKit
import MarkupKit

class SimpleStackViewController: UIViewController {
    override func loadView() {
        view = LMViewBuilder.viewWithName("SimpleStackView", owner: self, root: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Simple Stack View"

        navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain,
            target: nil, action: nil)
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: UIBarButtonItemStyle.Plain,
            target: self, action: "next")

        edgesForExtendedLayout = UIRectEdge.None
    }

    func next() {
        navigationController!.pushViewController(NestedStackViewController(), animated: true)
    }
}

The markup version uses an instance of LMLayerView as the root element, since this class will automatically create constraints to pin its children to its own bounds. Additionally, the layoutMargins property of the stack view is used to create the vertical gap at the top and bottom of the view instead of creating explicit offset constraints. Otherwise, the example is identical to the Apple version.

Nested Stack Views

Apple's second stack view example, "Nested Stack Views", demonstrates the use of multiple stack views in varying orientations to create a more complex layout:

Image title

Since stack views alone cannot be used to create the layout, several additional constraints are created to maintain a fixed aspect ratio for the image view and to ensure that the form fields are vertically aligned.

The markup for creating the nested stack view example is as follows:

<LMLayerView backgroundColor="#ffffff">
    <!-- Root Stack -->
    <UIStackView axis="vertical" layoutMarginsRelativeArrangement="true" layoutMarginTop="20" layoutMarginBottom="20" spacing="8">
        <!-- Upper Stack -->
        <UIStackView axis="horizontal" spacing="8">
            <!-- Image View -->
            <UIImageView id="imageView" image="square_flowers"
                horizontalContentCompressionResistancePriority="48"
                verticalContentCompressionResistancePriority="48"/>

            <!-- Name Rows Stack -->
            <UIStackView axis="vertical" spacing="8">
                <!-- First Name Stack -->
                <UIStackView axis="horizontal" alignment="firstBaseline" spacing="8">
                    <UILabel text="First" font="body"
                        horizontalContentHuggingPriority="251"
                        verticalContentHuggingPriority="251"/>
                    <UITextField id="firstNameTextField" placeholder="Enter First Name" font="System 14" borderStyle="roundedRect"
                        horizontalContentHuggingPriority="48"
                        horizontalContentCompressionResistancePriority="749"/>
                </UIStackView>

                <!-- Middle Name Stack -->
                <UIStackView axis="horizontal" alignment="firstBaseline" spacing="8">
                    <UILabel text="Middle" font="body"
                        horizontalContentHuggingPriority="251"
                        verticalContentHuggingPriority="251"/>
                    <UITextField id="middleNameTextField" placeholder="Enter Middle Name" font="System 14" borderStyle="roundedRect"
                        horizontalContentHuggingPriority="48"
                        horizontalContentCompressionResistancePriority="749"/>
                </UIStackView>

                <!-- Last Name Stack -->
                <UIStackView axis="horizontal" alignment="firstBaseline" spacing="8">
                    <UILabel text="Last" font="body"
                        horizontalContentHuggingPriority="251"
                        verticalContentHuggingPriority="251"/>
                    <UITextField id="lastNameTextField" placeholder="Enter Last Name" font="System 14" borderStyle="roundedRect"
                        horizontalContentHuggingPriority="48"
                        horizontalContentCompressionResistancePriority="749"/>
                </UIStackView>
            </UIStackView>
        </UIStackView>

        <!-- Text View -->
        <UITextView text="Notes:" font="body" backgroundColor="#aaaaaa"
            verticalContentHuggingPriority="249"/>

        <!-- Button Stack -->
        <UIStackView axis="horizontal" alignment="firstBaseline" distribution="fillEqually" spacing="8">
            <UIButton style="systemButton" normalTitle="Save"/>
            <UIButton style="systemButton" normalTitle="Cancel"/>
            <UIButton style="systemButton" normalTitle="Clear"/>
        </UIStackView>
    </UIStackView>
</LMLayerView>

Again, an LMLayerView is used as the root element and the top/bottom gap is created using the layoutMargins property of the root stack view, but otherwise the content of the markup example is identical to the storyboard version. The additional constraints are created by the view controller when the view is loaded, as shown below:

import UIKit
import MarkupKit

class NestedStackViewController: UIViewController {
    var imageView: UIImageView!

    var firstNameTextField: UITextField!
    var middleNameTextField: UITextField!
    var lastNameTextField: UITextField!

    override func loadView() {
        view = LMViewBuilder.viewWithName("NestedStackView", owner: self, root: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Nested Stack Views"

        navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain,
            target: nil, action: nil)
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: UIBarButtonItemStyle.Plain,
            target: self, action: "next")

        edgesForExtendedLayout = UIRectEdge.None

        // Create custom constraints
        NSLayoutConstraint.activateConstraints([
            // Image view aspect ratio
            NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.Equal, toItem: imageView, attribute: NSLayoutAttribute.Height,
                multiplier: 1.0, constant: 0),

            // Equal text field widths
            NSLayoutConstraint(item: middleNameTextField, attribute: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.Equal, toItem: firstNameTextField, attribute: NSLayoutAttribute.Width,
                multiplier: 1.0, constant: 0),
            NSLayoutConstraint(item: lastNameTextField, attribute: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.Equal, toItem: middleNameTextField, attribute: NSLayoutAttribute.Width,
                multiplier: 1.0, constant: 0)
        ])
    }

    func next() {
        ...
    }
}

Summary

This article provided an introduction to how iOS application development can be simplified using markup. In addition to auto layout, MarkupKit also provides a number of other features for simplifying app development including CSS-like styling and dynamic localization. For more information, please visit the MarkupKit project on GitHub. Additional code samples can be found in the project wiki.

Topics:
ios ,xml ,markup ,markupkit ,swift ,auto layout ,mobile

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}