This post continues on from the previous post Xamarin iOS Table Views and MVVM Light.
It is part of a series of posts investigating how to use the MVVM pattern on the Xamarin platforms using shared View Models/Models/Services.
Update 9th July 2016:- A new release of MVVM Light version 5.3 provides a ObservableTableViewSource
class for Xamarin.iOS. This class allows the UITableViewController
to be used correctly and removes for the workaround solutions described in this post. Please see the post Using the ObservableTableViewSource in MVVM Light V5.3 with Xamarin.iOS
Overview
In this post we delve a little deeper into using MVVM Light with Xamarin iOS TableViews, specifically how to customise the TableView to enable swipe to delete.
In attempting to enable this feature I ran into a shortcoming with the implementation of the MVVM Light ObservableTableViewController
. This class derives from the UITableViewController
, which provides as a convenience the methods CommitEditingStyle and CanEditRow amongst others. These methods are used to enable the swipe to delete but I found that attempting to derive from ObservableTableViewController
and then overriding these methods didn’t work. I may have misunderstood something but ultimately ended up copying the MVVM Light classes and modifying them slightly to implement the swipe to delete feature.
Resources
- The Xamarin docs for Tables and Cells Part 4 - Editing
Key Points
The MVVM Light
ObservableTableViewController
uses the classObservableTableSource
which derives fromUITableViewSource
. This base class contains the virtual methods CommitEditingStyle and CanEditRow which theUITableViewController
also provides as a convenience.To implement the swipe to delete was in fact relatively straight forward, I copied the
ObservableTableViewController
and modified theObservableTableSource
class to be public and made some minor modifications to theObservableTableViewController
so support this, mainly changing some private members to protected and protected methods to public. I could then derive from theObservableTableSource
and implement the overrides of the CommitEditingStyle and CanEditRow methods and derive fromObservableTableViewController
and override the CreateSource method to create the derivedObservableTableSource
.
The Example Step by Step
The example app extends the previous app with swipe to delete.
The code for this example is in GitHub.
The class names I have used are somewhat long but they reflect their intention and the source they derive from.
Taking the source code for the previous post as a starting point download the MVVM Light source code and copy the ObservableTableViewController class from the project Galasoft.MvvmLight.Platform (iOS) in the Helpers folder to the ObservableTables.iOS project. Extract the class ObservableTableSource and place in it’s own file.
The code listings that follow included the original commented out code so that the changes can be easily seen, they are in fact minimal.
The class diagram below reflects the new structure:-
Edit the ObservableTableViewController class with the following changes:-
Change the _tableSource to protected.
/*private*/ protected ObservableTableSource<T> _tableSource;
Change the BindCell method to public.
/*protected*/ public virtual void BindCell(UITableViewCell cell,
object item,
NSIndexPath indexPath)
Change the CreateCell method to public.
/*protected*/ public virtual UITableViewCell CreateCell(NSString reuseId)
Change the OnRowSelected method to public.
/*protected*/ public virtual void OnRowSelected(object item,
NSIndexPath indexPath)
Edit the ObservableTableSource class with the following changes:-
Change the class to public.
/*protected*/ public class ObservableTableSource<T2> : UITableViewSource
Change the _controller to public.
/*private*/ protected readonly ObservableTableViewController<T2> _controller;
Change the GetCell method to use the DataSource property
public override UITableViewCell GetCell(UITableView tableView,
NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(_reuseId) ??
_controller.CreateCell(_reuseId);
try
{
//var coll = _controller._dataSource;
var coll = _controller.DataSource;
Change the RowSelected method to use the DataSource property.
public override void RowSelected(UITableView tableView,
NSIndexPath indexPath)
{
//var item = _controller._dataSource != null ?
_controller._dataSource[indexPath.Row] : default(T2);
var item = _controller.DataSource != null ?
_controller.DataSource[indexPath.Row] : default(T2);
Change the RowsInSection method to use the DataSource property.
public override nint RowsInSection(UITableView tableView,
nint section)
{
//var coll = _controller._dataSource;
var coll = _controller.DataSource;
Now create a class TaskListObservableTableSource
which derives from ObservableTableSource
and adds overrides of the methods CanEditRow, CommitEditingStyle and EditingStyleForRow to implement the swipe to delete functionality.
using System;
using UIKit;
using Foundation;
namespace ObservableTables.iOS
{
public class TaskListObservableTableSource<T> :
ObservableTableSource<T>
{
public TaskListObservableTableSource(
ObservableTableViewController<T> controller)
: base(controller)
{
}
public override bool CanEditRow(UITableView tableView,
NSIndexPath indexPath)
{
return true;
}
public override void CommitEditingStyle(UITableView tableView,
UITableViewCellEditingStyle editingStyle,
NSIndexPath indexPath)
{
switch (editingStyle)
{
case UITableViewCellEditingStyle.Delete:
// remove the item from the underlying data source
_controller.DataSource.RemoveAt(indexPath.Row);
// No need to delete the row from the table as the tableview
// is bound to the data source
break;
}
}
public override UITableViewCellEditingStyle EditingStyleForRow(
UITableView tableView,
NSIndexPath indexPath)
{
return UITableViewCellEditingStyle.Delete;
}
}
}
Next create a class TaskListObservableTableViewController
which derives from ObservableTableViewController
. This class will override the CreateSource method to create a new instance of the TaskListObservableTableSource
.
using UIKit;
namespace ObservableTables.iOS
{
public class TaskListObservableTableViewController<T> :
ObservableTableViewController<T>
{
public TaskListObservableTableViewController()
: base()
{
}
public TaskListObservableTableViewController(
UITableViewStyle tableStyle)
: base(tableStyle)
{
}
protected override ObservableTableSource<T> CreateSource()
{
_tableSource = new TaskListObservableTableSource<T>(this);
return _tableSource;
}
}
}
Create a static extensions class named ExtensionsMvvmLight
and create an extension method GetTaskListController based on the GetController method in the ExtensionsApple
class in the MVVM Light Galasoft.MvvmLight.Platform (iOS) project Helpers folder. This method is called in the TaskListViewController
to create the TaskListObservableViewController
.
using System;
using System.Collections.Generic;
using Foundation;
using UIKit;
namespace ObservableTables.iOS
{
public static class ExtensionsMvvmLight
{
public static TaskListObservableTableViewController<T>
GetTaskListController<T>(
this IList<T> list,
Func<NSString, UITableViewCell> createCellDelegate,
Action<UITableViewCell, T, NSIndexPath> bindCellDelegate)
{
return new TaskListObservableTableViewController<T>
{
DataSource = list,
CreateCellDelegate = createCellDelegate,
BindCellDelegate = bindCellDelegate
};
}
}
}
Edit the TaskListViewController
class such that the private member variable tableViewController is now of the type TaskListObservableTableViewContoller
and call the GetTaskListController extension method in the ViewDidLoad method:-
using Foundation;
using System;
using UIKit;
using GalaSoft.MvvmLight.Helpers;
using ObservableTables.ViewModel;
namespace ObservableTables.iOS
{
partial class TaskListViewController : UIViewController
{
private TaskListObservableTableViewController<TaskModel>
tableViewController;
private TaskListViewModel Vm => Application.Locator.TaskList;
public TaskListViewController (IntPtr handle) : base (handle)
{
}
public UIBarButtonItem AddTaskButton
{
get;
private set;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
AddTaskButton = new UIBarButtonItem(UIBarButtonSystemItem.Add);
this.NavigationItem.SetLeftBarButtonItem (AddTaskButton, false);
//note this was needed when deploying to a real iPhone but worked
//on the simulator without it.
//The Argument Exception Event not found clicked was thrown
AddTaskButton.Clicked += (sender, e) => {};
AddTaskButton.SetCommand("Clicked", Vm.AddTaskCommand);
tableViewController =
Vm.TodoTasks.GetTaskListController(CreateTaskCell,
BindTaskCell);
tableViewController.TableView = TasksTableView;
}
private void BindTaskCell(UITableViewCell cell,
TaskModel taskModel,
NSIndexPath path)
{
cell.TextLabel.Text = taskModel.Name;
cell.DetailTextLabel.Text = taskModel.Notes;
}
private UITableViewCell CreateTaskCell(NSString cellIdentifier)
{
var cell = new UITableViewCell(UITableViewCellStyle.Subtitle,
null);
cell.TextLabel.TextColor = UIColor.FromRGB(55, 63, 255);
cell.DetailTextLabel.LineBreakMode =
UILineBreakMode.TailTruncation;
return cell;
}
}
}
This should complete the example which now has the swipe to delete feature.