Getting Started with MarkupKit

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.

This tutorial introduces the MarkupKit framework by demonstrating how to construct a simple "Hello, World"-style application using markup:

Tapping the button causes a greeting to appear:

The code examples are written in Swift. Xcode 9 and iOS 9 or later are required.

Get MarkupKit

MarkupKit is developed as a freely available open-source project on GitHub. The latest release can be downloaded here.

The file named MarkupKit-iOS.tar.gz contains the iOS framework bundle, MarkupKit.framework. Download this archive and expand it somewhere on your local system.

Create the Xcode Project

In Xcode, select File | New | Project to create a new iOS project. Select the iOS/Application/Single View App template and click the "Next" button. Name the product "HelloMarkupKit" and select Swift as the development language.

Next, add the MarkupKit framework to the project:

  • Select the project root node in the Project Navigator view
  • Select the "HelloMarkupKit" target
  • Select the "General" tab
  • In the Finder, navigate to the location where you unzipped MarkupKit.framework
  • Drag MarkupKit.framework from the Project Navigator to the "Embedded Binaries" section
  • In the dialog that appears, ensure that "Copy items if needed" is checked and click "Finish"

Create the View Controller

In Project Navigator, select ViewController.swift. Import the MarkupKit module by adding the following line to the import section:

import MarkupKit

Within the ViewController class, add an outlet for the label that will be used to display the greeting:

@IBOutlet var greetingLabel: UILabel!

Next, add the following method, which will load the view from markup. The markup document, ViewController.xml, will be created later:

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

Finally, add an action to handle taps on the "Say Hello" button:

@IBAction func sayHello() {
    greetingLabel.text = "Hello, World!";
}

Your final view controller should look something like this:

import UIKit
import MarkupKit
    
class ViewController: UIViewController {
    @IBOutlet var greetingLabel: UILabel!
    
    override func loadView() {
        view = LMViewBuilder.view(withName: "ViewController", owner: self, root: nil)
    }
    
    @IBAction func sayHello() {
        greetingLabel.text = "Hello, World!";
    }
}

Create the View

Select File | New | File… from the menu. In the dialog that appears, select iOS > Other > Empty and click "Next". Name the file ViewController.xml, ensure that the "HelloMarkupKit" project target is selected, and click the "Create" button. Add the following markup to the newly created document:

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

<LMColumnView backgroundColor="white">
    <LMSpacer height="96"/>

    <UIButton style="systemButton" title="Say Hello" onPrimaryActionTriggered="sayHello"/>
    <UILabel id="greetingLabel" font="headline" textAlignment="center"/>

    <LMSpacer/>
</LMColumnView>

This markup declares a simple view hierarchy containing a label and a button. View properties such as font and textAlignment are set using XML attributes. The "id" attribute creates an outlet for the label instance, which is used to associate the label with the instance variable defined in the controller class. The "onPrimaryActionTriggered" attribute defines an action handler for the button and associates it with the sayHello() method defined by the controller.

An instance of LMColumnView is used to automatically arrange the view elements in a vertical line. LMColumnView is an example of a layout view, a view whose sole responsibility is managing the size and position of its subviews. LMSpacer is used to provide fixed and flexible spacing at the top and bottom of the view, respectively.

Build and Run the App

The application is now ready to run. Ensure that "HelloMarkupKit" is selected in the scheme drop-down and launch the application by selecting Product | Run from the menu or by clicking the "Run" button:

Tapping the "Say Hello" button displays the greeting "Hello, World!":

Next Steps

This document introduced the MarkupKit framework by demonstrating how to build a simple, "Hello, World"-style application using markup. For more information, see the project README.

Natively Submitting HTML Forms in iOS

HTML forms are a common means of uploading information to a web server. For example, the HTML 5 specification includes a sample form that simulates a pizza delivery request. The form allows the user to provide contact information, pizza size and topping details, and delivery instructions.

A working implementation of this form can be found on httpbin.org. Submitting the form produces a JSON response that simply echoes the information sent with the request. However, in most "real world" applications, a form submission typically triggers some meaningful action on the server, such as posting a message, responding to a survey, or making a purchase.

Mobile applications often need to perform similar tasks. While it is possible to embed an HTML form in a native application using a web view, this is not always ideal. For example, the form may not be optimized for a mobile device, resulting in controls that are too small and difficult to interact with. Further, embedded forms don't generally provide the seamless experience users expect from a native application. They often look out of place, making it obvious that the app is not truly native:

A form constructed with native controls is usually more visually consistent with platform conventions and much easier to interact with. For example:

However, many native forms are processed via some form of custom XML or JSON-based web service API. This represents a duplication of effort, since developers need to support both the form processing code as well as the code for implementing the web service. It would be ideal if the server-side logic could be shared by both clients, reducing overall development effort while preserving the enhanced user experience provided by the native UI.

MarkupKit

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

This markup is equivalent to the following Swift code:

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

The native form shown in the previous section was created using the following markup. It declares a static table view whose contents represent the elements of the delivery request form:

<LMTableView style="groupedTableView">
    <!-- Contact information -->
    <LMTableViewCell selectionStyle="none">
        <UITextField id="nameTextField" placeholder="Customer Name"/>
    </LMTableViewCell>

    <LMTableViewCell selectionStyle="none">
        <UITextField id="phoneTextField" placeholder="Telephone" keyboardType="numberPad"/>
    </LMTableViewCell>

    <LMTableViewCell selectionStyle="none">
        <UITextField id="emailTextField" placeholder="Email Address" keyboardType="emailAddress"/>
    </LMTableViewCell>

    <!-- Pizza size/toppings -->
    <?sectionBreak?>
    <?sectionName size?>
    <?sectionSelectionMode singleCheckmark?>

    <sectionHeader title="Pizza Size"/>

    <UITableViewCell textLabel.text="Small" value="small"/>
    <UITableViewCell textLabel.text="Medium" value="medium"/>
    <UITableViewCell textLabel.text="Large" value="large"/>

    <?sectionBreak?>
    <?sectionName toppings?>
    <?sectionSelectionMode multipleCheckmarks?>

    <sectionHeader title="Pizza Toppings"/>

    <UITableViewCell textLabel.text="Bacon" value="bacon"/>
    <UITableViewCell textLabel.text="Extra Cheese" value="cheese"/>
    <UITableViewCell textLabel.text="Onion" value="onion"/>
    <UITableViewCell textLabel.text="Mushroom" value="mushroom"/>

    <!-- Delivery time/instructions -->
    <?sectionBreak?>

    <sectionHeader title="Preferred Delivery Time"/>

    <LMTableViewCell selectionStyle="none">
        <UIDatePicker id="deliveryDatePicker" datePickerMode="time" height="140"/>
    </LMTableViewCell>

    <?sectionBreak?>

    <sectionHeader title="Delivery Instructions"/>

    <LMTableViewCell selectionStyle="none">
        <UITextView id="commentsTextView" height="140" textContainerInset="4" textContainer.lineFragmentPadding="0"/>
    </LMTableViewCell>
</LMTableView>

Of course, MarkupKit isn't the only way to create native forms in iOS; however, it can significantly simplify the task.

HTTP-RPC

HTTP-RPC is an open-source framework for simplifying development of REST applications. It allows developers to access REST-based web services using a convenient, RPC-like metaphor while preserving fundamental REST principles such as statelessness and uniform resource access.

The project currently includes support for consuming web services in Objective-C/Swift and Java (including Android). It provides a consistent, callback-based API that makes it easy to interact with services regardless of target device or operating system.

For example, the following code snippet shows how a Swift client might access a simple web service that returns a friendly greeting:

Swift

serviceProxy.invoke("GET", path: "/hello") { result, error in
    print(result) // Prints "Hello, World!"
}

While HTTP-RPC is often used to access JSON-based REST APIs, it also supports posting data to the server using the application/x-www-form-urlencoded MIME type used by HTML forms.

For example, the following view controller uses the iOS HTTP-RPC client to submit the contents of the form from the previous section to the test service at httpbin.org. The actual form submission is performed in the submit() method using HTTP-RPC's WSWebServiceProxy class:

class ViewController: LMTableViewController {
    @IBOutlet var nameTextField: UITextField!
    @IBOutlet var phoneTextField: UITextField!
    @IBOutlet var emailTextField: UITextField!
    @IBOutlet var deliveryDatePicker: UIDatePicker!
    @IBOutlet var commentsTextView: UITextView!

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

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Pizza Delivery Form"

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style: UIBarButtonItemStyle.plain,
            target: self, action: #selector(submit))

        deliveryDatePicker.minuteInterval = 15
    }

    func submit() {
        let timeFormatter = DateFormatter()

        timeFormatter.dateFormat = "hh:mm"

        let serviceProxy = WSWebServiceProxy(session: URLSession.shared, serverURL: URL(string: "https://httpbin.org")!)

        serviceProxy.encoding = WSApplicationXWWWFormURLEncoded

        serviceProxy.invoke("POST", path: "/post", arguments: [
            "custname": nameTextField.text ?? "",
            "custtel": phoneTextField.text ?? "",
            "custemail": emailTextField.text ?? "",
            "size": tableView.value(forSection: tableView.section(withName: "size")) ?? "",
            "topping": tableView.values(forSection: tableView.section(withName: "toppings")),
            "delivery": timeFormatter.string(from: deliveryDatePicker.date),
            "comments": commentsTextView.text
        ]) { result, error in
            let alertController = UIAlertController(title: "Status",
                message: (error == nil) ? "Form submitted." : error!.localizedDescription,
                preferredStyle: .alert)

            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler:nil))

            self.present(alertController, animated: true, completion: nil)
        }
    }
}

While the example controller simply displays a success or failure message in response to the form submission, an actual application might do something slightly more sophisticated, such as presenting a confirmation page returned by the server.

As with MarkupKit, HTTP-RPC isn't strictly required, but its built-in support for executing URL-encoded form posts makes it a good option.

Summary

This article provided an overview of how MarkupKit and HTTP-RPC can be used to natively submit HTML forms in iOS, reducing development effort and improving user experience.

For more information, please see the following:

MVVM in iOS

MVVM ("model/view/view-model") is design pattern that helps promote a separation of concerns in software development. It is an extension of the well-known "model/view/controller" (MVC) pattern that is often used in user interface design.

In a traditional MVC application, a "controller" object is used to mediate interaction between two other objects known as the "model" and the "view". The model is an abstract representation of data managed by the application, and the view is a visual representation of the data contained in the model. The controller notifies the view of changes to the model, and updates the model in response to user input events received from the view.

MVVM expands on MVC by further decoupling the view from the controller. Instead of requiring the controller to explicitly manage the view's state, a view in an MVVM application uses data binding to be automatically updated in response to changes in a "view model" object exposed by the controller. This object adapts the data provided by the underlying model so that it can be easily consumed by the view. The additional level of indirection allows the view and controller to vary independently without the risk of breaking one or the other.

MVC Example

For example, consider a simple custom table view cell implemented using MVC:

The cell class might provide a set of outlets that the table view controller can use to update its state:

class CustomCell: UITableViewCell {
    @IBOutlet var headingLabel: UILabel!
    @IBOutlet var detailLabel: UILabel!

    ...
}

Row data might be represented by instances of the following class, which provides "heading" and "detail" values for each cell:

class Row: NSObject {
    var heading: String?
    var detail: String?
}

The controller would use the outlets to populate the cell's contents when a new cell is requested:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.description(), for: indexPath) as! CustomCell

    let row = rows[indexPath.row]

    cell.headingLabel.text = row.heading
    cell.detailLabel.text = row.detail

    return cell
}

However, this design creates a tight coupling between the controller and the custom cell view. Any time the view class changes, the controller must also be updated.

MVVM Example

MVVM solves this problem by decoupling the view from the controller. Rather than exposing its implementation details via outlets, the view registers itself as an observer on the properties of the view model. Using this approach, the view and controller both become dependent on the view model, but neither is dependent on the other. As long as the view model doesn't change, either one can be modified without impact.

For example, the following markup shows a custom table view cell implemented using MarkupKit, an open-source framework for building native iOS and tvOS applications using a simple, HTML-like markup language. The cell contains two labels arranged vertically in a column:

<root accessoryType="disclosureIndicator">
    <LMColumnView>
        <UILabel text="$row.heading"/>
        <UILabel text="$row.detail"/>
    </LMColumnView>
</root>

Instead of outlets, the cell class exposes a row property representing the view model. The labels' text properties are bound to the properties of this object:

class CustomCell: LMTableViewCell {
    // View model
    dynamic var row: Row!

    ...
}

With the bindings established, the controller can be implemented as shown below. It simply dequeues a cell and sets its row property to the corresponding model value. Because they are bound to the properties of the view model, the cell's labels are automatically updated to reflect the new values. No direct manipulation of view elements is required:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.description(), for: indexPath) as! CustomCell

    cell.row = rows[indexPath.row]

    return cell
}

Key-Value Observing

Even if you're not using MarkupKit, you can still create bindings manually using key-value observing (KVO). For example:

class CustomCell: UITableViewCell {
    dynamic var row: Row!

