Using the ObservableTableViewSource in MVVM Light V5.3 with Xamarin.iOS

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

Key Points

  • The ObservableTableViewSource can be used with an ObservableCollection (provided as a property by a view model) to provide a data source for a UITableViewController. This will update the table view as changes such as inserts and deletes are made to the ObservableCollection.

  • 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 and TaskListViewController 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 the ObservableTableViewSource. The TodoTasks is a ObservableCollection<TaskModel> property on the TaskListViewModel. The TableView is given the name TasksTableView via the Storyboard designer Properties pad, this allows for it's Source property to be set to the ObservableTableViewSource :-

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 with TaskModel 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 from UITableViewController instead of UIViewcontroller. 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 new ObservableTableViewSource 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!

Richard Woollcott

Read more posts by this author.

Taunton, United Kingdom