The MVVMLight framework version 5.3 was released by Laurent Bugnion back in April. This release provides the ObservableTableViewSource
class for Xamarin.iOS. This post presents how to use this class in conjunction with the UITableviewController
.
This post builds upon my earlier posts Xamarin iOS Table Views and MVVM Light and Digging Deeper into XamariniOS Table Views with MVVM Light.
This update removes the workarounds I presented in Digging Deeper into XamariniOS Table Views with MVVM Light and allows the use of the UITableviewController
as I had originally intended.
The application will look and work as previously presenting a simple Task List:-
The source code for this post can be found in GitHub.
Resources
Laurent's blog post Releasing MVVM Light V5.3 to Nuget.
Laurent's Evolve 2016 Presentation page an In-Depth study of the MVVM Light Databinding System. This page has links to the video's, slides and source code for this Evolve presentation.
The GitHub page for the An In-Depth Study of the MVVM Light Databinding System contains the source code for the examples in the presentation. Examples cover the use of binding, Commanding and Lists for Xamarin.Android and Xamarin.iOS
Key Points
The
ObservableTableViewSource
can be used with anObservableCollection
(provided as a property by a view model) to provide a data source for aUITableViewController
. This will update the table view as changes such as inserts and deletes are made to theObservableCollection
.The GetTableViewSource extension method is provided by MVVMLight to create the
ObservableTableViewSource
and wire it up to the ObservableCollection.This post shows how to modify the existing solution to remove the workarounds. For those of you in a hurry jumping to the full class listings for the
TaskListObservableTableSource
andTaskListViewController
provide the code required to get this working, snippets are also provided below.Once a
UITableViewController
has been added to the Storyboard designer and a code behind file generated the code snippets below demonstrate the use of theObservableTableViewSource
. The TodoTasks is aObservableCollection<TaskModel>
property on theTaskListViewModel
. TheTableView
is given the name TasksTableView via the Storyboard designer Properties pad, this allows for it's Source property to be set to theObservableTableViewSource
:-
partial class TaskListViewController : UITableViewController
{
private ObservableTableViewSource<TaskModel> source;
private TaskListViewModel Vm => Application.Locator.TaskList;
public TaskListViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Vm.Initialize ();
source = Vm.TodoTasks.GetTableViewSource (
CreateTaskCell,
BindTaskCell,
factory: () => new TaskListObservableTableSource ());
TasksTableView.Source = source;
}
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;
}
}
Update the Nuget packages
The existing solution can be updated to the version 5.3 of MVVMLight using the Niget package manager. The existing solution shown below indicates that updates are available for the Nuget packages:-
Right click on the Packages entry and select the Update option:-
The MVVMLight License Acceptance dialog is displayed:-
Click the Accept button to accept the license and the Nuget packages will be updated.
We can check the Packages in the Solution Explorer or open the packages.config for one of the apps and verify that the version of MVVMLight is now 5.3.0.0.
Now we can choose the Rebuild All option on the Build menu and verify that the update has not broken anything.
Updating the iOS projects to remove workarounds
The v5.3 release of MVVMLIght provides ObservableTableViewSource
and ObservableCollectionViewSource
classes.
This means we can remove the workaround classes ObservableTableSource and ObservableTableViewController implemented in the post Digging Deeper into Xamarin iOS Table Views with MVVMLight.
Delete the classes ObservableTableSource
, ObservableTableViewController
and TaskListObservableTableViewController
.
Delete the file ExtensionsMvvmLight.cs containing the extension method GetTaskListController.
Open the TaskListObservableTableSource.cs
, the current class listing is:-
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;
}
}
We need to edit it and implement the following changes:-
Add a using statement:- using GalaSoft.MvvmLight.Helpers;
Change the class to derive from
ObservableTableViewSource
withTaskModel
as the generic parameter.Delete the constructor.
Edit the method
CommitEditingStyle
and change the line of code:-
_controller.DataSource.RemoveAt(indexPath.Row);
to:-
DataSource.RemoveAt(indexPath.Row);
The modified TaskListObservableTableSource
full class listing is:-
using UIKit;
using Foundation;
using GalaSoft.MvvmLight.Helpers;
namespace ObservableTables.iOS
{
public class TaskListObservableTableSource : ObservableTableViewSource<TaskModel>
{
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
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 we can open the class TaskListViewController
, the current class listing is:-
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();
Vm.Initialize ();
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 class will be replaced by deleting this and removing the corresponding UIViewController
from the Storyboard designer. We can then drag on a new UITableViewController
from the Toolbox onto the Storyboard designer and re-create the code-behind for the new UITableViewController
.
Delete the controller from the storyboard.
Delete the existing TaskListViewController.cs
code behind file:-
Confirm the deletion by clicking the Delete button:-
Select the TableViewController in the Toolbox and drag onto the Storyboard designer:-
Set the Class property of the TableViewController to TaskListViewController and press Return to create the codebehind class file.
Use the Document Outline pad to select the Table View:-
Next and then set the name in the Properties pad to TasksTableView
Ctrl+drag from the Navigation controller onto the Task List View Controller. choose the Root option on the dialog:-
The Storyboard designer should now look like this:-
Open the TaskListViewController.cs
file. The intial controller code will be:-
public partial class TaskListViewController : UITableViewController
{
public TaskListViewController (IntPtr handle) : base (handle)
{
}
}
The modifications to the original workaround class would be:-
Edit the
TaskListViewController
to derive fromUITableViewController
instead ofUIViewcontroller
. Note this is done by re-generating the code-behind via the Storyboard designer.Delete the private member
tableViewcontroller
.Add a private member variable
source
which is using the newObservableTableViewSource
class provided in version 5.3 of MVVM Light:-
partial class TaskListViewController : UITableViewController
{
private ObservableTableViewSource<TaskModel> source;
private TaskListViewModel Vm => Application.Locator.TaskList;
public TaskListViewController (IntPtr handle) : base (handle)
{
}
- Edit the ViewDidLoad method. We first of all delete the lines:-
tableViewController = Vm.TodoTasks.GetTaskListController(CreateTaskCell, BindTaskCell);
tableViewController.TableView = TasksTableView;
- Add the lines below to create the
TaskListObservableTableSource
:-
source = Vm.TodoTasks.GetTableViewSource (
CreateTaskCell,
BindTaskCell,
factory: () => new TaskListObservableTableSource ());
TasksTableView.Source = source;
We will edit the using statements to those shown below:-
using Foundation;
using System;
using UIKit;
using GalaSoft.MvvmLight.Helpers;
using ObservableTables.ViewModel;
Next we will replace the code generated for us with the full class listing below:-
partial class TaskListViewController : UITableViewController
{
private ObservableTableViewSource<TaskModel> source;
private const string ReuseId = "SampleID";
private TaskListViewModel Vm => Application.Locator.TaskList;
public TaskListViewController (IntPtr handle) : base (handle)
{
}
public UIBarButtonItem AddTaskButton
{
get;
private set;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
Vm.Initialize ();
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);
source = Vm.TodoTasks.GetTableViewSource (
CreateTaskCell,
BindTaskCell,
factory: () => new TaskListObservableTableSource ());
}
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;
}
}
Build and run and the application should now run without the previous workarounds required when using version 5.2 or earlier of MVVMLight.
Thanks for the great work Laurent!