Skip to content

Instantly share code, notes, and snippets.

@praeclarum
Created April 7, 2014 16:57
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save praeclarum/10024108 to your computer and use it in GitHub Desktop.
Save praeclarum/10024108 to your computer and use it in GitHub Desktop.
This is my UITableViewController with DataSource property that listens for INotifyCollectionChanged
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace Praeclarum.UI
{
[Register ("ObservableTableViewController")]
public class ObservableTableViewController : UITableViewController
{
public UITableViewRowAnimation AddAnimation { get; set; }
public UITableViewRowAnimation DeleteAnimation { get; set; }
object dataSource;
IList list;
INotifyCollectionChanged notifier;
System.Threading.Thread mainThread;
bool loadedView = false;
public object DataSource {
get {
return dataSource;
}
set {
if (dataSource == value)
return;
if (notifier != null) {
notifier.CollectionChanged -= HandleCollectionChanged;
}
dataSource = value;
list = value as IList;
notifier = value as INotifyCollectionChanged;
if (notifier != null) {
notifier.CollectionChanged += HandleCollectionChanged;
}
if (loadedView) {
TableView.ReloadData ();
}
}
}
public ObservableTableViewController ()
: base (UITableViewStyle.Plain)
{
Initialize ();
}
public ObservableTableViewController (UITableViewStyle withStyle)
: base (withStyle)
{
Initialize ();
}
void Initialize ()
{
mainThread = System.Threading.Thread.CurrentThread;
AddAnimation = UITableViewRowAnimation.Automatic;
DeleteAnimation = UITableViewRowAnimation.Automatic;
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
TableView.Source = CreateSource ();
loadedView = true;
}
protected virtual ObservableTableSource CreateSource ()
{
return new ObservableTableSource (this);
}
protected virtual UITableViewCell CreateCell (NSString reuseId)
{
return new UITableViewCell (UITableViewCellStyle.Default, reuseId);
}
protected virtual void BindCell (UITableViewCell cell, object item, NSIndexPath indexPath)
{
cell.TextLabel.Text = item.ToString ();
}
protected virtual void OnRowSelected (object item, NSIndexPath indexPath)
{
}
void HandleCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
if (!loadedView)
return;
NSAction act = () => {
if (e.Action == NotifyCollectionChangedAction.Reset) {
TableView.ReloadData ();
}
if (e.Action == NotifyCollectionChangedAction.Add) {
var count = e.NewItems.Count;
var paths = new NSIndexPath[count];
for (var i = 0; i < count; i++) {
paths [i] = NSIndexPath.FromRowSection (e.NewStartingIndex + i, 0);
}
TableView.InsertRows (paths, AddAnimation);
} else if (e.Action == NotifyCollectionChangedAction.Remove) {
var count = e.OldItems.Count;
var paths = new NSIndexPath[count];
for (var i = 0; i < count; i++) {
paths [i] = NSIndexPath.FromRowSection (e.OldStartingIndex + i, 0);
}
TableView.DeleteRows (paths, DeleteAnimation);
}
};
var isMainThread = System.Threading.Thread.CurrentThread == mainThread;
if (isMainThread) {
act ();
} else {
NSOperationQueue.MainQueue.AddOperation (act);
NSOperationQueue.MainQueue.WaitUntilAllOperationsAreFinished ();
}
}
protected class ObservableTableSource : UITableViewSource
{
readonly ObservableTableViewController controller;
static readonly NSString reuseId = new NSString ("C");
public ObservableTableSource (ObservableTableViewController controller)
{
this.controller = controller;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
var item = controller.list != null ? controller.list [indexPath.Row] : null;
try {
controller.OnRowSelected (item, indexPath);
} catch (Exception ex) {
Debug.WriteLine (ex);
}
}
public override int NumberOfSections (UITableView tableView)
{
return 1;
}
public override int RowsInSection (UITableView tableview, int section)
{
var coll = controller.list;
return coll != null ? coll.Count : 0;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (reuseId) ??
controller.CreateCell (reuseId);
try {
var coll = controller.list;
if (coll != null) {
var obj = coll[indexPath.Row];
controller.BindCell (cell, obj, indexPath);
}
return cell;
}
catch (Exception ex) {
Debug.WriteLine (ex);
}
return cell;
}
}
}
}
@praeclarum
Copy link
Author

You can use this class as simply as:

PresentViewController (new ObservableTableViewController {
    DataSource = new[] { "Hello", "world" }
}, true, null);

Of course, to make it reactive, you will want to give it a collection that implements INotifyCollectionChanged. For example:

var items = new ObservableCollection<string> { "Hello", "world" };
var controller = new ObservableTableViewController { DataSource = items, };
await PresentViewControllerAsync (controller, true);

To customize the cells, derive a new class and override the CreateCell and BindCell methods. To get full control, override CreateSource to return your own custom UITableViewSource.

This class can handle notification events from non-UI threads.

@qqilihq
Copy link

qqilihq commented Jan 24, 2015

Hi Frank, this code is very interesting for me. I tried to integrate it into a current Xamarin project, and after fixing minor API changes I'm facing the problem, that in the current API of Xamarin, obviously the NSAction class is missing. Do you have any hints how to get it running?

[edit] Fixed it, almost too easy: Replace NSAction with Action. See here. Thanks for your great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment