How To Hugely Simplify Your iOS Development With MarkupKit

MarkupKit is a framework for simplifying development of native iOS applications. It allows developers to construct user interfaces declaratively using a human-readable markup language rather than visually using Interface Builder, similar to how applications are built for Android and .NET.

For example, the following markup creates an instance of UILabel and sets the value of its text property to “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!"

This tutorial introduces the MarkupKit framework by demonstrating how to create the following registration form using markup:

It highlights several of MarkupKit’s key features including:

  • Static table views
  • Custom table view cells
  • Template properties
  • Localization
  • Autolayout

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

Get MarkupKit

MarkupKit is developed as a freely available open-source project on GitHub. Begin by downloading the latest release here.

The file named MarkupKit.framework.tar.gz contains the 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 Application template and click the “Next” button. Name the product “MarkupKitTutorial” and select Swift as the development language.

Add the MarkupKit framework to the project. Select File | Add Files to “MarkupKitTutorial”… and navigate to the location where you unzipped MarkupKit.framework. This will add the framework as a linked library. However, since MarkupKit is not a core iOS framework, it needs to be embedded in the application. To add MarkupKit as an embedded binary, do the following:

  • Select the project root node in the Project Navigator view
  • Select the “MarkupKitTutorial” target
  • Select the “General” tab
  • Delete MarkupKit.framework from the “Linked Frameworks and Libraries” section
  • Drag MarkupKit.framework from the Project Navigator to the “Embedded Binaries” section

MarkupKit will now be bundled with the application.

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

Modify the controller to extend UITableViewController instead of UIViewController and add the following loadView() override. This is the code that will actually load the view from the markup document we’ll be creating in the next section:

override func loadView() {
    // Load view from markup
    view = LMViewBuilder.viewWithName("ViewController", owner: self, root: nil)
}

Leave the automatically generated viewDidLoad() method as is for now, but remove the didReceiveMemoryWarning() method; it is not needed in this example.

Your view controller class should now look something like this:

import UIKit
import MarkupKit

class ViewController: UITableViewController {
    override func loadView() {
        // Load view from markup
        view = LMViewBuilder.viewWithName("ViewController", owner: self, root: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // ...
    }
}

Remove the Main Storyboard

Although MarkupKit can optionally be used in conjunction with storyboards, we will not be doing so in this example. Remove the main application storyboard that was automatically generated by Xcode:

  • In Project Navigator, delete Main.storyboard
  • Select the project root node
  • Select the “MarkupKitTutorial” target
  • Select the “General” tab
  • Under “Deployment Info”, remove “Main” from the “Main Interface” field

Finally, update the application delegate to instantiate the view controller programmatically. Modify the automatically generated application:didFinishLaunchingWithOptions: method in AppDelegate.swift as follows:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    window = UIWindow()

    window!.rootViewController = UINavigationController(rootViewController: ViewController())

    window!.backgroundColor = UIColor.whiteColor()
    window!.frame = UIScreen.mainScreen().bounds

    window!.makeKeyAndVisible()

    return true
}

This will ensure that the view controller is presented at application startup. You can remove all of the other automatically generated methods; they are not used in this example.

Create the String Table

One of the things we will be demonstrating in this tutorial is MarkupKit’s support for localization. As a result, we’ll need a string table to store our localized content. Create the default string table by selecting File | New | File… from the menu, then selecting iOS/Resource/Strings File from the list of presented template options:

Name the file Localizable.strings. In the newly created file, add entries for the form title and “Submit” button text:

"title" = "Registration Form";
"submit" = "Submit";

We’ll use these values in the next section to localize the controller’s navigation bar content.

Create the Submit Button

Modify the controller’s viewDidLoad() method as follows to set the title and create the “Submit” button using the localized string values we created in the previous section:

override func viewDidLoad() {
    super.viewDidLoad()

    let mainBundle = NSBundle.mainBundle()

    // Set title
    title = mainBundle.localizedStringForKey("title", value: nil, table: nil)

    // Create "submit" button
    navigationItem.rightBarButtonItem = UIBarButtonItem(title: mainBundle.localizedStringForKey("submit", value: nil, table: nil),
        style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ViewController.submitForm))
}

