MarkupKit 2.8 Released

MarkupKit 2.8 is now available for download. This release adds support for declarative data binding, facilitating development of MVVM-style applications using MarkupKit.

Using data binding, developers can eliminate a lot of the tedious boilerplate code that is typically required to manage the state of user interface elements explicitly. For example, the following markup binds the text property of a text field to the name property of the document’s owner:

<UITextField text="$name"/>

Any time the value of the owner’s name property changes, the new value will be automatically reflected in the text field’s text property, and vice versa.

For more information, see the following examples or the project README:

  • Custom Cell View – custom table view cell implemented in markup. Cell values are updated via data binding.

  • Form View – demonstrates how to create a simple form using auto layout. Data binding is used to manage the form values.

Simplifying Auto Layout in iOS

Auto layout is an iOS feature that allows developers to create applications that automatically adapt to device size, orientation, or content changes. An application built using auto layout generally has little or no hard-coded view positioning logic, but instead dynamically arranges user interface elements based on their preferred or "intrinsic" content sizes.

Auto layout in iOS is implemented primarily via layout constraints, which, while powerful, are not particularly convenient to work with. This article provides an overview of how constraints are typically managed in an iOS application, and then discusses some alternatives that can significantly simplify the task of working with auto layout.

Storyboards

The structure of an iOS user interface is commonly represented by XIB files or storyboards created using Xcode's Interface Builder utility. This tool allows developers to lay out an application's user interface visually using drag/drop and other interactive features.

For example, the following is a storyboard representing a simple view. The view contains two subviews whose positions will be automatically determined at runtime based on layout constraints. The constraints pin the subviews to the edges of the parent view as well as to each other, with a 16-pixel gap in between:

Running the application in portrait mode on an iPhone 7 produces the following results:

In lanscape mode, the application looks like this:

While storyboards are undoubtedly the most common way to define auto layout constraints, they are not necessarily the most efficient. Using Interface Builder to visually establish every relationship can be awkward, especially when working with large or complex view hierarchies. Further, storyboards are not stored in a human-readable text format, which makes it difficult to identify changes across revisions. Finally, although controller logic can be shared between projects, iOS storyboards cannot be used in a tvOS application. Separate storyboards must be created for each platform, resulting in a potentially significant duplication of effort.

Programmatically Defined Constraints

In addition to storyboards, constraints can also be managed programmatically. For example, the following Swift code produces the same results as the previous example:

func createConstraintBasedView() {
    // Red view
    let redView = UIView()

    redView.translatesAutoresizingMaskIntoConstraints = false
    redView.backgroundColor = UIColor(red: 202.0 / 255.0, green: 53.0 / 255.0, blue: 56.0 / 255.0, alpha: 1.0)

    view.addSubview(redView)

    // Blue view
    let blueView = UIView()

    blueView.translatesAutoresizingMaskIntoConstraints = false
    blueView.backgroundColor = UIColor(red: 59.0 / 255.0, green: 85.0 / 255.0, blue: 162.0 / 255.0, alpha: 1.0)

    view.addSubview(blueView)

    // Constraints
    NSLayoutConstraint.activate([
        NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal,
            toItem: topLayoutGuide, attribute: .bottom,
            multiplier: 1.0, constant: 0.0),
        NSLayoutConstraint(item: redView, attribute: .bottom, relatedBy: .equal,
            toItem: bottomLayoutGuide, attribute: .top,
            multiplier: 1.0, constant: 0.0),
        NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal,
            toItem: view, attribute: .leadingMargin,
            multiplier: 1.0, constant: 0.0),

        NSLayoutConstraint(item: blueView, attribute: .top, relatedBy: .equal,
            toItem: topLayoutGuide, attribute: .bottom,
            multiplier: 1.0, constant: 0.0),
        NSLayoutConstraint(item: blueView, attribute: .bottom, relatedBy: .equal,
            toItem: bottomLayoutGuide, attribute: .top,
            multiplier: 1.0, constant: 0.0),
        NSLayoutConstraint(item: blueView, attribute: .leading, relatedBy: .equal,
            toItem: redView, attribute: .trailing,
            multiplier: 1.0, constant: 16.0),
        NSLayoutConstraint(item: blueView, attribute: .trailing, relatedBy: .equal,
            toItem: view, attribute: .trailingMargin,
            multiplier: 1.0, constant: 0.0),

        NSLayoutConstraint(item: redView, attribute: .width, relatedBy: .equal,
            toItem: blueView, attribute: .width,
            multiplier: 1.0, constant: 0.0),
    ])
}

