Cross Platform
Android
iOS
Mac
Test Cloud

Part 5 – Working with Tables in the iOS Designer

In the previous sections we explored developing using Tables. In this, the fifth and final section, we will aggregate what we have learned so far and create a basic "Chore List" application using a Storyboard.

Storyboarding UITableView

Storyboards are a WYSIWYG way to create iOS applications, and are supported inside both Xamarin Studio and Visual Studio. For more information on Storyboards, refer to the Introduction To Storyboards document. Storyboards also allow you to edit the cell layouts in the table, which simplifies developing with tables and cells

When configuring properties of a table view in the iOS Designer, there are two types of cell content you can choose from: Dynamic or Static Prototype Content.

Dynamic 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 allow tables to be designed right on the design surface. Cells can be dragged into the table and customized by changing properties and adding controls.

Creating a Storyboard-Driven App

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.

Walkthrough

Create a new solution in Xamarin Studio using File > New > Solution > iOS > Classic API > 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 in the iOS Designer.

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 three steps:

  • First, layout the required view controllers and set their properties.
  • Second, create your UI by dragging and dropping object onto your view
  • Finally, add the required UIKit class to each view and give various controls a name so they can be referenced in code.

Once the storyboard is complete, 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:

  1. Select the black bar at the bottom of the Detail View and delete it.
  2. Drag a UITableViewController onto the Storyboard from the Object Library.
  3. Create a segue from the Master View Controller to the View Controller that was just added. To createthe segue, Control+drag from the Detail cell to the newly added UITableViewController. Choose the option Push under Segue Selection
  4. Select the new segue you created and give it an identifier to reference this segue in code. Click on the segue and enter TaskSegue for the Identifier in the Properties Pad , like this:

  5. Next, configure the two Table Views by selecting them and using the Properties Pad. Make sure to select View and not View Controller – you can use the Document Outline to help with selection:

  6. Change the Master View to be Content: Dynamic Prototypes (the View on the Design Surface will be labelled Prototype Content ).

  7. Change the new UITableViewController to be Content: Static Cells .

  8. The new UITableViewController must have its class name and identifier set. Select the View Controller and type TaskDetailViewController for the Class in the Properties Pad – this will create a new TaskDetailViewController.cs file in our Solution Pad. Finally, enter the StoryboardID as detail, as illustrated in the example below. This will be used later to load this view in C# code:

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

Create the UI

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

Master View Controller

First, select the prototype cell in the Master View Controller and set the Identifier as taskcell, as illustrated below. This will be used later in code to retrieve instances of this UITableViewCell:

Next, we'll need to create a button that will add new tasks.

  • Drag a Bar Button Item from the Toolbox into the navigation bar.
  • In the Properties Pad, under Bar Button Item select Identifier: Add (to make it a + plus button).
  • Give it a Name. This will enable us to refer to it in code at a later stage.

TaskDetail View Controller

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. The screenshot below shows the finished UI with two sections. One section has three cells, three labels, two text fields and one switch, while the second section has one cell with two buttons:

The steps to build the complete layout are:

Select the table view and open the Property Pad. Update the following properties:

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

Now select the top section and under Properties > Table View Section change Rows to 3, as illustrated below:

For each cell open the Properties Pad 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).

In the second section, set Rows to 1 and grab the bottom resize handle to make it taller.

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

Adding UIKit Class and Naming Controls

There are a few final steps in creating our Storyboard. First we must give each of our controls a name under Identity > Name so they can be used in code later on. Name these as follows:

  • Title UITextField : TitleText
  • Notes UITextField : NotesText
  • UISwitch : DoneSwitch
  • Delete UIButton : DeleteButton
  • Save UIButton : SaveButton

Adding Code

The remainder of the work will be done in Xamarin Studio or Visual Studio with C#.

First we want to create a Chores class, which will provide a way to get and set the value of ID, Name, Notes and the Done Boolean, so that we can use those values throughout the application.

In your Chores class add the following code:

public class Chore {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
  }

Next, Create a RootTableSource class that inherits from UITableViewSource. The difference between this and a non-Storyboard table view is that the GetView method doesn’t need to instantiate any cells – theDequeueReusableCell 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<>
    Chore[] tableItems;
    string cellIdentifier = "taskcell"; // set in the Storyboard

    public RootTableSource (Chore[] items)
    {
      tableItems = items; 
    }
    public override int RowsInSection (UITableView tableview, int section)
    {
      return tableItems.Length;
    }
    public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
    {
      // in a Storyboard, Dequeue will ALWAYS return a cell, 
      var 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 Chore GetItem(int id) {
      return tableItems[id];
    }
  }

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

chores = new List<Chore> {
      new Chore {Name="Groceries", Notes="Buy bread, cheese, apples", Done=false},
      new Chore {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(chores.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:

Chore currentTask {get;set;}
    public MasterViewController Delegate {get;set;} // will be used to Save, Delete later

public override void ViewWillAppear (bool animated)
    {
      base.ViewWillAppear (animated);
      TitleText.Text = currentTask.Name;
      NotesText.Text = currentTask.Notes;
      DoneSwitch.On = currentTask.Done;
    }

    // this will be called before the view is displayed
    public void SetTask (MasterViewController d, Chore task) {
      Delegate = d;
      currentTask = task;
    }

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 MasterViewController.cs to update the underlying data and close the detail screen:

public void SaveTask (Chore chore)
    {
      var oldTask = chores.Find(t => t.Id == chore.Id);
      NavigationController.PopViewControllerAnimated(true);
    }

    public void DeleteTask (Chore chore) 
    {
      var oldTask = chores.Find(t => t.Id == chore.Id);
      chores.Remove (oldTask);
      NavigationController.PopViewControllerAnimated(true);
    }

Now add the button's TouchUpInsideevent handler to the ViewDidLoad method of TaskDetailViewController.cs. The Delegate property reference to the MasterViewController 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 MasterViewController.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 - in this example that will be 'detail':

public void CreateTask () 
    {
      // first, add the task to the underlying data
      var newId = chores[chores.Count - 1].Id + 1;
      var newChore = new Chore{Id = newId};
      chores.Add (newChore);

      // then open the detail view to edit it
      var detail = Storyboard.InstantiateViewController("detail") as TaskDetailViewController;
      detail.SetTask (this, newChore);
      NavigationController.PushViewController (detail, true);
    }

Finally, wire up the button in the navigation bar in MasterViewController.cs's ViewDidLoad method 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.