Note that we are associating an action named submitForm with the “Submit” button. This method will be invoked when the user taps the button. Create a stub implementation for this method now:

func submitForm() {
    // Display "form submitted" message
    let alertController = UIAlertController(title: "Status", message: "Form submitted.", preferredStyle: .Alert)

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

    presentViewController(alertController, animated: true, completion: nil)
}

We’ll come back to this method later once the form is complete.

Your view controller should now look something like this:

import UIKit
import MarkupKit

import UIKit
import MarkupKit

class ViewController: UIViewController {
    override func loadView() {
        // Load view from markup
        view = LMViewBuilder.viewWithName("ViewController", owner: self, root: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let mainBundle = NSBundle.mainBundle()

        // Set title
        title = mainBundle.localizedStringForKey("title", value: nil, table: nil)

        // Create "submit" button
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: mainBundle.localizedStringForKey("submit", value: nil, table: nil),
            style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ViewController.submitForm))
    }

    func submitForm() {
        // Display "form submitted" message
        let alertController = UIAlertController(title: "Status", message: "Form submitted.", preferredStyle: .Alert)

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

        presentViewController(alertController, animated: true, completion: nil)
    }
}

Create the View

Now we’re ready to create the markup document that will represent our view. Select File | New | File… from the menu. In the dialog that appears, select iOS/Other/Empty. Name the file ViewController.xml, ensure that the “MarkupKitTutorial” project target is selected, and click the “Create” button.

Add the following markup to the file you just created. It will serve as the starting point for the form we’ll be building over the next few sections:







    

The properties processing instruction (PI) is where we’ll be defining our “template properties”. Template properties are similar to CSS styles. They allow developers to abstract common property definitions into templates, which can then be applied by name to individual view instances. This makes it easy to assign common property values as well as modify properties later.

LMTableView is a MarkupKit-provided subclass of UITableView that facilitates the declaration of table view content. An LMTableView instance acts as its own data source and delegate, serving cells from a statically defined collection of sections and allowing it to be used as a general-purpose layout device. This particular table view will host all of the various fields we’ll be adding to our registration form over the course of the tutorial.

Build and Run the Application

Although it doesn’t do much yet, the application is now ready to run. Ensure that the “MarkupKitTutorial” scheme 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 “Submit” button displays the placeholder alert message we added earlier:

Create the Form Elements

Over the next few sections, we’ll be creating the form elements that we’ll use to collect the user’s registration data. As we add view elements to the markup document, we’ll also be adding corresponding outlets and actions to the controller class, similar to what we might do if we were creating the form using Interface Builder. However, since LMTableView will be handling all of the cell and layout management for us, our final controller class will be smaller and simpler than the Interface Builder version would be.

Name

First, we’ll create the text fields that we’ll use to collect the user’s first and last names. Add the following markup to the LMTableView element in ViewController.xml:



    




    

This markup declares two table view cells: one containing a text field representing the user’s first name, and a second for the user’s last name. Each cell is an instance of LMTableViewCell, a MarkupKit-provided subclass of UITableViewCell that facilitates the declaration of custom table view cell content. Cells are automatically sized to match the width of the table view and the intrinsic height of the content using autolayout. You guessed it right: No custom size or position calculations are required 😉

Property Templates

The “class” attribute is used to apply a property template to a view. It is similar to the “class” attribute in HTML, which associates a set of CSS style declarations with an HTML element. In this case, the attribute refers to the “formField” class, a property template that we’ll be applying to all of the form field cells in our table view.

Property templates are specified using JavaScript Object Notation (JSON). Each template is represented by a dictionary object defined at the top level of the JSON document. The dictionary’s key represents the name of the template, and its contents represent the property values that will be set when the template is applied.

Add the following JSON to the properties PI in ViewController.xml to create the “formField” class:

"formField": {
    "selectionStyle": "none"
}

When the markup is processed, the properties specified by the class will be applied to the table view cells. In this case, the template will set each cell’s “selectionStyle” property to “none”, ensuring that a tap on a cell does not show a highlighted state; since we’re using the cells to display input fields, we want the fields themselves to show a selected or focused state, not the underlying cells.

Outlets

The “id” attribute creates an outlet for a view declared in markup. Using key-value coding (KVC), MarkupKit “injects” the named view instance into the document’s owner (in this case, the view controller), allowing the application to interact with it.

The example markup defines two outlets: one for the first name text field and another for the last name field. Let’s add the outlets to our controller now. Enter the following declarations in ViewController.swift:

@IBOutlet var firstNameTextField: UITextField!
@IBOutlet var lastNameTextField: UITextField!

When the document is loaded, these outlets will be populated with the text field instances we declared in the markup document, just as if they had been defined in a storyboard or created programmatically.

Localization

Note that the values of the text fields’ “placeholder” attributes begin with an “@” symbol. When an attribute value begins with “@”, MarkupKit attempts to look up a localized version of the value before setting the associated property. The value of the attribute (minus the “@” prefix) represents the key that will be used to look up the value.

Add the following values to Localizable.strings so that the text fields’ placeholder properties will be properly localized when the document is loaded:

"first" = "First";
"last" = "Last";

Other Attributes

One additional attribute is used in this example to disable autocorrect, which is typically enabled by default. Setting the value of a text field’s “autocorrectionType” attribute to “no” is equivalent to programmatically setting the value of its autocorrectionType property to UITextAutocorrectionTypeNo, explicitly disabling autocorrect.

Section Header Views

Our final task for this section is to create the header view. Add the following markup to the table view immediately before the declaration of the first name cell:



The sectionHeaderView processing instruction corresponds to a call to the setView:forHeaderInSection: method of LMTableView, which associates a header view with a given section. The view element immediately following the PI is used as the header view for the current section.

In this case, the header view is an instance of UITableViewHeaderFooterView whose text label’s text property is set to the localized value associated with the “name” key. Add this value to Localizable.strings now:

"name" = "Name";

Build and Run the Application

At this point, ViewController.swift should look something like this:

import UIKit
import MarkupKit

class ViewController: UIViewController {
    @IBOutlet var firstNameTextField: UITextField!
    @IBOutlet var lastNameTextField: UITextField!

    override func loadView() {
        // Load view from markup
        view = LMViewBuilder.viewWithName("ViewController", owner: self, root: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let mainBundle = NSBundle.mainBundle()

        // Set title
        title = mainBundle.localizedStringForKey("title", value: nil, table: nil)

        // Create "submit" button
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: mainBundle.localizedStringForKey("submit", value: nil, table: nil),
            style: UIBarButtonItemStyle.Plain, target: self, action: #selector(ViewController.submitForm))
    }

    func submitForm() {
        // Display "form submitted" message
        let alertController = UIAlertController(title: "Status", message: "Form submitted.", preferredStyle: .Alert)

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

        presentViewController(alertController, animated: true, completion: nil)
    }
}

ViewController.xml should look like this:







    
    
    

    
    
        
    

    
    
        
    

Finally, Localizable.strings should look something like this:

"title" = "Registration Form";
"submit" = "Submit";

"name" = "Name";
"first" = "First";
"last" = "Last";

Build and run the application. It should look something like this:

Address

Next, we’ll create the address section. Add the following markup to ViewController.xml, immediately after the last name cell declaration:









    




    

The sectionBreak processing instruction inserts a new section in a table view. It corresponds to a call to the insertSection: method of the LMTableView class (the first section is created implicitly when the table view itself is created).

Like the previous section, this section contains a header view and some text fields with localized placeholders. Add the localized values to Localizable.strings now:

"address" = "Address";
"street" = "Street";
"city" = "City";

Also add the new outlets to ViewController.swift:

@IBOutlet var streetTextField: UITextField!
@IBOutlet var cityTextField: UITextField!

Layout Views

Next, add the following markup below the city cell. The contents of this cell will be used to collect information about the user’s state and zip code:



    
        
            
            

            
            
                
                
                
            
        

        
    

Add the associated string values to Localizable.strings:

"state" = "State";
"zipCode" = "Zip Code";

Finally, add the outlets to ViewController.swift:

@IBOutlet var stateTextField: UITextField!
@IBOutlet var statePickerView: LMPickerView!
@IBOutlet var zipCodeTextField: UITextField!

Unlike the preceding examples, this cell doesn’t simply contain a single text field; it contains two fields, one representing the user’s state and another for the user’s zip code. Both fields are themselves contained in an instance of LMRowView, a MarkupKit-provided view class that automatically arranges its subviews in a horizontal line (kind of similar to LinearLayout in Android).

LMRowView is an example of a “layout view”, a class whose sole responsibility is managing the size and position of its subviews. Layout views allow developers to easily incorporate autolayout into iOS applications without needing to interact with layout constraints directly. Other layout views provided by MarkupKit include LMColumnView, which automatically arranges its subviews in a vertical line, and LMLayerView, which arranges its subviews in layers, like a stack of transparencies. These view classes can be combined to quickly and easily create sophisticated layouts.

View Weights

Note that each text field has a “weight” value associated with it. This value specifies the amount of excess space a view would like to be given within its superview 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.

In this case, the first text field has a weight of 2 and the second field a weight of 1. This means that the first view will be given 2 / (2 + 1), or two thirds of the available space in the row, and the second will be given 1 / (2 + 1), or one third of the available space.

Input Views

While the zip code field in the preceding markup is fairly straightforward, the state field has some additional content associated with it. The inputView processing instruction is used to associate an input view with a text field. It corresponds to the inputView property of the UITextField class:

    
    

    ...

In this case, the input view is an instance of LMPickerView that will be used to present a list of U.S. states to the user. LMPickerView is a MarkupKit-provided subclass of UIPickerView that facilitates the declaration of picker view content. Like LMTableView, LMPickerView acts as its own data source and delegate, serving content from a statically-defined collection of row and component titles.

For example, the following markup could be used to create a picker view containing six rows representing the New England states. The “title” attribute represents the value that the user will see in the picker view. The “value” attribute is optional and is generally used to associate a system-level key or ID with a row. Replace the LMPickerView with “statePickerView” id with the following code:

    
    
    
    
    
    

However, while inline row declarations can be convenient for a small number of fixed options, they are not ideal for a larger number of options, such as the full U.S. state list. Instead, we’ll load the complete state list from a JSON document and populate the picker view programmatically.

To create the list, select File | New | File… from the menu and select iOS/Other/Empty in the dialog that appears. Name the file states.json and add the following values to it:

[
    {"name": "Alabama", "code": "AL"},
    {"name": "Alaska", "code": "AK"},
    {"name": "Arizona", "code": "AZ"},
    {"name": "Arkansas", "code": "AR"},
    {"name": "California", "code": "CA"},
    {"name": "Colorado", "code": "CO"},
    {"name": "Connecticut", "code": "CT"},
    {"name": "Delaware", "code": "DE"},
    {"name": "District of Columbia", "code": "DC"},
    {"name": "Florida", "code": "FL"},
    {"name": "Georgia", "code": "GA"},
    {"name": "Hawaii", "code": "HI"},
    {"name": "Idaho", "code": "ID"},
    {"name": "Illinois", "code": "IL"},
    {"name": "Indiana", "code": "IN"},
    {"name": "Iowa", "code": "IA"},
    {"name": "Kansa", "code": "KS"},
    {"name": "Kentucky", "code": "KY"},
    {"name": "Lousiana", "code": "LA"},
    {"name": "Maine", "code": "ME"},
    {"name": "Maryland", "code": "MD"},
    {"name": "Massachusetts", "code": "MA"},
    {"name": "Michigan", "code": "MI"},
    {"name": "Minnesota", "code": "MN"},
    {"name": "Mississippi", "code": "MS"},
    {"name": "Missouri", "code": "MO"},
    {"name": "Montana", "code": "MT"},
    {"name": "Nebraska", "code": "NE"},
    {"name": "Nevada", "code": "NV"},
    {"name": "New Hampshire", "code": "NH"},
    {"name": "New Jersey", "code": "NJ"},
    {"name": "New Mexico", "code": "NM"},
    {"name": "New York", "code": "NY"},
    {"name": "North Carolina", "code": "NC"},
    {"name": "North Dakota", "code": "ND"},
    {"name": "Ohio", "code": "OH"},
    {"name": "Oklahoma", "code": "OK"},
    {"name": "Oregon", "code": "OR"},
    {"name": "Pennsylvania", "code": "PA"},
    {"name": "Rhode Island", "code": "RI"},
    {"name": "South Carolina", "code": "SC"},
    {"name": "South Dakota", "code": "SD"},
    {"name": "Tennessee", "code": "TN"},
    {"name": "Texas", "code": "TX"},
    {"name": "Utah", "code": "UT"},
    {"name": "Vermont", "code": "VT"},
    {"name": "Virginia", "code": "VA"},
    {"name": "Washington", "code": "WA"},
    {"name": "West Virginia", "code": "WV"},
    {"name": "Wisconsin", "code": "WI"},
    {"name": "Wyoming", "code": "WY"}
]

Next, update ViewController.swift to populate the picker view by adding the following code to the controller’s viewDidLoad method:

// Load state data
let stateData = NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("states", ofType: "json")!)
let states = (try! NSJSONSerialization.JSONObjectWithData(stateData!, options: NSJSONReadingOptions())) as! [[String: AnyObject]]