Because the constraints are established in code, it is easy to identify changes between revisions. Additionally, this version also works in tvOS:

Unfortunately, managing constraints programmatically is not particularly convenient. This simple layout required the definition of eight individual constraints. More complex layouts could quickly become untenable.

Stack Views

The UIStackView class, introduced in iOS 9, provides an alternative to managing constraints directly. Stack views automatically arrange their subviews in a vertical or horizontal line, and can be nested to create sophisticated layouts.

For example, the following code uses a stack view to produce results identical to the first two examples. It also works in both iOS and tvOS:

func createStackView() -> UIView {
    let view = UIStackView()

    view.isLayoutMarginsRelativeArrangement = true
    view.spacing = 16

    // Red view
    let redView = UIView()

    redView.backgroundColor = UIColor(red: 202.0 / 255.0, green: 53.0 / 255.0, blue: 56.0 / 255.0, alpha: 1.0)

    view.addArrangedSubview(redView)

    // Blue view
    let blueView = UIView()

    blueView.backgroundColor = UIColor(red: 59.0 / 255.0, green: 85.0 / 255.0, blue: 162.0 / 255.0, alpha: 1.0)

    view.addArrangedSubview(blueView)

    // Width constraint
    NSLayoutConstraint.activate([
        NSLayoutConstraint(item: redView, attribute: .width, relatedBy: .equal,
            toItem: blueView, attribute: .width,
            multiplier: 1.0, constant: 0.0),
    ])

    return view
} 

While it is arguably more readable than the previous version, this example is still somewhat verbose. It also still requires the explicit creation of a layout constraint to manage the width relationship, which is not ideal.

Layout Views

MarkupKit is an open-source framework for simplifying development of native iOS and tvOS applications. Among other things, it provides the following collection of view classes, whose sole responsibility is managing the size and position of their respective subviews:

  • LMRowView – arranges subviews in a horizontal line
  • LMColumnView – arranges subviews in a vertical line
  • LMLayerView – arranges subviews in layers, like a stack of transparencies
  • LMAnchorView – optionally anchors subviews to one or more edges

These classes use layout constraints internally, allowing developers to easily take advantage of auto layout while eliminating the need to manage constraints directly.

For example, the following code uses an instance of LMRowView to replicate the results produced by the previous examples. The weight property is used to ensure that the views are the same width. This property, which is added to UIView by MarkupKit, specifies the amount of excess space the view would like to be given within its parent view, relative to all other weights. Since both views are assigned a weight of 1, they will each be given 1 / (1 + 1), or one-half, of the available space:

func createRowViewProgrammatically() -> UIView {
    let view = LMRowView()

    view.spacing = 16

    // Red view
    let redView = UIView()

    redView.backgroundColor = LMViewBuilder.colorValue("#CA3538")
    redView.weight = 1.0

    view.addArrangedSubview(redView)

    // Blue view
    let blueView = UIView()

    blueView.backgroundColor = LMViewBuilder.colorValue("#3B55A2")
    blueView.weight = 1.0

    view.addArrangedSubview(blueView)

    return view
}

Like stack views, layout views are easy to work with programmatically, and are supported in both iOS and tvOS. However, unlike stack views, no manual constraint is manipulation required; MarkupKit uses the defined weight values to automatically establish the width relationship.

Additionally, the colorValue(_) method of MarkupKit's LMViewBuilder class is used to simplify color assignment in this example. The logic is still a bit verbose though, and could become difficult to manage as view complexity increases.

Markup

MarkupKit's namesake feature is its support for declarative view construction. Using markup, the view hiearchy created in the previous example can be represented entirely as follows:

<LMRowView spacing="16">
    <UIView backgroundColor="#ca3538" weight="1"/>
    <UIView backgroundColor="#3b55a2" weight="1"/>
</LMRowView>

