Cross Platform
Android
iOS
Mac

Part 5 - Using XCode, Interface Builder, and Storyboards

Apple released a new WYSIWYG way to create iOS applications with Xcode 4.2/iOS5 called a Storyboard. A Storyboard can contain many screen layouts and define the navigation between each screen (which is called a ‘segue’). Storyboards are edited with Xcode’s Interface Builder, where you can drag objects onto the design surface, edit their properties and create outlets to link them with your code.

Interface Builder and Storyboards are currently not supported in Xamarin.iOS for Visual Studio.

Storyboarding UITableView

Storyboards also introduced a new way to design tables, where you can edit the cell layouts in the table

Prototype Content

A UITableView with prototype content is typically intended to display a list of data where the prototype cell (or cells, as you can define more than one) are re-used for each item in the list. The cells don’t need to be instantiated, they are obtained in the GetView method by calling the DequeueReusableCell method of its UITableViewSource.

Static Content

UITableViews with static content are a new feature in Xcode 4.2 that allow tables to be designed WYSIWYG. Cells can be dragged into the table and customized by changing properties and adding controls. In previous versions of Xcode each cell would be a separate XIB file.

The StoryboardTable example contains a simple master-detail app that uses both types of UITableView in a Storyboard. The remainder of this section describes how to build a small to-do list example that will look like this when complete:

The user-interface will be built with a storyboard, and both screens will use a UITableView. The main screen uses prototype content to layout the row and the detail screen uses static content to create a data-entry form using custom cell layouts.

Creating a Storyboard-driven app

Create a new solution in Xamarin Studio using File > New > Solution > iOS >iPhone Storyboard > Master-Detail Application.

The solution will open with some c# files and a MainStoryboard.storyboard file already created. Double-click the storyboard file to open it with Xcode’s Interface Builder.

The default Master-Detail storyboard looks like this:

Without any further changes the application can be started in the simulator and it will be possible to navigate between the two views. This will be the foundation of the StoryboardTable example.

Modifying the Storyboard

The storyboard will be edited in two steps:

  • First, layout the required view controllers and set their properties. Once this is done the changes must be synchronized with the Xamarin.iOS solution by switching to Xamarin Studio.
  • Secondly, add the required UIKit class to each view and create outlets so they can be referenced in code.

Once the storyboard is complete with outlets, code can be added to make everything work.

Layout The View Controllers

The first change to the storyboard is deleting the existing Detail view and replacing it with a UITableViewController. Follow these steps:

  • Select the Detail view and delete it.
  • Drag a UITableViewController onto the Storyboard from the Object Library.
  • Create a segue from the Master to the view that was just added. To create the segue, Control+drag from the Detail cell to the newly added UITableViewController.
  • Select the new segue you created and give it an identifier, which will be used in code to reference this segue. Click on the segue and type “TaskSegue” for the Identifier in the Attributes Inspector, like this:



Then configure the two table views by selecting them and switching to the Attributes Inspector as shown:

  • Change the Master to be Content: Dynamic Prototypes (the view on the storyboard will be labelled Prototype Content).
  • Change the new UITableViewController to be Content: Static Cells.

Finally, the new UITableViewController must have its class name and identifier set. Select the view and type “TaskDetailViewController” for the Class in the Identity Inspector.

Switch to the Attributes Inspector and enter “task” for the view’s Identifier. This will be used later to load this view in c# code.

The storyboard design surface now looks like this (the Master view’s title has been changed to “TaskyBoard”):

Before continuing, a header file needs to be created for the new view that was added. Save the storyboard and switch to Xamarin Studio so that it can synchronize the Xamarin.iOS project with the storyboard.

Wait for the Xcode project updated message to appear in Xamarin Studio’s status bar, then return to Xcode.

Create the UI

Now that the views and segues are configured, the user interface elements need to be added.

First select the prototype cell in the master view controller and open the Attributes Inspector to set the Identifier, which will be used later in code to retrieve instances of this UITableViewCell.

Use “taskcell” for the identifier, as shown:

Then create a button that will add new tasks.

  • Drag a Bar Button Item into the navigation bar
  • In the Attributes Inspector select Identifier: Add (to make it a + plus button)
  • Create an outlet for the button by Control-dragging from it to RootViewController.h (using the Assistant Editor if it is not already visible). Name the outlet appropriately (eg. AddButton).

The detail view requires a lot more work. Table View Cells need to be dragged onto the view and then populated with labels, text views and buttons. This screenshot shows a cell being dragged into a table (the first three have already been done and populated with controls):

The steps to build the complete layout are:

Select the table view and open the Attributes Inspector. Update the following properties:

  • Sections: 2
  • Style: Grouped
  • Separator: None
  • Selection: No Selection

Now drag three cells into first section from the Object Library. For each cell open the Attributes Inspector and set:

  • Style: Custom
  • Identifier: choose a unique identifier for each cell (eg. “title”, “notes”, “done”).
  • Drag the required controls to produce the layout shown in the screenshot (place UILabel, UITextField and UISwitch on the correct cells, and set the labels appropriately, ie. Title, Notes and Done).

Drag a single cell into second section and grab the bottom resize handle to make it taller.

  • Set the Identifier: to a unique value in the Attributes Inspector (eg. “save”).
  • Set the Background: Clear Color.
  • Drag two buttons onto the cell and set their titles appropriately (ie. Save and Delete).

Lastly, wire up outlets for the input controls to TaskDetailViewController.h file. Hold down the Control key and drag from each control into the TaskDetailViewController.h file. Name the outlets appropriately (eg. TitleText, NotesText, DoneSwitch, DeleteButton, SaveButton).

This screenshot shows all the outlets already created in the .h file.