for state in states {
    statePickerView.insertRow(statePickerView.numberOfRowsInComponent(0), inComponent: 0,
        withTitle: state["name"] as! String, value: state["code"])
}

Now the complete list of U.S. states will be available to the user when the picker view is shown.

Accessory Views

The inputAccessoryView processing instruction is used to associate an input accessory view with a text field. It corresponds to the inputAccessoryView property of the UITextField class. An input accessory view is displayed immediately above the keyboard when it is visible on screen:

    ...

    
    
        
        
        
    

In this case, the accessory view is a toolbar containing “Cancel” and “Done” options. An action named “cancelStateEdit” is associated with the “Cancel” button, and an action named “updateStateText” is associated with the “Done” button. Add these actions to the view controller class now:

@IBAction func cancelStateEdit() {
    // Hide keyboard
    stateTextField.resignFirstResponder()
}

@IBAction func updateStateText() {
    // Update state text field and hide keyboard
    stateTextField.text = statePickerView.titleForRow(statePickerView.selectedRowInComponent(0), forComponent: 0)!
    stateTextField.resignFirstResponder()
}

Tapping the “Cancel” button will dismiss the picker view, and tapping “Done” will dismiss the picker and update the state text field to reflect the selected state value.

Other Attributes

Two additional attributes are used in the preceding example: “keyboardType” and “textAlignment”. Setting the value of a text field’s “keyboardType” attribute to “phonePad” is equivalent to setting the value of its keyboardType property to UIKeyboardTypePhonePad, ensuring that the field will display a keypad designed for entering telephone numbers. Specifying a value of “right” for a text field’s “textAlignment” attribute is equivalent to programmatically setting its textAlignment property to NSTextAlignmentRight, ensuring that the text field’s content will be right-aligned.

Build and Run the Application

Build and run the application. When the state field has the input focus, the picker view will be displayed, allowing the user to select a state from the list:

Contact Information

Next we’ll add fields for collecting contact information for the user including phone number and email address. Add the following markup to ViewController.xml immediately after the state cell:







    
        
        
    




    
        
        
    

Add the associated string values to Localizable.strings:

"contactInformation" = "Contact Information";
"phoneNumber" = "Phone Number";
"emailAddress" = "Email Address";

Add the outlets to ViewController.swift:

@IBOutlet var phoneNumberTextField: UITextField!
@IBOutlet var emailAddressTextField: UITextField!

The markup we just added creates two text fields with associated right views, as specified by the rightView processing instruction. The views are instances of UIImageView that present a hint to the user about the content expected by the field.

The image views refer to a property template named “hintIcon” that we will use to set the icons’ color. Add the following template to the properties PI, making sure that you add a comma after the previous template declaration (otherwise, it won’t be valid JSON):

"hintIcon": {
    "tintColor": "#aaaaaa"
}

Icon Image Sets

You will need to create image sets for the named images, “PhoneIcon” and “EmailIcon”. I used phone and email icons from Google’s material design collection when building the example application. Download the icon archives and unzip them somewhere on your local system:

Select the “Assets.xcassets” folder in Project Navigator and create two new image sets, one named “PhoneIcon” and the other named “EmailIcon”. Use the ic_phone_18pt.png image as the 1x phone icon asset, the ic_phone_18pt_2x.png image as the 2x phone icon asset, and the ic_phone_18pt_3x.png image as the 3x phone icon asset. Repeat this process for the email icons.

Finally, to ensure that the tint color we defined in the “hintIcon” template is correctly applied, we need to mark the image sets as “template images”. Ensure that the “Assets.xcassets” folder is still selected in Project Navigator, and reveal the Attributes Inspector by selecting View | Utilities | Show Attributes Inspector from the menu. Select the “PhoneIcon” image set and select “Template Image” from the “Render As” drop-down list. Repeat this process for the “EmailIcon” image set.

Now, when the icons are loaded, iOS will treat them as template images and will allow the tint color to be applied.

Other Attributes

The “autocapitalizationType” attribute is used in the preceding example to disable auto-capitalization for the email address field. Setting the value of this attribute to “none” is equivalent to programmatically setting the value of the text field’s autocapitalizationType property to UITextAutocapitalizationTypeNone, explicitly disabling auto-capitalization.

Additionally, setting the “rightViewMode” attribute to “always” is equivalent to setting the value of the field’s rightViewMode property to UITextFieldViewModeAlways, ensuring that the right view (i.e. the hint icon) is always visible.

Build and Run the Application

Build and run the application. The hint icons should appear to the right of the phone number and email address text fields with the light gray tint color specified by the template applied:

Date of Birth

Next, we will add a date picker to allow the user to provide a birth date. Add the following markup immediately after the email address cell declaration:








    

Note the use of the “height” attribute, which is used to constrain the height of the date picker to 162 pixels (the default height is larger).

Add the associated string value to Localizable.strings:

"dateOfBirth" = "Date of Birth";

Add the outlet to ViewController.swift:

@IBOutlet var dateOfBirthDatePicker: UIDatePicker!

That’s it! The form will now present an inline date picker allowing the user to select a birth date.

Build and Run the Application

Build and run the application. Scroll down to the bottom of the form. The birth date picker should look something like this:

Gender

Finally, we will create a table view section that allows the user to specify a gender. Add the following markup to ViewController.xml immediately after the date of birth cell:











The sectionName processing instruction corresponds to a call to the setName:forSection: method of LMTableView and is used to associate a text-based name with a section. Identifying sections by name rather than numeric index provides a level of indirection that allows sections to be added or reordered without breaking controller code.

The sectionSelectionMode processing instruction sets the selection mode for a section. It corresponds to a call to the setSelectionMode:forSection: method of LMTableView. Valid values for this PI include “default”, “singleCheckmark”, and “multipleCheckmarks”. The “default” option produces the default selection behavior (the application is responsible for managing selection state). The “singleCheckmark” option ensures that only a single row will be checked in the section at a given time, similar to a group of radio buttons. The “multipleCheckmarks” option causes the checked state of a row to be toggled each time the row is tapped, similar to a group of checkboxes. Since we only want to allow the user to select a single gender at a time, we’re using the “singleCheckmark” selection mode here.

Note that, unlike all of the previous sections, this section uses instances of UITableViewCell rather than LMTableViewCell to represent the gender options. While we could have used LMTableViewCell here as well, this isn’t necessary, since UITableViewCell already allows us to easily present read-only text content via its text label.