The markup is loaded using the view(withName:owner:root:) method of LMViewBuilder. This method is similar to the loadNibNamed(_:owner:options:) method of the NSBundle class, and returns the root element of the view hiearchy declared in the document:

func createRowViewDeclaratively() -> UIView {
    return LMViewBuilder.view(withName: "ViewController", owner: self, root: nil)!
}

Like the previous version, this example works in both iOS and tvOS. However, unlike all of the preceding examples, this version is extremely concise. It is also much more readable: the element hierarchy declared in the document parallels the resulting view hiearchy, making it easy to understand the relationships between views.

For example, the periodic table shown below was constructed using a combination of MarkupKit-provided layout views and UILabel instances:

Creating this view in Interface Builder would be an arduous task. Creating it programmatically would be even more difficult. However, in markup it is almost trivial. The complete source code for this example can be found here.

Using markup can also help promote a clear separation of responsibility within an application. Most, if not all, aspects of a view's presentation can be specified in the view declaration, leaving the view controller responsible solely for managing the view's behavior.

Summary

This article provided an overview of how layout constraints are typically managed in an iOS application, and discussed some alternatives that can significantly simplify the task of working with auto layout, including stack views, layout views, and markup.

For more information, please see the MarkupKit README.

MarkupKit: Declarative UI for iOS

MarkupKit is an open-source framework for simplifying development of native iOS and tvOS applications. It allows developers to construct user interfaces declaratively using a human-readable, HTML-like markup language, rather than visually using Interface Builder or programmatically in code.

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!"

Building an interface in markup can significantly reduce development time. For example, the periodic table shown below was constructed using a combination of MarkupKit's layout views and UILabel instances:

Creating this view in Interface Builder would be an arduous task. Creating it programmatically would be even more difficult. However, in markup it is almost trivial. The complete source code for this example can be found here.

Using markup also helps to promote a clear separation of responsibility. Most, if not all, aspects of a view's presentation can be specified in the view declaration, leaving the controller responsible solely for managing the view's behavior.

This document introduces the MarkupKit framework and provides an overview of some of its key features, including property templates, outlets and actions, localization, and auto layout.

Document Structure

MarkupKit uses XML to define the structure of a user interface. The hierarchical nature of an XML document parallels the view hierarchy of an iOS application, making it easy to understand the relationships between views.

Elements

Elements in a MarkupKit document typically represent instances of UIView or its subclasses. As elements are read by the XML parser, the corresponding class instances are dynamically created and added to the view hierarchy.

For example, the following markup declares an instance of LMColumnView containing a UIImageView and a UILabel. LMColumnView is a MarkupKit-provided subclass of UIView that automatically arranges its subviews in a vertical line:

<LMColumnView>
    <UIImageView image="world.png" contentMode="center"/>
    <UILabel text="Hello, World!" textAlignment="center"/>
</LMColumnView>

Elements may not always represent view instances, however. For example, this markup creates an instance of UISegmentedControl, the content of which is defined by a collection of "segment" tags:

<UISegmentedControl>
    <segment title="Small"/>
    <segment title="Medium"/>
    <segment title="Large"/>
    <segment title="Extra-Large"/>
</UISegmentedControl>

Attributes

Attributes in a MarkupKit document typically represent view properties. For example, the following markup declares an instance of a system-style UIButton and sets its title property to "Press Me!":

<UIButton style="systemButton" title="Press Me!"/>

Property values are set using key-value coding (KVC). Type conversions for string, number, and boolean properties are handled automatically by KVC. Other types, such as colors, fonts, images, and enumerations, are handled specifically by MarkupKit.

For example, the following markup creates a label whose font is set to 24-point Helvetica and whose text color is set to "#ff0000", or bright red:

<UILabel text="A Red Label" font="Helvetica 24" textColor="#ff0000"/>

A few attributes have special meaning in MarkupKit and do not represent properties. These include "style", "class", and "id". Their respective purposes are explained in more detail later.

Additionally, attributes whose names begin with "on" represent control events, or "actions". The values of these attributes represent the handler methods that are triggered when their associated events are fired. For example, this markup creates a button with an associated action that will be triggered when the button is pressed:

<UIButton style="systemButton" title="Press Me!" onPrimaryActionTriggered="buttonPressed"/>

Actions are also discussed in more detail below.

