After setting up my solution, my next step was to figure out how to display a list in iOS with the ability to select an item. In WPF, my first thought would be to utilize the ListBox control, bind its ItemsSource to the underlying data, and define an ItemTemplate for how I want each item to look. Not so simple in iOS. There’s a nice list control called the UITableView that provides support for a number of neat things (indexed list, splitting the items into sections/grouping them, selection, etc…). However, to accomplish the most important part, hooking it up to data, you have to define a data source object that you assign to the .Source property of the UITableView. There are a number of ways to accomplish this, but I went with creating a source that inherits from UITableViewSource. Xamarin has a nice guide that I used for guidance (Working with Tables and Cells).
Coming from WPF and MVVM I wanted to make this source reusable rather than following all the examples that were hardcoded to a specific object type. So I decided to create an ObjectTableSource, and since I’m still in the early stages of development I decided to make use of the built-in styles for the cell appearance rather than making a custom cell. With this in mind I needed to make sure that the objects provided to my table source had specific properties for me to utilize so I created an interface called ISupportTableSourceand used that as the .
public interface ISupportTableSource { string Text { get; } string DetailText { get; } string ImageUri { get; } }
public class ObjectTableSource : UITableViewSource
When inheriting from UITableViewSource you must override RowsInSection and GetCell. RowsInSection is exactly what it sounds like, you return how many items are in that section. My current version only supports 1 section so it returns the total number of items. GetCell returns the prepared UITableViewCell. The UITableView supports virtualization of its cell controls so in order to get the cell you need to call the DequeueReusableCellmethod on the table view. In versions earlier than iOS 6 this will return null if the cell hasn’t been created yet. In iOS 6 and later you can choose to register a cell type with the UITableView and that will make it so a cell is always returned. However, going this path means that you can’t specify which of the 4 build in styles to use (since it is specified in the constructor only), so I refrained from registering the cell type and handle null. When preparing the cell I also loaded any images on a background thread so the UI is still responsive, but I’ll cover that in another post.
public override int RowsInSection( UITableView tableview, int section ) { return _items.Count; } public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath ) { // if there are no cells to reuse, create a new one UITableViewCell cell = tableView.DequeueReusableCell( CellId ) ?? new UITableViewCell( _desiredCellStyle, CellId ); ISupportTableSource item = _items[indexPath.Row]; if ( !String.IsNullOrEmpty( item.Text ) ) { cell.TextLabel.Text = item.Text; } if ( !String.IsNullOrEmpty( item.DetailText ) ) { cell.DetailTextLabel.Text = item.DetailText; } if ( !String.IsNullOrEmpty( item.ImageUri ) ) { cell.ImageView.ShowLoadingAnimation(); LoadImageAsync( cell, item.ImageUri ); } return cell; }
The remaining thing for making this usable was to provide a way to notify when an item has been selected. There isn’t any event on the UITableViewSource like I expected, instead I needed to override the RowSelectedmethod and fire my own event providing the object found at the selected row.
public override void RowSelected( UITableView tableView, NSIndexPath indexPath ) { ISupportTableSource selectedItem = _items[indexPath.Row]; // normal iOS behaviour is to remove the blue highlight tableView.DeselectRow( indexPath, true ); OnItemSelected( selectedItem ); }