The value property is defined by an extension MarkupKit adds to the UITableViewCell class. It is used to associate an optional value with a cell, such as the “M” and “F” gender codes shown above. MarkupKit also adds a boolean checked property to UITableViewCell which, when set, causes a checkmark to appear in the corresponding row. We’ll use these values later when we complete the implementation of the submitForm method.

As with the previous sections, we need to add the localized values to Localizable.strings:

"gender" = "Gender";
"male" = "Male";
"female" = "Female";

However, unlike previous sections, this section does not define any outlets. Instead, we’ll add the following constant to the view controller class, immediately following the date picker outlet:

static let GenderSectionName = "gender"

We’ll use this constant to help retrieve the selected value from the gender section when the form is submitted.

Build and Run the Application

Build and run the application. Scroll down to the bottom of the form. The gender section should look something like the following. Tapping a row updates the selection state for the section:

Submit the Form

The final step in this tutorial is to write the code that simulates the form submission. Way back at the beginning, we stubbed out the code for the submitForm method that will handle this. We’ll now come back and complete the implementation of this method. We’ll leave the placeholder alert we created to provide feeback to the user and make it seem as though something is happening. However, we’ll add some new code to extract the values from the form fields and use them to create a JSON object we might submit to a hypothetical REST service to register the user.

Add the following code to the controller’s submitForm method, immediately before the code that displays the alert:

// Get selected state code
let state = (stateTextField.text!.isEmpty) ? "" : statePickerView.valueForRow(statePickerView.selectedRowInComponent(0), forComponent: 0)!

// Format date of birth
let dateFormatter = NSDateFormatter()

dateFormatter.dateStyle = .ShortStyle
dateFormatter.timeStyle = .NoStyle

let dateOfBirth = dateFormatter.stringFromDate(dateOfBirthDatePicker.date)

// Get selected gender
let genderSection = tableView.sectionWithName(ViewController.GenderSectionName)
let selectedGenderRow = tableView.rowForCheckedCellInSection(genderSection)

let gender = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: selectedGenderRow, inSection: genderSection))!.value!

First, if the user has entered anything in the state field, we get the value of the selected row in the state picker. Next, we retrieve the selected date from the date picker and format it using the “short” date style and no time style. We then look up the index of the gender section using the GenderSectionName constant, use it to retrieve the index of the checked cell in the section, and then use both values to get the value associated with the selected cell.

Finally, we can write the code that will simulate the form submission. Add the following code to submitForm, immediately after the preceding code:

// Simulate form submission
let form = [
    "firstName": firstNameTextField.text!,
    "lastName": lastNameTextField.text!,
    "street": streetTextField.text!,
    "city": cityTextField.text!,
    "state": state,
    "zipCode": zipCodeTextField.text!,
    "phoneNumber": phoneNumberTextField.text!,
    "emailAddress": emailAddressTextField.text!,
    "dateOfBirth": dateOfBirth,
    "gender": gender
]

let data = try! NSJSONSerialization.dataWithJSONObject(form, options: NSJSONWritingOptions.PrettyPrinted)

print(String(data: data, encoding: NSUTF8StringEncoding)!)

This code creates a dictionary instance containing all of the form values we want to submit to the server. It then creates a JSON object from the dictionary and logs the dictionary contents to the console.

Build and Run the Application

Build and run the application. Enter some values in the form and tap the “Submit” button. You should see the placeholder alert we created earlier:

Additionally, you should see something like the following in the console, simulating the form submission to the server:

{
  "city" : "Boston",
  "gender" : "M",
  "street" : "123 Main St.",
  "lastName" : "Smith",
  "phoneNumber" : "6175551212",
  "dateOfBirth" : "6\/1\/96",
  "zipCode" : "02109",
  "firstName" : "John",
  "emailAddress" : "jsmith@xyz.com",
  "state" : "MA"
}

Where To Go From Here

As usual, you can download the final project here.

This tutorial introduced the MarkupKit framework by demonstrating how to build a simple registration form using markup. For more information, including additional examples, please visit the MarkupKit project on GitHub.

Greg Brown
Principal software engineer with 20+ years of experience in consulting, product, and open-source development.