Property Templates

Often, when constructing a user interface, the same set of property values are applied repeatedly to instances of a given type. For example, an application designer may want all buttons to have a similar appearance. While it is possible to simply duplicate the property definitions across each button instance, this is repetitive and does not allow the design to be easily modified later – every instance must be located and modified individually, which can be time consuming and error prone.

MarkupKit allows developers to abstract common sets of property definitions into CSS-like "property templates", which can then be applied by name to individual view instances. This makes it much easier to assign common property values as well as modify them later.

Property templates are specified using JavaScript Object Notation (JSON), and may be either external or inline. Inline templates are defined within the markup document itself, and external templates are specified in a separate file.

For example, the following JSON document defines a template named "greeting", which contains definitions for "font" and "textAlignment" properties:

{
  "greeting": {
    "font": "Helvetica 24", 
    "textAlignment": "center"
  }
}

Templates are added to a MarkupKit document using the properties processing instruction (PI). The following PI adds all properties defined by MyStyles.json to the current document:

<?properties MyStyles?>

Inline templates simply embed the entire template definition within the processing instruction:

<?properties {
  "greeting": {
    "font": "Helvetica 24", 
    "textAlignment": "center"
  }
}?>

Templates are applied to view instances using the reserved "class" attribute. The value of this attribute refers to the name of a template defined within the current document. All property values defined by the template are applied to the view. Nested properties, such as "titleLabel.font", are supported.

For example, given the preceding template definition, the following markup would produce a label reading "Hello, World!" in 24-point Helvetica with horizontally centered text:

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

Multiple templates can be applied to a view using a comma-separated list of template names; for example:

<UILabel class="bold, red" text="Bold Red Label"/>

Outlets

The reserved "id" attribute can be used to assign a name to a view instance. This creates an "outlet" for the view that makes it accessible to calling code. Using KVC, MarkupKit "injects" the named view instance into the document's owner (generally either the view controller for the root view or the root view itself), allowing the application to interact with it.

For example, the following markup declares an instance of UITextField and assigns it an ID of "textField":

<UITextField id="textField"/>

The owning class might declare an outlet for the text field in Objective-C like this:

@property (nonatomic) IBOutlet UITextField *textField;

or in Swift, like this:

@IBOutlet var textField: UITextField!

In either case, when the document is loaded, the outlet will be populated with the text field instance, and the application can interact with it just as if it was defined in a storyboard or created programmatically.

Actions

Most non-trivial applications need to respond in some way to user interaction. UIKit controls (subclasses of the UIControl class) fire events that notify an application when such interaction has occurred. For example, the UIButton class fires the UIControlEventPrimaryActionTriggered event when a button instance is tapped.

While it would be possible for an application to register for events programmatically using outlets, MarkupKit provides a more convenient alternative. Any attribute whose name begins with "on" (but does not refer to a property) is considered a control event. The value of the attribute represents the name of the action that will be triggered when the event is fired.

For example, the following markup declares an instance of UIButton that calls the buttonPressed: method of the document's owner when the button is tapped:

<UIButton style="systemButton" title="Press Me!" onPrimaryActionTriggered="buttonPressed:"/>

For example:

@IBAction func buttonPressed(_ sender: UIButton) {
    // Handle button press
}

Localization

If an attribute's value begins with "@", MarkupKit attempts to look up a localized version of the value before setting the property.

For example, if an application has defined a localized greeting in Localizable.strings as follows:

"hello" = "Hello, World!";

the following markup will produce an instance of UILabel with the value of its text property set to "Hello, World!":

<UILabel text="@hello"/>

If a localized value is not found, the key will be used instead. This allows developers to easily identify missing string resources at runtime.

MarkupKit Classes

MarkupKit includes a number of classes to help simplify application development. Some of the most common are discussed below.

LMViewBuilder

LMViewBuilder is the class that is actually responsible for loading a MarkupKit document. It provides the following class method, which, given a document name, owner, and optional root view, deserializes a view hierarchy from markup:

+ (UIView *)viewWithName:(NSString *)name owner:(nullable id)owner root:(nullable UIView *)root;

The name parameter represents the name of the view to load. It is the file name of the XML document containing the view declaration, minus the .xml extension.

The owner parameter represents the view's owner. It is often an instance of UIViewController, but this is not strictly required. For example, custom table and collection view cell classes often specify themselves as the owner.

The root parameter represents the value that will be used as the root view instance when the document is loaded. This value is often nil, meaning that the root view will be specified by the document itself. However, when non-nil, it means that the root view is being provided by the caller. In this case, the reserved <root> tag can be used as the document's root element to refer to this view.

For example, a view controller that is defined by a storyboard already has an established view instance when viewDidLoad is called. The controller can pass itself as the view's owner and the value of its view property as the root argument. This allows the navigational structure of the application (i.e. segues) to be defined in a storyboard, but the content of individual views to be defined in markup.

Layout Views

Auto layout is an iOS feature that allows developers to create applications that automatically adapt to device size, orientation, or content changes. An application built using auto layout generally has little or no hard-coded view positioning logic, but instead dynamically arranges user interface elements based on their preferred or "intrinsic" content sizes.

Auto layout in iOS is implemented primarily via layout constraints, which, while powerful, are not particularly convenient to work with. To simplify the process, MarkupKit provides the following set of view classes, whose sole responsibility is managing the size and position of their respective subviews:

  • LMRowView – arranges subviews in a horizontal line
  • LMColumnView – arranges subviews in a vertical line
  • LMLayerView – arranges subviews in layers, like a stack of transparencies
  • LMAnchorView – optionally anchors subviews to one or more edges

These classes use layout constraints internally, allowing developers to easily take advantage of auto layout while eliminating the need to manage constraints directly.

LMRowView

The LMRowView class arranges its subviews in a horizontal line. Subviews are laid out from leading to trailing edge in the order in which they are declared. For example, the following markup creates a row view containing three labels:

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

The "layoutMargins" attribute establishes a 12-pixel wide gap around the row view's border, and the trailing spacer view ensures that the labels are left-aligned within the row (or right-aligned in locales that use right-to-left text):

Spacer views are discussed in more detail later.

Baseline Alignment

Subviews can be baseline-aligned within a row using the alignToBaseline property. For example, this markup creates a row view containing three labels, all with different font sizes:

<LMRowView alignToBaseline="true" layoutMargins="12">
    <UILabel text="Ten" font="Helvetica 12"/>
    <UILabel text="Twenty" font="Helvetica 24"/>
    <UILabel text="Thirty" font="Helvetica 48"/>
    <LMSpacer/>
</LMRowView>

Because alignToBaseline is set to true, the baselines of all three labels will line up:

LMColumnView

The LMColumnView class arranges its subviews in a vertical line. Subviews are laid out from top to bottom in the order in which they are declared. For example, the following markup creates a column view containing three text fields:

<LMColumnView layoutMargins="12">
    <UITextField placeholder="First" borderStyle="roundedRect"/>
    <UITextField placeholder="Second" borderStyle="roundedRect"/>
    <UITextField placeholder="Third" borderStyle="roundedRect"/>
    <LMSpacer/>
</LMColumnView>

The left and right edges of each subview are automatically pinned to the left and right edges of the column view, ensuring that all of the text fields are the same width:

Grid Alignment

Nested subviews of a column view can be vertically aligned in a spreadsheet-like grid using the alignToGrid property. When this property is set to true, cells in contiguous rows will be resized to match the width of the widest cell in the column.

For example, the following markup would produce a grid containing three rows and two columns:

<LMColumnView alignToGrid="true" layoutMargins="12">
    <LMRowView>
        <UILabel text="One"/>
        <UITextField weight="1" placeholder="First" borderStyle="roundedRect"/>
    </LMRowView>

    <LMRowView>
        <UILabel text="Two"/>
        <UITextField weight="1" placeholder="Second" borderStyle="roundedRect"/>
    </LMRowView>

    <LMRowView>
        <UILabel text="Three"/>
        <UITextField weight="1" placeholder="Third" borderStyle="roundedRect"/>
    </LMRowView>
</LMColumnView>

The weight values ensure that the text fields are allocated all of the remaining space within each row after the size of label has been determined:

Weights are discussed in more detail below.

View Weights

Often, a row or column view will be given more space than it needs to accommodate the intrinsic sizes of its subviews. MarkupKit adds a weight property to UIView that is used to determine how the extra space should be allocated. Weight is a numeric value that specifies the amount of excess space the view would like to be given within its superview (once the sizes of all unweighted views have been determined) and is relative to all other weights specified within the superview.

For row views, weight applies to the excess horizontal space, and for column views to the excess vertical space. For example, since both labels in the following example have a weight of 0.5, they will each be allocated 50% of the width of the row view. The labels are given a border to make their bounds more obvious:

<LMRowView layoutMargins="12">
    <UILabel weight="0.5" text="50%" textAlignment="center"
        layer.borderWidth="0.5" layer.borderColor="#ff6666"/>
    <UILabel weight="0.5" text="50%" textAlignment="center"
        layer.borderWidth="0.5" layer.borderColor="#ff6666"/>
</LMRowView>

In this example, the first label will be given one-sixth of the available space, the second one-third (2/6), and the third one-half (3/6):

<LMColumnView layoutMargins="12">
    <UILabel weight="1" text="1/6" textAlignment="center"
        layer.borderWidth="0.5" layer.borderColor="#ff6666"/>
    <UILabel weight="2" text="1/3" textAlignment="center"
        layer.borderWidth="0.5" layer.borderColor="#ff6666"/>
    <UILabel weight="3" text="1/2" textAlignment="center"
        layer.borderWidth="0.5" layer.borderColor="#ff6666"/>
</LMColumnView>

Spacer Views

A common use for weights is to create flexible space around a view. For example, the following markup will center a label horizontally within a row:

<LMRowViewn layoutMargins="12">
    <UIView weight="1"/>
    <UILabel text="Hello, World!"/>
    <UIView weight="1"/>
</LMRowView>

Because such "spacer" views are so common, MarkupKit provides a dedicated UIView subclass called LMSpacer for conveniently creating flexible space between other views. LMSpacer has a default weight of 1, so the previous example could be rewritten as follows, eliminating the "weight" attribute and improving readability:

<LMRowView layoutMargins="12">
    <LMSpacer/>
    <UILabel text="Hello, World!"/>
    <LMSpacer/>
</LMRowView>

Layer Views

The LMLayerView class simply arranges its subviews in layers, like a stack of transparencies. The subviews are all automatically sized to fill the layer view.

For example, the following markup creates a layer view containing an image view and a label:

<LMLayerView>
    <UIImageView image="world.png" contentMode="center"/>
    <UILabel text="Hello, World!" textColor="#ffffff" textAlignment="center"/>
</LMLayerView>

Since it is declared first, the contents of the image view will appear beneath the label text:

Anchor Views

The LMAnchorView class optionally anchors subviews to one or more of its own edges. Anchors are specified as a comma-separated list of edges to which the view will be anchored within its parent.

For example, the following markup creates an anchor view containing four labels anchored to its top, left, right, and bottom edges. The labels will all be inset by 16 pixels:

<LMAnchorView layoutMargins="16">
    <UILabel text="Top" anchor="top" 
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
    <UILabel text="Left" anchor="left" 
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
    <UILabel text="Right" anchor="right" 
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
    <UILabel text="Bottom" anchor="bottom" 
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
</LMAnchorView>

Subviews may also be anchored to the leading and trailing edges of the parent view to support right-to-left locales; for example:

<LMAnchorView layoutMargins="16">
    <UILabel text="Leading" anchor="leading"
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
    <UILabel text="Trailing" anchor="trailing"
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
</LMAnchorView>

Additionally, subviews may be anchored to multiple edges for a given dimension. For example, the following markup creates an anchor view containing two labels, each of which will span the entire width of the anchor view:

<LMAnchorView layoutMargins="16">
    <UILabel text="Top" textAlignment="center" anchor="top, left, right"
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
    <UILabel text="Bottom" textAlignment="center" anchor="bottom, left, right"
        layer.borderWidth="0.5" 
        layer.borderColor="#ff6666"/>
</LMAnchorView>

If no anchor is specified for a given dimension, the subview will be centered within the anchor view for that dimension.

More Information

This document introduced the MarkupKit framework and provided an overview of some of its key features.

The latest MarkupKit release can be downloaded here. It is also available via CocoaPods. For more information, see the project README.