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:

MarkupKit 3.1 Released

MarkupKit 3.1 is now available for download. This release improves selection management in table and picker views by allowing callers to get and set selection state by value, similar to a <select> element in HTML.

For example, a web page that allows a user to choose one of several size options might include something like the following:

<select name="size">
    <option value="S">Small</option>
    <option value="M">Medium</option>
    <option value="L">Large</option>
    <option value="XL">Extra-Large</option>
</select>

Alternatively, the options could be presented using radio buttons like this:

<input type="radio" name="size" value="S"/>Small<br/>
<input type="radio" name="size" value="M"/>Medium<br/>
<input type="radio" name="size" value="L"/>Large<br/>
<input type="radio" name="size" value="XL"/>Extra-Large<br/>

In MarkupKit, this can be implemented using LMTableView as shown below:

<LMTableView style="groupedTableView">
    <?sectionName sizes?>
    <?sectionSelectionMode singleCheckmark?>
    <UITableViewCell textLabel.text="Small" value="S"/>
    <UITableViewCell textLabel.text="Medium" value="M"/>
    <UITableViewCell textLabel.text="Large" value="L"/>
    <UITableViewCell textLabel.text="Extra-Large" value="XL"/>
    ...
</LMTableView>

The value associated with the currently selected row can be retrieved using the valueForSection: method of LMTableView. This method takes a single argument containing the section index, which can be obtained via the sectionWithName: method. Rows can be programmatically selected using setValue:forSection::

let value = tableView.value(forSection: tableView.section(withName: "sizes")) as! String

tableView.setValue("L", forSection: tableView.section(withName: "sizes"))

Picker Views

Alternatively, size selection could be implemented using a picker view:

<LMPickerView id="pickerView">
    <?componentName sizes?>
    <row title="Small" value="S"/>
    <row title="Medium" value="M"/>
    <row title="Large" value="L"/>
    <row title="Extra-Large" value="XL"/>
</LMPickerView>

In this case, the value associated with the selected row can be retrieved using the valueForComponent: method of LMPickerView, and rows can be programmatically selected using setValue:forComponent:. The component index can be obtained via componentWithName::

let value = pickerView.value(forComponent: pickerView.component(withName: "sizes")) as! String

pickerView.setValue("L", forComponent: pickerView.component(withName: "sizes"), animated: false)

Multiple Selection

In HTML, multiple options are often presented using a multi-select list element as shown below:

<select name="pets" size="5" multiple>
    <option value="D">Dog</option>
    <option value="C">Cat</option>
    <option value="F">Fish</option>
    <option value="R">Rabbit</option>
    <option value="T">Turtle</option>
</select>

Alternatively, the options may be represented by a group of checkboxes:

<input type="checkbox" name="pets" value="D"/>Dog<br/>
<input type="checkbox" name="pets" value="C"/>Cat<br/>
<input type="checkbox" name="pets" value="F"/>Fish<br/>
<input type="checkbox" name="pets" value="R"/>Rabbit<br/>
<input type="checkbox" name="pets" value="R"/>Turtle<br/>

In MarkupKit, this can also be implemented using LMTableView:

<LMTableView style="groupedTableView">
    <?sectionName pets?>
    <?sectionSelectionMode multipleCheckmarks?>
    <UITableViewCell textLabel.text="Dog" value="D"/>
    <UITableViewCell textLabel.text="Cat" value="C"/>
    <UITableViewCell textLabel.text="Fish" value="F"/>
    <UITableViewCell textLabel.text="Rabbit" value="R"/>
    <UITableViewCell textLabel.text="Turtle" value="T"/>
    ...
</LMTableView>

The values associated with the selected rows can be obtained using the valuesForSection: method, and set using setValues:forSection::

let values = tableView.values(forSection: tableView.section(withName: "pets")) as! [String]

tableView.setValues(["C", "T"], forSection: tableView.section(withName: "pets"))

Additional Information

For more information, please see the project README.

Made with MarkupKit

Just wanted to share some screen shots from an app I’ve been developing recently with MarkupKit. The app provides access to visitor passes and attraction information for a number of cities across the US and Europe/Asia:

Have you built anything with MarkupKit you’d like to share? Let me know in the comments.

A Lightweight Alternative to JAX-RS

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), making it easy to interact with services regardless of target device or operating system.

HTTP-RPC also includes optional support for implementing REST services in Java, providing a lightweight alternative to larger REST frameworks such as JAX-RS. The entire platform is distributed as two JAR files totaling approximately 30KB in size, making it an ideal choice for applications where a minimal footprint is required.

This article introduces the HTTP-RPC server framework and provides an overview of its key features.

DispatcherServlet

DispatcherServlet is an abstract base class for REST services. Service operations are defined by adding public methods to a concrete service implementation.

Methods are invoked by submitting an HTTP request for a path associated with a servlet instance. Arguments are provided either via the query string or in the request body (like an HTML form), or as JSON. DispatcherServlet converts the request parameters to the expected argument types, invokes the method, and serializes the return value to the output stream as JSON.

The RequestMethod annotation is used to associate a service method with an HTTP verb such as GET or POST. The optional ResourcePath annotation can be used to associate the method with a specific path relative to the servlet. If unspecified, the method is associated with the servlet itself.

Multiple methods may be associated with the same verb and path. DispatcherServlet selects the best method to execute based on the provided argument values. For example, the following service class might be used to implement some simple mathematical operations:

@WebServlet(urlPatterns={"/math/*"})
public class MathServlet extends DispatcherServlet {
    @RequestMethod("GET")
    @ResourcePath("/sum")
    public double getSum(double a, double b) {
        return a + b;
    }

    @RequestMethod("GET")
    @ResourcePath("/sum")
    public double getSum(List<Double> values) {
        double total = 0;

        for (double value : values) {
            total += value;
        }

        return total;
    }
}

The following request would cause the first method to be invoked:

GET /math/sum?a=2&b=4

This request would invoke the second method:

GET /math/sum?values=1&values=2&values=3

In either case, the service would return the value 6 in response.

Method Arguments

Method arguments may be any of the following types:

  • Numeric primitive or wrapper class (e.g. int or Integer)
  • boolean or Boolean
  • String
  • java.util.List
  • java.util.Map
  • java.net.URL

List arguments represent either multi-value parameters submitted using one of the form encodings or array structures submitted as JSON. Map arguments represent object structures submitted as JSON, and must use strings for keys. List and map values are automatically converted to their declared types when possible.

URL arguments represent file uploads. They may be used only with POST requests submitted using the multi-part form data encoding. For example:

@WebServlet(urlPatterns={"/upload/*"})
@MultipartConfig
public class FileUploadServlet extends DispatcherServlet {
    @RequestMethod("POST")
    public void upload(URL file) throws IOException {
        ...
    }

    @RequestMethod("POST")
    public void upload(List<URL> files) throws IOException {
        ...
    }
}

Return Values

Return values are converted to their JSON equivalents as follows:

  • Number: number
  • Boolean: true/false
  • CharSequence: string
  • Iterable: array
  • java.util.Map: object

Methods may also return void or Void to indicate that they do not produce a value.

For example, the following method would produce a JSON object containing three values. The mapOf() and entry() methods are provided by the framework to help simplify map creation:

@RequestMethod("GET")
public Map<String, ?> getMap() {
    return mapOf(
        entry("text", "Lorem ipsum"),
        entry("number", 123),
        entry("flag", true)
    );
}

The service would return the following in response:

{
    "text": "Lorem ipsum",
    "number": 123,
    "flag": true
}

Request and Repsonse Properties

DispatcherServlet provides the following methods to allow a service to access the request and response objects associated with the current operation:

protected HttpServletRequest getRequest() { ... }
protected HttpServletResponse getResponse() { ... }

For example, a service might access the request to get the name of the current user, or use the response to return a custom header.

The response object can also be used to produce a custom result. If a service method commits the response by writing to the output stream, the return value (if any) will be ignored by DispatcherServlet. This allows a service to return content that cannot be easily represented as JSON, such as image data or alternative text formats.

Path Variables

Path variables may be specified by a "?" character in the resource path. For example:

@RequestMethod("GET")
@ResourcePath("/contacts/?/addresses/?")
public List<Map<String, ?>> getContactAddresses() { ... }

The getKeys() method returns the list of variables associated with the current request:

protected List<String> getKeys() { ... }

For example, given the following path:

/contacts/jsmith/addresses/home

getKeys() would return the following:

["jsmith", "home"]

Summary

HTTP-RPC is an open-source framework for simplifying development of REST applications. It provides a lightweight alternative to larger Java REST frameworks such as JAX-RS, making it an ideal choice for low-footprint applications such as microservices or IoT.

For more information, see the project README.

MVVM in iOS

NOTE Swift 3.1 appears to break nested bindings as shown in this example. The initial binding works, but reassigning the root variable triggers an exception. Binding directly to root properties (e.g. "heading" vs. "content.heading") still seems to work fine.

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!

    ...
}

The controller would use the outlets to populate the cell's contents when a new cell is requested. In this example, row data is provided by dictionary instances containing "heading" and "detail" values for each cell:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let row = rows[indexPath.row]

    let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell

    cell.headingLabel.text = row["heading"] as? String
    cell.detailLabel.text = row["detail"] as? String

    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:

<LMColumnView>
    <UILabel class="label.heading" text="$content.heading"/>
    <UILabel class="label.detail" text="$content.detail"/>
</LMColumnView>

Instead of outlets, the custom cell class exposes a content property representing the view model. The labels' text properties are bound to the properties of this object. Internally, MarkupKit uses key-value observing (KVO) to manage bindings:

class CustomCell: LMTableViewCell {
    // View model
    dynamic var content: [String: AnyObject]?

    ...
}

With the bindings established, the controller can be implemented as shown below. It simply dequeues a cell and sets its content property to the dictionary for the corresponding row. 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") as! CustomCell

    cell.content = rows[indexPath.row]

    return cell
}

Even if you're not using MarkupKit, you can still create bindings manually using KVO. For example:

class CustomCell: UITableViewCell {
    dynamic var row: [String: AnyObject]?

    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 3.0 Released

MarkupKit 3.0 is now available for download. The primary focus of this release was not the addition of new features but improvements to an existing feature.

In earlier versions, custom table and picker view data sources and delegates could call back into the source view to obtain information about static content. However, this approach relied on overrides of UIKit view methods such as numberOfSections, numberOfRowsInSection:, and cellForRowAtIndexPath:, which already have established and documented semantics. The redefined behavior did not always reflect the original intent, which could (and did) lead to bugs.

As of MarkupKit 3.0, a custom data source or delegate can call directly into the respective protocol implementations provided by LMTableView and LMPickerView. The view methods are no longer overridden, allowing them to retain their original behaviors.

However, while this approach provides a much cleaner and more deterministic delegation model, it is not backwards-compatible. All custom data source and delegate implementations must be updated to call the delegate methods instead of the view methods. Specifically, table view controllers that provide custom content should now extend the new LMTableViewController class and delegate to the base method as needed. For example:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let n: Int
    if (tableView.name(forSection: section) == "dynamic") {
        n = ...
    } else {
        n = super.tableView(tableView, numberOfRowsInSection: section)
    }

    return n
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell
    if (tableView.name(forSection: indexPath.section) == "dynamic") {
        cell = ...
    } else {
        cell = super.tableView(tableView, cellForRowAt: indexPath)
    }

    return cell
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if (tableView.name(forSection: indexPath.section) == "dynamic") {
        ...
    } else {
        super.tableView(tableView, didSelectRowAt: indexPath)
    }
}

Additionally, controllers that provide custom content for LMPickerView instances should be updated to delegate to the picker view as shown below:

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return pickerView.numberOfComponents(in: pickerView)
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    let n: Int
    if (pickerView.name(forComponent: component) == "dynamic") {
        n = ...
    } else {
        n = pickerView.pickerView(pickerView, numberOfRowsInComponent: component)
    }

    return n
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    let title: String
    if (pickerView.name(forComponent: component) == "dynamic") {
        title = ...
    } else {
        title = pickerView.title(forRow: row, forComponent:component)!
    }

    return title
}

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