    let headingKeyPath = "row.heading"
    let detailKeyPath = "row.detail"

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        // Add observers
        addObserver(self, forKeyPath: headingKeyPath, options: [.initial, .new], context: nil)
        addObserver(self, forKeyPath: detailKeyPath, options: [.initial, .new], context: nil)
    }

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
    }

    deinit {
        // Remove observers
        removeObserver(self, forKeyPath: headingKeyPath)
        removeObserver(self, forKeyPath: detailKeyPath)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        // Respond to changes
        let value = change?[.newKey] as? String

        switch keyPath! {
        case headingKeyPath:
            textLabel?.text = value

        case detailKeyPath:
            detailTextLabel?.text = value

        default:
            break
        }
    }
}

However, in general, this approach will be much more verbose than using markup.

Summary

This article introduced the MVVM design pattern and provided an example of how it can be used to simplify the implementation of a custom table view cell. A complete example can be found here.

For more information, see the MarkupKit README.

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.

Using MarkupKit with Charts

For a while now, I’ve been meaning to see if the popular open-source Charts library would work with MarkupKit. I finally got around to trying it this morning.

It actually turned out to be much easier than I expected. All of the Charts demos are built using XIB files. All I had to do was create a markup document that mimicked the contents of a XIB file, including outlets and actions, and add the code to load the view from markup instead of the XIB. I chose to try to replicate the pie chart example:

To load the view declaration, I added the following method to the demo’s PieChartViewController class. No other changes to the controller were necessary:

- (void)loadView
{
    [self setView:[LMViewBuilder viewWithName:@"PieChartViewController" owner:self root:nil]];
}

The contents of PieChartViewController.xml are shown below. Note the use of the Charts prefix in the PieChartView declaration. This is necessary because the PieChartView class is defined in the Charts module:

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

<!-- Pie chart demo -->
<LMColumnView layoutMarginsRelativeArrangement="false" backgroundColor="#f0f0f0">
    <!-- Options button -->
    <LMRowView layoutMarginTop="6" layoutMarginLeft="18" layoutMarginRight="18">
        <LMSpacer/>

        <UIButton style="systemButton" title="Options"
            titleLabel.font="HelveticaNeue-Light 17.0" tintColor="#4c8fbd"
            onTouchUpInside="optionsButtonTapped:"/>
    </LMRowView>

    <!-- Pie chart view -->
    <Charts.PieChartView id="chartView" weight="1"/>

    <!-- Sliders -->
    <LMColumnView layoutMarginLeft="8" layoutMarginRight="8" layoutMarginBottom="24">
        <LMRowView>
            <UISlider id="sliderX" minimumValue="1" maximumValue="25" value="1" weight="4"
                onValueChanged="slidersValueChanged:"/>

            <UITextField id="sliderTextX" weight="1"
                font="HelveticaNeue-Light 15.0"
                textAlignment="center"/>
        </LMRowView>

        <LMRowView>
            <UISlider id="sliderY" minimumValue="1" maximumValue="200" value="1" weight="4"
                onValueChanged="slidersValueChanged:"/>

            <UITextField id="sliderTextY" weight="1"
                font="HelveticaNeue-Light 15.0"
                textAlignment="center"/>
        </LMRowView>
    </LMColumnView>
</LMColumnView>

The results are shown below. They are virtually indistinguishable from the original (though the chart data differs slightly, since it appears to be randomly generated each time the app is run):

So, I was very happy to discover that, not only did MarkupKit work with the Charts library, I was able to use it as a drop-in replacement for the original XIB file in the demo application!

The latest version of MarkupKit can be downloaded here. See the project README for more information.

Creating a Paging Scroll View using Markup

I'm often inspired to try to replicate programming examples I find elsewhere on the web using MarkupKit. For one thing, it's a good way to test the framework. It's also a good way to validate that the API supports the kinds of things that developers might want to do with it. But most importantly, it is a way to provide a practical demonstration of how MarkupKit can be used to help simplify the app development process.

Today I was inspired to attempt to recreate an example I came across of building a paging scroll view. The "pages" of the scroll view are simply a collection of images depicting a variety of generic office scenes. Layered above the pages are a couple of logos, some text, a page control, and a button (which fades into view when the user visits the final page). The results are shown below:

The original example used views and constraints created interactively using Interface Builder. The MarkupKit version is constructed declaratively using XML:

<LMLayerView layoutMarginsRelativeArrangement="false">
    <!-- Page images -->
    <LMPageView id="pageView">
        <UIImageView image="Slide 1" contentMode="scaleAspectFit"/>
        <UIImageView image="Slide 2" contentMode="scaleAspectFit"/>
        <UIImageView image="Slide 3" contentMode="scaleAspectFit"/>
        <UIImageView image="Slide 4" contentMode="scaleAspectFit"/>
    </LMPageView>

    <!-- Icons, text, page control, and start button -->
    <LMColumnView layoutMarginTop="50" layoutMarginLeft="30" layoutMarginBottom="40" layoutMarginRight="30">
        <!-- Main icon -->
        <UIImageView image="Icon" height="100" contentMode="scaleAspectFit"/>

        <LMColumnView spacing="30">
            <!-- Text icon -->
            <UIImageView image="Text Icon" height="66" contentMode="scaleAspectFit"/>

            <!-- Text label -->
            <UILabel id="label" textAlignment="center" font="System 14" numberOfLines="0"/>
        </LMColumnView>

        <LMSpacer/>

        <LMColumnView spacing="30">
            <!-- Page control -->
            <UIPageControl id="pageControl" numberOfPages="4"
                pageIndicatorTintColor="#ef003d" currentPageIndicatorTintColor="#ffffff"
                userInteractionEnabled="false"/>

            <!-- Start button -->
            <UIButton id="startButton" title="Let's Start"
                titleLabel.font="System 15" titleLabel.textColor="#ffffff"
                layer.backgroundColor="#ef003d" layer.cornerRadius="4"
                contentEdgeInsets="12"
                alpha="0"/>
        </LMColumnView>
    </LMColumnView>
</LMLayerView>

The root view is an instance of LMLayerView, a MarkupKit-provided UIView subclass that automatically arranges its subviews in layers, like a stack of transparencies. The first layer contains the page view, and the second contains the overlay content.

Paging support is provided by an instance of LMPageView, a MarkupKit-provided subclass of UIScrollView that automatically presents its subviews as a sequence of pages. The pages themselves are UIImageView instances containing the generic office images.

The overlay content consists of two image views, a UILabel instance, a UIPageControl, and a UIButton. These views are automatically arranged by the various LMColumnView instances that contain them. Column views automatically arrange their subviews in a vertical line that runs from top to bottom. The subviews are generally given their natural (or "intrinsic") heights, but are sized to fill the width of the column view.

The root column view contains the main icon and establishes a set of margins around its content. Nested column views are used to apply fixed spacing between the text logo and text label as well as the page control and the button (which is initially invisible). An instance of LMSpacer is used to provide flexible space between the column views.

The button and the page control are styled to match the icons as well as each other. The button is intially assigned an alpha value of 0 so it won't be visible until the user reaches the final page.

The example requires a small amount of controller code to handle page changes and update the state of the label and page control:

import UIKit
import MarkupKit

class ViewController: UIViewController, UIScrollViewDelegate {
    weak var pageView: LMPageView!

    weak var label: UILabel!
    weak var pageControl: UIPageControl!
    weak var startButton: UIButton!

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

    override func viewDidLoad() {
        super.viewDidLoad()

        pageView.delegate = self
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        scrollViewDidEndDecelerating(pageView)
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let currentPage = scrollView.currentPage

        pageControl.currentPage = currentPage

        switch (currentPage) {
        case 0:
            label.text = "Sweettutos.com is your blog of choice for Mobile tutorials"

        case 1:
            label.text = "I write mobile tutorials mainly targeting iOS"

        case 2:
            label.text = "And sometimes I write games tutorials about Unity"

        default:
            label.text = "Keep visiting sweettutos.com for new coming tutorials, and don't forget to subscribe to be notified by email :)"

            UIView.animate(withDuration: 1.0, animations: {
                self.startButton.alpha = 1.0
            }) 
        }
    }
}

As usual, the markup is loaded in loadView(). The controller assigns itself as the page view's delegate in viewDidLoad() so it can respond to page changes, which trigger a call to scrollViewDidEndDecelerating(). In scrollViewDidEndDecelerating(), the controller updates the state of the page control to match the current page shown by the page view, and then sets the label text to a value appropriate for the page (the strings are taken from the original example). The viewDidAppear() method calls scrollViewDidEndDecelerating() simply to avoid duplicating the code that sets the initial state of the label.

While the original example was well written and very well documented, the MarkupKit version is considerably less verbose and ultimately requires much less effort on the part of the developer.

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