Caching Web Service Response Data in iOS

Many iOS applications obtain data via HTTP web services that return JSON documents. For example, the following code uses the HTTP-RPC WSWebServiceProxy class to invoke a simple web service that returns a simulated list of users as JSON. The results are stored as a dictionary and presented in a table view:

class UserViewController: UITableViewController {
    var users: [[String: Any]]! = nil

    ...

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

        if (users == nil) {
            // Load the data from the server
            AppDelegate.serviceProxy.invoke("GET", path: "/users") { result, error in
                if (error == nil) {
                    self.users = result as? [[String: Any]]

                    self.tableView.reloadData()
                } 
            }
        }
    }

    ...
}

This works fine when both the device and the service are online, but it fails if either one is not. In some cases this may be acceptable, but other times it might be preferable to show the user the most recent response when more current data is not available.

To facilitate offline support, the response data must be cached. However, since writing to the file system is a potentially time-consuming operation, it should be done in the background to avoid blocking the main (UI) thread. Here, the data is written using an operation queue to ensure that access to it is serialized:

class UserViewController: UITableViewController {
    var users: [[String: Any]]! = nil

    var userCacheURL: URL?
    let userCacheQueue = OperationQueue()

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
            userCacheURL = cacheURL.appendingPathComponent("users.json")
        }
    }

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

        if (users == nil) {
            // Load the data from the server
            AppDelegate.serviceProxy.invoke("GET", path: "/users") { result, error in
                if (error == nil) {
                    self.users = result as? [[String: Any]]

                    self.tableView.reloadData()

                    // Write the response to the cache
                    if (self.userCacheURL != nil) {
                        self.userCacheQueue.addOperation() {
                            if let stream = OutputStream(url: self.userCacheURL!, append: false) {
                                stream.open()

                                JSONSerialization.writeJSONObject(result!, to: stream, options: [.prettyPrinted], error: nil)

                                stream.close()
                            }
                        }
                    }
                } 
            }
        }
    }

    ...
}

Finally, the data can be retrieved from the cache if the web service call fails. The data is read from the cache in the background, and the UI is updated by reloading the table view on the main thread:

class UserViewController: UITableViewController {
    var users: [[String: Any]]! = nil

    var userCacheURL: URL?
    let userCacheQueue = OperationQueue()

    ...

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

        if (users == nil) {
            // Load the data from the server
            AppDelegate.serviceProxy.invoke("GET", path: "/users") { result, error in
                if (error == nil) {
                    self.users = result as? [[String: Any]]

                    self.tableView.reloadData()

                    // Write the data to the cache
                    if (self.userCacheURL != nil) {
                        self.userCacheQueue.addOperation() {
                            if let stream = OutputStream(url: self.userCacheURL!, append: false) {
                                stream.open()

                                JSONSerialization.writeJSONObject(result!, to: stream, options: [.prettyPrinted], error: nil)

                                stream.close()
                            }
                        }
                    }
                } else if (self.userCacheURL != nil) {
                    // Read the data from the cache
                    self.userCacheQueue.addOperation() {
                        if let stream = InputStream(url: self.userCacheURL!) {
                            stream.open()

                            self.users = (try? JSONSerialization.jsonObject(with: stream, options: [])) as? [[String: Any]]

                            stream.close()
                        }

                        // Update the UI
                        OperationQueue.main.addOperation() {
                            self.tableView.reloadData()
                        }
                    }
                }
            }
        }
    }

    ...
}

Now, as long as the application has been able to connect to the server at least once, it can function either online or offline, using the cached response data.

For more ways to simplify iOS app development, please see my projects on GitHub:

Applying Style Sheets Client-Side in iOS

While native mobile applications can often provide a more seamless and engaging user experience than a browser-based app, it is occasionally convenient to present certain types of content using a web view. Specifically, any content that is primarily text-based and requires minimal user interaction may be a good candidate for presentation as HTML; for example, product descriptions, user reviews, or instructional content.

However, browser-based content often tends to look out of place within a native app. For example, consider the following simple HTML document:

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1.0"/>
</head>
<body>
    <h1>Lorem Ipsum</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    <ul>
    <li>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</li> 
    <li>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</li>
    <li>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</li>
    </ul>
</body>
</html>

Rendered by WKWebView, the result looks like this:

Because the text is displayed using the default browser font rather than the system font, it is immediately obvious that the content is not native. To make it appear more visually consistent with other elements of the user interface, a stylesheet could be used to render the document using the system font:

<head>
    ...
    <style>
    body {
        font-family: '-apple-system';
        font-size: 10pt;
    }
    </style>
</head>

The result is shown below. The styling of the text now matches the rest of the UI:

However, while this approach may work for this simple example, it does not scale well. Different app (or OS) versions may have different styling requirements.

By applying the stylesheet on the client, the presentation can be completely separated from the content. This can be accomplished by linking to the stylesheet rather than embedding it inline:

<head>
    ...
    <link rel="stylesheet" type="text/css" href="example.css"/>
</head>

However, instead of downloading the stylesheet along with the HTML document, it is distributed with the application itself and applied using the loadHTMLString:baseURL: method of the WKWebView class. The first argument to this method contains the (unstlyed) HTML content, and the second contains the base URL against which relative URLs in the document (such as stylesheets) will be resolved:

class ViewController: UIViewController {
    var webView: WKWebView!

    var serviceProxy: WSWebServiceProxy!

    override func loadView() {
        webView = WKWebView()

        view = webView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "Local CSS Example"

        serviceProxy = WSWebServiceProxy(session: URLSession.shared, serverURL: URL(string: "http://localhost")!)
    }

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

        serviceProxy.invoke("GET", path: "example.html") { result, error in
            if (result != nil) {
                self.webView.loadHTMLString(result as! String, baseURL: Bundle.main.resourceURL!)
            }
        }
    }
}

In this example, the document, example.html, is loaded using the HTTP-RPC WSWebServiceProxy class. The stylesheet, example.css, is stored in the resource folder of the application's main bundle:

body {
    font-family: '-apple-system';
    font-size: 10pt;
}

The results are identical to the previous example. However, the content and visual design are no longer tightly coupled and can vary independently:

For more ways to simplify iOS app development, please see my projects on GitHub:

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.heading

    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.

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.

HTTP-RPC 4.0 Released

HTTP-RPC 4.0 is now available for download. This release adds support for application/x-www-form-urlencoded and application/json request encodings. In addition to submitting request arguments as multipart/form-data, client applications can now POST arguments as URL-encoded key/value pairs and POST or PUT argument values as JSON.

For example, the following Java code would submit a JSON-encoded POST request to the resource located at /example:

serviceProxy.setEncoding(WebServiceProxy.APPLICATION_JSON);

serviceProxy.invoke("POST", "/example", mapOf(
    entry("string", "hello"),
    entry("number", 123),
    entry("flag", true)), (result, exception) -> {
    // ...
});

In Swift, the code might look like this:

serviceProxy.encoding = WSApplicationJSON

serviceProxy.invoke("POST", path: "/example", arguments: [
    "string": "hello",
    "number": 123,
    "flag": true
    ]) { result, error in
    // ...
}

In either case, the body of the request would contain the following:

{
  "string": "hello",
  "number": 123,
  "flag": true
}

For more information, see the project 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.