Save the storyboard and switch to Xamarin Studio so that it can synchronize the Xamarin.iOS project with the storyboard.

When the Xcode project updated message appears in Xamarin Studio’s status bar the solution is ready to add some code.

Adding Code

The remainder of the work will be done in Xamarin Studio with c#.

Create a RootTableSource class that inherits from UITableViewSource. The only difference to a non-Storyboard table view is the GetView method doesn’t need to instantiate any cells – the DequeueReusableCell method will always return an instance of the prototype cell (with matching identifier).

The code below is from the RootTableSource.cs file:

public class RootTableSource : UITableViewSource {
    // there is NO database or storage of Tasks in this example, just an in-memory List<>
    Task[] tableItems;
    string cellIdentifier = "taskcell"; // set in the Storyboard
    public RootTableSource (Task[] items)
    {
        tableItems = items; 
    }
    public override int RowsInSection (UITableView tableview, int section)
    {
        return tableItems.Length;
    }
    public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
    {
        // in a Storyboard, Dequeue will ALWAYS return a cell, 
        UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
        // now set the properties as normal
        cell.TextLabel.Text = tableItems[indexPath.Row].Name;
        if (tableItems[indexPath.Row].Done) 
            cell.Accessory = UITableViewCellAccessory.Checkmark;
        else
            cell.Accessory = UITableViewCellAccessory.None;
        return cell;
    }
    public Task GetItem(int id) {
        return tableItems[id];
    }
}

To use the RootTableSource class, create a new collection in the RootViewController’s constructor:

tasks = new List<Task> {
        new Task() {Name="Groceries", Notes="Buy bread, cheese, apples", Done=false},
        new Task() {Name="Devices", Notes="Buy Nexus, Galaxy, Droid", Done=false}
};

In ViewWillAppear pass the collection to the source and assign to the table view:

TableView.Source = new RootTableSource(tasks.ToArray ());

The main screen will now load and display a list of two tasks. When a task is touched the segue defined by the storyboard will cause the detail screen to appear, but it will not display any data until we have wired it up.

To ‘send a parameter’ in a segue override the PrepareForSegue method and set properties on the DestinationViewController. The DestinationViewController class will have been instantiated but not yet displayed to the user – this means you can set properties on the class but not modify any UI controls.

public override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender)
{
    if (segue.Identifier == "TaskSegue") { // set in Storyboard
        var navctlr = segue.DestinationViewController as TaskDetailViewController;
        if (navctlr != null) {
            var source = TableView.Source as RootTableSource;
            var rowPath = TableView.IndexPathForSelectedRow;
            var item = source.GetItem(rowPath.Row);
            navctlr.SetTask (this, item); // to be defined on the TaskDetailViewController
        }
    }
}

In TaskDetailViewController the SetTask method assigns its parameters to properties so they can be referenced in ViewWillAppear. The control properties cannot be modified in SetTask because may not exist when PrepareForSegue is called.

Task currentTask {get;set;}
public RootViewController Delegate {get;set;} // will be used to Save, Delete later
public void SetTask (RootViewController d, Task task) {
    Delegate = d;
    currentTask = task;
}
public override void ViewWillAppear (bool animated)
{
    base.ViewWillAppear (animated);
    TitleText.Text = currentTask.Name;
    NotesText.Text = currentTask.Notes;
    DoneSwitch.On = currentTask.Done;
}

The segue will now open the detail screen and display the selected task information. Unfortunately there is no implementation for the Save and Delete buttons. Before implementing the buttons, add these methods to RootViewController.cs to update the underlying data and close the detail screen.

public void SaveTask (Task task) {
    var oldTask = tasks.Find(t => t.Id == task.Id);
    oldTask = task;
    NavigationController.PopViewControllerAnimated(true);
}
public void DeleteTask (Task task) {
    var oldTask = tasks.Find(t => t.Id == task.Id);
    tasks.Remove (oldTask);
    NavigationController.PopViewControllerAnimated(true);
}

Now add the button TouchUpInside handlers to TaskDetailViewController.cs in ViewDidLoad. The Delegate property reference to the RootViewController was created specifically so we can call SaveTask and DeleteTask, which close this view as part of their operation.

SaveButton.TouchUpInside += (sender, e) => {
    currentTask.Name = TitleText.Text;
    currentTask.Notes = NotesText.Text;
    currentTask.Done = DoneSwitch.On;
    Delegate.SaveTask(currentTask);
};
DeleteButton.TouchUpInside += (sender, e) => {
    Delegate.DeleteTask(currentTask);
};

The last remaining piece of functionality to build is the creation of new tasks. In RootViewController.cs add a method that creates new tasks and opens the detail view. To instantiate a view from a storyboard use the InstantiateViewController method with the Identifier for that view.

public void CreateTask () {
    // first, add the task to the underlying data
    var newId = tasks[tasks.Count - 1].Id + 1;
    var newTask = new Task(){Id=newId};
    tasks.Add (newTask);
    // then open the detail view to edit it
    var detail = Storyboard.InstantiateViewController("detail") as TaskDetailViewController;
    detail.SetTask (this, newTask);
    NavigationController.PushViewController (detail, true);
}

Finally, wire up the button in the navigation bar to call it.

AddButton.Clicked += (sender, e) => {
    CreateTask ();
};

That completes the Storyboard example – the finished app looks like this:

The example demonstrates:

  • Creating a table with Prototype Content where cells are defined for re-use to display lists of data.
  • Creating a table with Static Content to build an input form. This included changing the table style and adding sections, cells and UI controls.
  • How to create a segue and override the PrepareForSegue method to notify the target view of any parameters it requires.
  • Loading storyboard views directly with the Storyboard.InstantiateViewController method.