Working with Tables in the iOS Designer
Storyboards are a WYSIWYG way to create iOS applications, and are supported inside Visual Studio on Mac and Windows. 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
UITableView
s 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 Visual Studio using (Create) New Project… > Single View App(C#), and call it StoryboardTables.
The solution will open with some C# files and a Main.storyboard
file already created. Double-click the Main.storyboard
file to open it in the iOS Designer.
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 objects 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:
Select the bar at the bottom of the View Controller and delete it.
Drag a Navigation Controller and a Table View Controller onto the Storyboard from the Toolbox.
Create a segue from the Root View Controller to the second Table View Controller that was just added. To create the segue, Control+drag from the Detail cell to the newly added UITableViewController. Choose the option Show under Segue Selection.
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:
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.
Change the Root View Controller to be Content: Dynamic Prototypes (the View on the Design Surface will be labelled Prototype Content ):
Change the new UITableViewController to be Content: Static Cells.
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 the Solution Pad. Enter the StoryboardID as detail, as illustrated in the example below. This will be used later to load this view in C# code:The storyboard design surface should now look like this (the Root View Controller's navigation item title has been changed to “Chore Board”):
Create the UI
Now that the views and segues are configured, the user interface elements need to be added.
Root 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 an instance of this UITableViewCell:
Next, you'll need to create a button that will add new tasks, as illustrated below:
Do the following:
- Drag a Bar Button Item from the Toolbox to the right hand side of the navigation bar.
- In the Properties Pad, under Bar Button Item select Identifier: Add (to make it a + plus button).
- Give it a Name so that it can be identified in code at a later stage. Note that you will need to give the Root View Controller a Class Name (for example ItemViewController) to allow you to set the Bar button item's name.
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
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 of the cell to make it taller.
Set the Identifier: to a unique value (eg. “save”).
Set the Background: Clear Color .
Drag two buttons onto the cell and set their titles appropriately (i.e. Save and Delete), as illustrated below:
At this point you may also want to set constraints on your cells and controls to ensure an adaptive layout.
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 Visual Studio on Mac or Windows with C#. Note that the property names used in code reflect those set in the walkthrough above.
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 Chores {
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<>
Chores[] tableItems;
string cellIdentifier = "taskcell"; // set in the Storyboard
public RootTableSource(Chores[] items)
{
tableItems = items;
}
public override nint RowsInSection(UITableView tableview, nint 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 Chores GetItem(int id)
{
return tableItems[id];
}
To use the RootTableSource
class, create a new collection in the ItemViewController
’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:
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
TableView.Source = new RootTableSource(chores.ToArray());
}
If you run the app now, 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 at the moment.
To ‘send a parameter’ in a segue, override the PrepareForSegue
method and set properties on the DestinationViewController
(the TaskDetailViewController
in this example). The Destination View Controller class will have been instantiated but is 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 ItemViewController 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 (ItemViewController 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 ItemViewController.cs to update the underlying data and close the detail screen:
public void SaveTask(Chores chore)
{
var oldTask = chores.Find(t => t.Id == chore.Id);
NavigationController.PopViewController(true);
}
public void DeleteTask(Chores chore)
{
var oldTask = chores.Find(t => t.Id == chore.Id);
chores.Remove(oldTask);
NavigationController.PopViewController(true);
}
Next, you'll need to add the button's TouchUpInside
event handler to the ViewDidLoad
method of TaskDetailViewController.cs. The Delegate
property reference to the ItemViewController
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 ItemViewController.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 ItemViewController.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.