Added ObservableTableViewSource and ObservableCollectionViewSource with corresponding
extension methods and unit tests.
This commit is contained in:
Родитель
7a2877bf34
Коммит
e1358e670f
|
@ -68,6 +68,8 @@
|
|||
<Compile Include="Helpers\BindingGenericApple.cs" />
|
||||
<Compile Include="Helpers\ExtensionsApple.cs" />
|
||||
<Compile Include="Helpers\ObservableTableViewController.cs" />
|
||||
<Compile Include="Helpers\ObservableCollectionViewSource.cs" />
|
||||
<Compile Include="Helpers\ObservableTableViewSource.cs" />
|
||||
<Compile Include="Threading\DispatcherHelper.cs" />
|
||||
<Compile Include="Views\ControllerBase.cs" />
|
||||
<Compile Include="Views\DialogService.cs" />
|
||||
|
|
|
@ -28,25 +28,113 @@ namespace GalaSoft.MvvmLight.Helpers
|
|||
public static class ExtensionsApple
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewController{T}"/> for a given <see cref="ObservableCollection{T}"/>.
|
||||
/// Creates a new <see cref="ObservableCollectionViewSource{TItem, TCell}"/> for a given <see cref="IList{TItem}"/>.
|
||||
/// Note that if the IList doesn't implement INotifyCollectionChanged, the associated UICollectionView won't be
|
||||
/// updated when the IList changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the items contained in the collection.</typeparam>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <typeparam name="TCell">The type of cells in the CollectionView associated to this ObservableCollectionViewSource.</typeparam>
|
||||
/// <param name="list">The IList that should be represented in the associated UICollectionView</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UICollectionViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="getSupplementaryViewDelegate">A delegate to a method returning a <see cref="UICollectionReusableView"/>
|
||||
/// and used to set supplementary views on the UICollectionView.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableCollectionViewSource{TItem, TCell}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableCollectionViewSource.</param>
|
||||
/// <returns>The new instance of ObservableCollectionViewSource.</returns>
|
||||
public static ObservableCollectionViewSource<TItem, TCell> GetCollectionViewSource<TItem, TCell>(
|
||||
this IList<TItem> list,
|
||||
Action<TCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
Func<NSString, NSIndexPath, UICollectionReusableView> getSupplementaryViewDelegate = null,
|
||||
string reuseId = null,
|
||||
Func<ObservableCollectionViewSource<TItem, TCell>> factory = null)
|
||||
where TCell : UICollectionViewCell
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.GetSupplementaryViewDelegate = getSupplementaryViewDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableCollectionViewSource<TItem, TCell>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
GetSupplementaryViewDelegate = getSupplementaryViewDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableCollectionViewSource{TItem, TCell}"/> for a given <see cref="ObservableCollection{TItem}"/>.
|
||||
/// The associated UICollectionView will be updated when the ObservableCollection changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <typeparam name="TCell">The type of cells in the CollectionView associated to this ObservableCollectionViewSource.</typeparam>
|
||||
/// <param name="list">The ObservableCollection that should be represented in the associated UICollectionView</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UICollectionViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="getSupplementaryViewDelegate">A delegate to a method returing a <see cref="UICollectionReusableView"/>
|
||||
/// and used to set supplementary views on the UICollectionView.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableCollectionViewSource{TItem, TCell}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableCollectionViewSource.</param>
|
||||
/// <returns>The new instance of ObservableCollectionViewSource.</returns>
|
||||
public static ObservableCollectionViewSource<TItem, TCell> GetCollectionViewSource<TItem, TCell>(
|
||||
this ObservableCollection<TItem> list,
|
||||
Action<TCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
Func<NSString, NSIndexPath, UICollectionReusableView> getSupplementaryViewDelegate = null,
|
||||
string reuseId = null,
|
||||
Func<ObservableCollectionViewSource<TItem, TCell>> factory = null)
|
||||
where TCell : UICollectionViewCell
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.GetSupplementaryViewDelegate = getSupplementaryViewDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableCollectionViewSource<TItem, TCell>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
GetSupplementaryViewDelegate = getSupplementaryViewDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewController{TItem}"/> for a given <see cref="ObservableCollection{TItem}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items contained in the collection.</typeparam>
|
||||
/// <param name="collection">The collection that the adapter will be created for.</param>
|
||||
/// <param name="createCellDelegate">A delegate to a method creating or reusing a <see cref="UITableViewCell"/>.
|
||||
/// The cell will then be passed to the bindCellDelegate
|
||||
/// delegate to set the elements' properties.</param>
|
||||
/// The cell will then be passed to the bindCellDelegate delegate to set the elements' properties.
|
||||
/// If you use a reuseId, you can pass null for the createCellDelegate.</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.
|
||||
/// The cell must be created first in the createCellDelegate delegate, unless a <see cref="reuseId"/> is passed to the method.</param>
|
||||
/// The cell must be created first in the createCellDelegate delegate, unless a
|
||||
/// reuseId is passed to the method.</param>
|
||||
/// <param name="reuseId">A reuse identifier for the TableView's cells.</param>
|
||||
/// <returns>A controller adapted to the collection passed in parameter.</returns>
|
||||
public static ObservableTableViewController<T> GetController<T>(
|
||||
this ObservableCollection<T> collection,
|
||||
public static ObservableTableViewController<TItem> GetController<TItem>(
|
||||
this ObservableCollection<TItem> collection,
|
||||
Func<NSString, UITableViewCell> createCellDelegate,
|
||||
Action<UITableViewCell, T, NSIndexPath> bindCellDelegate,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null)
|
||||
{
|
||||
return new ObservableTableViewController<T>
|
||||
return new ObservableTableViewController<TItem>
|
||||
{
|
||||
DataSource = collection,
|
||||
CreateCellDelegate = createCellDelegate,
|
||||
|
@ -56,25 +144,26 @@ namespace GalaSoft.MvvmLight.Helpers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewController{T}"/> for a given <see cref="IList{T}"/>.
|
||||
/// Creates a new <see cref="ObservableTableViewController{TItem}"/> for a given <see cref="IList{TItem}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the items contained in the list.</typeparam>
|
||||
/// <typeparam name="TItem">The type of the items contained in the list.</typeparam>
|
||||
/// <param name="list">The list that the adapter will be created for.</param>
|
||||
/// <param name="createCellDelegate">A delegate to a method creating or reusing a <see cref="UITableViewCell"/>.
|
||||
/// The cell will then be passed to the bindCellDelegate
|
||||
/// delegate to set the elements' properties.</param>
|
||||
/// The cell will then be passed to the bindCellDelegate delegate to set the elements' properties.
|
||||
/// If you use a reuseId, you can pass null for the createCellDelegate.</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.
|
||||
/// The cell must be created first in the createCellDelegate delegate, unless a <see cref="reuseId"/> is passed to the method.</param>
|
||||
/// The cell must be created first in the createCellDelegate delegate, unless a reuseId is
|
||||
/// passed to the method.</param>
|
||||
/// <param name="reuseId">A reuse identifier for the TableView's cells.</param>
|
||||
/// <returns>A controller adapted to the collection passed in parameter.</returns>
|
||||
public static ObservableTableViewController<T> GetController<T>(
|
||||
this IList<T> list,
|
||||
public static ObservableTableViewController<TItem> GetController<TItem>(
|
||||
this IList<TItem> list,
|
||||
Func<NSString, UITableViewCell> createCellDelegate,
|
||||
Action<UITableViewCell, T, NSIndexPath> bindCellDelegate,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null)
|
||||
{
|
||||
return new ObservableTableViewController<T>
|
||||
return new ObservableTableViewController<TItem>
|
||||
{
|
||||
DataSource = list,
|
||||
CreateCellDelegate = createCellDelegate,
|
||||
|
@ -83,6 +172,166 @@ namespace GalaSoft.MvvmLight.Helpers
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewSource{TItem}"/> for a given <see cref="IList{TItem}"/>.
|
||||
/// Note that if the IList doesn't implement INotifyCollectionChanged, the associated UITableView won't be
|
||||
/// updated when the IList changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <param name="list">The IList that should be represented in the associated UITableView</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableTableViewSource{TItem}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableTableViewSource.</param>
|
||||
/// <returns>The new instance of ObservableTableViewSource.</returns>
|
||||
public static ObservableTableViewSource<TItem> GetTableViewSource<TItem>(
|
||||
this IList<TItem> list,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null,
|
||||
Func<ObservableTableViewSource<TItem>> factory = null)
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableTableViewSource<TItem>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewSource{TItem}"/> for a given <see cref="ObservableCollection{TItem}"/>.
|
||||
/// The associated UITableView will be updated when the ObservableCollection changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <param name="list">The ObservableCollection that should be represented in the associated UITableView</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableTableViewSource{TItem}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableTableViewSource.</param>
|
||||
/// <returns>The new instance of ObservableTableViewSource.</returns>
|
||||
public static ObservableTableViewSource<TItem> GetTableViewSource<TItem>(
|
||||
this ObservableCollection<TItem> list,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null,
|
||||
Func<ObservableTableViewSource<TItem>> factory = null)
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableTableViewSource<TItem>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewSource{TItem}"/> for a given <see cref="IList{TItem}"/>.
|
||||
/// Note that if the IList doesn't implement INotifyCollectionChanged, the associated UITableView won't be
|
||||
/// updated when the IList changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <param name="list">The IList that should be represented in the associated UITableView</param>
|
||||
/// <param name="createCellDelegate">A delegate to a method creating or reusing a <see cref="UITableViewCell"/>.
|
||||
/// The cell will then be passed to the bindCellDelegate delegate to set the elements' properties.
|
||||
/// Use this method only if you don't want to register with the UITableView.RegisterClassForCellReuse method
|
||||
/// for cell reuse.</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableTableViewSource{TItem}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableTableViewSource.</param>
|
||||
/// <returns>The new instance of ObservableTableViewSource.</returns>
|
||||
public static ObservableTableViewSource<TItem> GetTableViewSource<TItem>(
|
||||
this IList<TItem> list,
|
||||
Func<NSString, UITableViewCell> createCellDelegate,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null,
|
||||
Func<ObservableTableViewSource<TItem>> factory = null)
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.CreateCellDelegate = createCellDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableTableViewSource<TItem>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
CreateCellDelegate = createCellDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ObservableTableViewSource{TItem}"/> for a given <see cref="ObservableCollection{TItem}"/>.
|
||||
/// The associated UITableView will be updated when the ObservableCollection changes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the IList.</typeparam>
|
||||
/// <param name="list">The ObservableCollection that should be represented in the associated UITableView</param>
|
||||
/// <param name="bindCellDelegate">A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item passed as second parameter.</param>
|
||||
/// <param name="createCellDelegate">A delegate to a method creating or reusing a <see cref="UITableViewCell"/>.
|
||||
/// The cell will then be passed to the bindCellDelegate delegate to set the elements' properties.
|
||||
/// Use this method only if you don't want to register with the UITableView.RegisterClassForCellReuse method
|
||||
/// for cell reuse.</param>
|
||||
/// <param name="reuseId">An ID used for optimization and cell reuse.</param>
|
||||
/// <param name="factory">An optional delegate returning an instance of a class deriving from
|
||||
/// <see cref="ObservableTableViewSource{TItem}"/>. This can be used if you need to implement
|
||||
/// specific features in addition to the built-in features of ObservableTableViewSource.</param>
|
||||
/// <returns>The new instance of ObservableTableViewSource.</returns>
|
||||
public static ObservableTableViewSource<TItem> GetTableViewSource<TItem>(
|
||||
this ObservableCollection<TItem> list,
|
||||
Func<NSString, UITableViewCell> createCellDelegate,
|
||||
Action<UITableViewCell, TItem, NSIndexPath> bindCellDelegate,
|
||||
string reuseId = null,
|
||||
Func<ObservableTableViewSource<TItem>> factory = null)
|
||||
{
|
||||
if (factory != null)
|
||||
{
|
||||
var coll = factory();
|
||||
coll.DataSource = list;
|
||||
coll.BindCellDelegate = bindCellDelegate;
|
||||
coll.CreateCellDelegate = createCellDelegate;
|
||||
coll.ReuseId = reuseId;
|
||||
return coll;
|
||||
}
|
||||
|
||||
return new ObservableTableViewSource<TItem>
|
||||
{
|
||||
DataSource = list,
|
||||
BindCellDelegate = bindCellDelegate,
|
||||
CreateCellDelegate = createCellDelegate,
|
||||
ReuseId = reuseId
|
||||
};
|
||||
}
|
||||
|
||||
internal static string GetDefaultEventNameForControl(this Type type)
|
||||
{
|
||||
string eventName = null;
|
||||
|
|
|
@ -0,0 +1,401 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace GalaSoft.MvvmLight.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="UICollectionViewSource"/> that automatically updates the associated <see cref="UICollectionView"/> when its
|
||||
/// data source changes. Note that the changes are only observed if the data source
|
||||
/// implements <see cref="INotifyCollectionChanged"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the data source.</typeparam>
|
||||
/// <typeparam name="TCell">The type of the <see cref="UICollectionViewCell"/> used in the CollectionView.
|
||||
/// This can either be UICollectionViewCell or a derived type.</typeparam>
|
||||
public class ObservableCollectionViewSource<TItem, TCell> : UICollectionViewSource, INotifyPropertyChanged
|
||||
where TCell : UICollectionViewCell
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SelectedItem" /> property's name.
|
||||
/// </summary>
|
||||
public const string SelectedItemPropertyName = "SelectedItem";
|
||||
|
||||
private readonly NSString _defaultReuseId = new NSString("C");
|
||||
private readonly Thread _mainThread;
|
||||
|
||||
private IList<TItem> _dataSource;
|
||||
private INotifyCollectionChanged _notifier;
|
||||
private NSString _reuseId;
|
||||
private TItem _selectedItem;
|
||||
private UICollectionView _view;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to a method taking a <see cref="UICollectionViewCell"/>
|
||||
/// and setting its elements' properties according to the item
|
||||
/// passed as second parameter.
|
||||
/// </summary>
|
||||
public Action<TCell, TItem, NSIndexPath> BindCellDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data source of this list controller.
|
||||
/// </summary>
|
||||
public IList<TItem> DataSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Equals(_dataSource, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifier != null)
|
||||
{
|
||||
_notifier.CollectionChanged -= HandleCollectionChanged;
|
||||
}
|
||||
|
||||
_dataSource = value;
|
||||
_notifier = value as INotifyCollectionChanged;
|
||||
|
||||
if (_notifier != null)
|
||||
{
|
||||
_notifier.CollectionChanged += HandleCollectionChanged;
|
||||
}
|
||||
|
||||
if (_view != null)
|
||||
{
|
||||
_view.ReloadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to a method returning a <see cref="UICollectionReusableView"/>
|
||||
/// and used to set supplementary views on the UICollectionView.
|
||||
/// </summary>
|
||||
public Func<NSString, NSIndexPath, UICollectionReusableView> GetSupplementaryViewDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reuse identifier for the UICollectionView's cells.
|
||||
/// </summary>
|
||||
public string ReuseId
|
||||
{
|
||||
get
|
||||
{
|
||||
return NsReuseId.ToString();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_reuseId = string.IsNullOrEmpty(value) ? null : new NSString(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UICollectionView's selected item. You can use one-way databinding on this property.
|
||||
/// </summary>
|
||||
public TItem SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItem;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Equals(_selectedItem, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedItem = value;
|
||||
RaisePropertyChanged(SelectedItemPropertyName);
|
||||
RaiseSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private NSString NsReuseId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reuseId ?? _defaultReuseId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initializes a new instance of <see cref="ObservableCollectionViewSource{TItem, TCell}"/>
|
||||
/// </summary>
|
||||
public ObservableCollectionViewSource()
|
||||
{
|
||||
_mainThread = Thread.CurrentThread;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.GetCell"/> method.
|
||||
/// Creates and returns a cell for the UICollectionView. Where needed, this method will
|
||||
/// optimize the reuse of cells for a better performance.
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the item for which the cell must be returned.</param>
|
||||
/// <returns>The created and initialised <see cref="UICollectionViewCell"/>.</returns>
|
||||
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
|
||||
{
|
||||
var cell = (TCell)collectionView.DequeueReusableCell(NsReuseId, indexPath);
|
||||
|
||||
try
|
||||
{
|
||||
var coll = _dataSource;
|
||||
|
||||
if (coll != null)
|
||||
{
|
||||
var item = coll[indexPath.Row];
|
||||
BindCell(cell, item, indexPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.GetItemsCount"/> method.
|
||||
/// Gets the number of items in the data source.
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <param name="section">The section for which the count is needed. In the current
|
||||
/// implementation, only one section is supported.</param>
|
||||
/// <returns>The number of items in the data source.</returns>
|
||||
public override nint GetItemsCount(UICollectionView collectionView, nint section)
|
||||
{
|
||||
SetView(collectionView);
|
||||
return _dataSource.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.GetViewForSupplementaryElement"/> method.
|
||||
/// When called, checks if the <see cref="GetSupplementaryViewDelegate"/>
|
||||
/// delegate has been set. If yes, calls that delegate to get a supplementary view for the UICollectionView.
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <param name="elementKind">The kind of supplementary element.</param>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the element.</param>
|
||||
/// <returns>A supplementary view for the UICollectionView.</returns>
|
||||
public override UICollectionReusableView GetViewForSupplementaryElement(
|
||||
UICollectionView collectionView,
|
||||
NSString elementKind,
|
||||
NSIndexPath indexPath)
|
||||
{
|
||||
if (GetSupplementaryViewDelegate == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"GetViewForSupplementaryElement was called but no GetSupplementaryViewDelegate was found");
|
||||
}
|
||||
|
||||
var view = GetSupplementaryViewDelegate(elementKind, indexPath);
|
||||
return view;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.ItemDeselected"/> method.
|
||||
/// Called when an item is deselected in the UICollectionView.
|
||||
/// <remark>If you subclass ObservableCollectionViewSource, you may override this method
|
||||
/// but you may NOT call base.ItemDeselected(...) in your overriden method, as this causes an exception
|
||||
/// in iOS. Because of this, you must take care of resetting the <see cref="SelectedItem"/> property
|
||||
/// yourself by calling SelectedItem = default(TItem);</remark>
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the element.</param>
|
||||
public override void ItemDeselected(UICollectionView collectionView, NSIndexPath indexPath)
|
||||
{
|
||||
SelectedItem = default(TItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.ItemSelected"/> method.
|
||||
/// Called when an item is selected in the UICollectionView.
|
||||
/// <remark>If you subclass ObservableCollectionViewSource, you may override this method
|
||||
/// but you may NOT call base.ItemSelected(...) in your overriden method, as this causes an exception
|
||||
/// in iOS. Because of this, you must take care of setting the <see cref="SelectedItem"/> property
|
||||
/// yourself by calling var item = GetItem(indexPath); SelectedItem = item;</remark>
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the element.</param>
|
||||
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
|
||||
{
|
||||
var item = _dataSource[indexPath.Row];
|
||||
SelectedItem = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UICollectionViewSource.NumberOfSections"/> method.
|
||||
/// The number of sections in this UICollectionView. In the current implementation,
|
||||
/// only one section is supported.
|
||||
/// </summary>
|
||||
/// <param name="collectionView">The UICollectionView associated to this source.</param>
|
||||
/// <returns></returns>
|
||||
public override nint NumberOfSections(UICollectionView collectionView)
|
||||
{
|
||||
SetView(collectionView);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a <see cref="UICollectionViewCell"/>'s elements according to an item's properties.
|
||||
/// If a <see cref="BindCellDelegate"/> is available, this delegate will be used.
|
||||
/// If not, a simple text will be shown.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell that will be prepared.</param>
|
||||
/// <param name="item">The item that should be used to set the cell up.</param>
|
||||
/// <param name="indexPath">The <see cref="NSIndexPath"/> for this cell.</param>
|
||||
protected virtual void BindCell(UICollectionViewCell cell, object item, NSIndexPath indexPath)
|
||||
{
|
||||
if (BindCellDelegate == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"BindCell was called but no BindCellDelegate was found");
|
||||
}
|
||||
|
||||
BindCellDelegate((TCell)cell, (TItem)item, indexPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item selected by the NSIndexPath passed as parameter.
|
||||
/// </summary>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the desired item.</param>
|
||||
/// <returns>The item selected by the NSIndexPath passed as parameter.</returns>
|
||||
protected TItem GetItem(NSIndexPath indexPath)
|
||||
{
|
||||
return _dataSource[indexPath.Row];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="PropertyChanged"/> event.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property that changed.</param>
|
||||
protected virtual void RaisePropertyChanged(string propertyName)
|
||||
{
|
||||
var handler = PropertyChanged;
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action act = () =>
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case 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);
|
||||
}
|
||||
|
||||
_view.InsertItems(paths);
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
var count = e.OldItems.Count;
|
||||
var paths = new NSIndexPath[count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = NSIndexPath.FromRowSection(e.OldStartingIndex + i, 0);
|
||||
paths[i] = index;
|
||||
|
||||
var item = e.OldItems[i];
|
||||
|
||||
if (Equals(SelectedItem, item))
|
||||
{
|
||||
SelectedItem = default(TItem);
|
||||
}
|
||||
}
|
||||
|
||||
_view.DeleteItems(paths);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
_view.ReloadData();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var isMainThread = Thread.CurrentThread == _mainThread;
|
||||
|
||||
if (isMainThread)
|
||||
{
|
||||
act();
|
||||
}
|
||||
else
|
||||
{
|
||||
NSOperationQueue.MainQueue.AddOperation(act);
|
||||
NSOperationQueue.MainQueue.WaitUntilAllOperationsAreFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseSelectionChanged()
|
||||
{
|
||||
var handler = SelectionChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetView(UICollectionView collectionView)
|
||||
{
|
||||
if (_view != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_view = collectionView;
|
||||
_view.RegisterClassForCell(typeof (TCell), NsReuseId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property of this instance changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new item gets selected in the UICollectionView.
|
||||
/// </summary>
|
||||
public event EventHandler SelectionChanged;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,512 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace GalaSoft.MvvmLight.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="UITableViewSource"/> that automatically updates the associated <see cref="UITableView"/> when its
|
||||
/// data source changes. Note that the changes are only observed if the data source
|
||||
/// implements <see cref="INotifyCollectionChanged"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items in the data source.</typeparam>
|
||||
public class ObservableTableViewSource<TItem> : UITableViewSource, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SelectedItem" /> property's name.
|
||||
/// </summary>
|
||||
public const string SelectedItemPropertyName = "SelectedItem";
|
||||
|
||||
private readonly NSString _defaultReuseId = new NSString("C");
|
||||
private readonly Thread _mainThread;
|
||||
|
||||
private IList<TItem> _dataSource;
|
||||
private INotifyCollectionChanged _notifier;
|
||||
private NSString _reuseId;
|
||||
private TItem _selectedItem;
|
||||
private UITableView _view;
|
||||
|
||||
/// <summary>
|
||||
/// When set, specifies which animation should be used when rows are added.
|
||||
/// </summary>
|
||||
public UITableViewRowAnimation AddAnimation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to a method taking a <see cref="UITableViewCell"/>
|
||||
/// and setting its elements' properties according to the item
|
||||
/// passed as second parameter.
|
||||
/// </summary>
|
||||
public Action<UITableViewCell, TItem, NSIndexPath> BindCellDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to a method creating or reusing a <see cref="UITableViewCell"/>.
|
||||
/// The cell will then be passed to the <see cref="BindCellDelegate"/>
|
||||
/// delegate to set the elements' properties. Note that this delegate is only
|
||||
/// used if you didn't register with a ReuseID using the UITableView.RegisterClassForCell method.
|
||||
/// </summary>
|
||||
public Func<NSString, UITableViewCell> CreateCellDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The data source of this list controller.
|
||||
/// </summary>
|
||||
public IList<TItem> DataSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Equals(_dataSource, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifier != null)
|
||||
{
|
||||
_notifier.CollectionChanged -= HandleCollectionChanged;
|
||||
}
|
||||
|
||||
_dataSource = value;
|
||||
_notifier = value as INotifyCollectionChanged;
|
||||
|
||||
if (_notifier != null)
|
||||
{
|
||||
_notifier.CollectionChanged += HandleCollectionChanged;
|
||||
}
|
||||
|
||||
if (_view != null)
|
||||
{
|
||||
_view.ReloadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set, specifieds which animation should be used when a row is deleted.
|
||||
/// </summary>
|
||||
public UITableViewRowAnimation DeleteAnimation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set, returns the height of the view that will be used for the TableView's footer.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetViewForFooterDelegate"/>
|
||||
public Func<nfloat> GetHeightForFooterDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set, returns the height of the view that will be used for the TableView's header.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetViewForHeaderDelegate"/>
|
||||
public Func<nfloat> GetHeightForHeaderDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set, returns a view that can be used as the TableView's footer.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetHeightForFooterDelegate"/>
|
||||
public Func<UIView> GetViewForFooterDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When set, returns a view that can be used as the TableView's header.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetHeightForHeaderDelegate"/>
|
||||
public Func<UIView> GetViewForHeaderDelegate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A reuse identifier for the TableView's cells.
|
||||
/// </summary>
|
||||
public string ReuseId
|
||||
{
|
||||
get
|
||||
{
|
||||
return NsReuseId.ToString();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_reuseId = string.IsNullOrEmpty(value) ? null : new NSString(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UITableView's selected item. You can use one-way databinding on this property.
|
||||
/// </summary>
|
||||
public TItem SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItem;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (Equals(_selectedItem, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedItem = value;
|
||||
RaisePropertyChanged(SelectedItemPropertyName);
|
||||
RaiseSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private NSString NsReuseId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reuseId ?? _defaultReuseId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and initializes an instance of <see cref="ObservableTableViewSource{TItem}"/>
|
||||
/// </summary>
|
||||
public ObservableTableViewSource()
|
||||
{
|
||||
_mainThread = Thread.CurrentThread;
|
||||
AddAnimation = UITableViewRowAnimation.Automatic;
|
||||
DeleteAnimation = UITableViewRowAnimation.Automatic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a cell for the UITableView. Where needed, this method will
|
||||
/// optimize the reuse of cells for a better performance.
|
||||
/// </summary>
|
||||
/// <param name="view">The UITableView associated to this source.</param>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the item for which the cell must be returned.</param>
|
||||
/// <returns>The created and initialised <see cref="UITableViewCell"/>.</returns>
|
||||
public override UITableViewCell GetCell(UITableView view, NSIndexPath indexPath)
|
||||
{
|
||||
if (_view == null)
|
||||
{
|
||||
_view = view;
|
||||
}
|
||||
|
||||
var cell = view.DequeueReusableCell(NsReuseId) ?? CreateCell(NsReuseId);
|
||||
|
||||
try
|
||||
{
|
||||
var coll = _dataSource;
|
||||
|
||||
if (coll != null)
|
||||
{
|
||||
var item = coll[indexPath.Row];
|
||||
BindCell(cell, item, indexPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When called, checks if the <see cref="GetHeightForFooterDelegate"/>has been set.
|
||||
/// If yes, calls that delegate to get the TableView's footer height.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="section">The section index.</param>
|
||||
/// <returns>The footer's height.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
|
||||
{
|
||||
if (GetHeightForFooterDelegate != null)
|
||||
{
|
||||
return GetHeightForFooterDelegate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When called, checks if the <see cref="GetHeightForHeaderDelegate"/>
|
||||
/// delegate has been set. If yes, calls that delegate to get the TableView's header height.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="section">The section index.</param>
|
||||
/// <returns>The header's height.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
||||
{
|
||||
if (GetHeightForHeaderDelegate != null)
|
||||
{
|
||||
return GetHeightForHeaderDelegate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When called, checks if the <see cref="GetViewForFooterDelegate"/>
|
||||
/// delegate has been set. If yes, calls that delegate to get the TableView's footer.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="section">The section index.</param>
|
||||
/// <returns>The UIView that should appear as the section's footer.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override UIView GetViewForFooter(UITableView tableView, nint section)
|
||||
{
|
||||
if (GetViewForFooterDelegate != null)
|
||||
{
|
||||
return GetViewForFooterDelegate();
|
||||
}
|
||||
|
||||
return base.GetViewForFooter(tableView, section);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When called, checks if the <see cref="GetViewForHeaderDelegate"/>
|
||||
/// delegate has been set. If yes, calls that delegate to get the TableView's header.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="section">The section index.</param>
|
||||
/// <returns>The UIView that should appear as the section's header.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override UIView GetViewForHeader(UITableView tableView, nint section)
|
||||
{
|
||||
if (GetViewForHeaderDelegate != null)
|
||||
{
|
||||
return GetViewForHeaderDelegate();
|
||||
}
|
||||
|
||||
return base.GetViewForHeader(tableView, section);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UITableViewSource.NumberOfSections"/> method.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <returns>The number of sections of the UITableView.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override nint NumberOfSections(UITableView tableView)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UITableViewSource.RowDeselected"/> method. When called, sets the
|
||||
/// <see cref="SelectedItem"/> property to null and raises the PropertyChanged and the SelectionChanged events.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="indexPath">The row's NSIndexPath.</param>
|
||||
public override void RowDeselected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
SelectedItem = default(TItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UITableViewSource.RowSelected"/> method. When called, sets the
|
||||
/// <see cref="SelectedItem"/> property and raises the PropertyChanged and the SelectionChanged events.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="indexPath">The row's NSIndexPath.</param>
|
||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
var item = _dataSource != null ? _dataSource[indexPath.Row] : default(TItem);
|
||||
SelectedItem = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the <see cref="UITableViewSource.RowsInSection"/> method
|
||||
/// and returns the number of rows in the associated data source.
|
||||
/// </summary>
|
||||
/// <param name="tableView">The active TableView.</param>
|
||||
/// <param name="section">The active section.</param>
|
||||
/// <returns>The number of rows in the data source.</returns>
|
||||
/// <remarks>In the current implementation, only one section is supported.</remarks>
|
||||
public override nint RowsInSection(UITableView tableView, nint section)
|
||||
{
|
||||
if (_view == null)
|
||||
{
|
||||
_view = tableView;
|
||||
}
|
||||
|
||||
return _dataSource == null ? 0 : _dataSource.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a <see cref="UITableViewCell"/> to an item's properties.
|
||||
/// If a <see cref="BindCellDelegate"/> is available, this delegate will be used.
|
||||
/// If not, a simple text will be shown.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell that will be prepared.</param>
|
||||
/// <param name="item">The item that should be used to set the cell up.</param>
|
||||
/// <param name="indexPath">The <see cref="NSIndexPath"/> for this cell.</param>
|
||||
protected virtual void BindCell(UITableViewCell cell, object item, NSIndexPath indexPath)
|
||||
{
|
||||
if (BindCellDelegate == null)
|
||||
{
|
||||
cell.TextLabel.Text = item.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
BindCellDelegate(cell, (TItem)item, indexPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="UITableViewCell"/> corresponding to the reuseId.
|
||||
/// If it is set, the <see cref="CreateCellDelegate"/> delegate will be used.
|
||||
/// </summary>
|
||||
/// <param name="reuseId">A reuse identifier for the cell.</param>
|
||||
/// <returns>The created cell.</returns>
|
||||
protected virtual UITableViewCell CreateCell(NSString reuseId)
|
||||
{
|
||||
if (CreateCellDelegate == null)
|
||||
{
|
||||
return new UITableViewCell(UITableViewCellStyle.Default, reuseId);
|
||||
}
|
||||
|
||||
return CreateCellDelegate(reuseId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item selected by the NSIndexPath passed as parameter.
|
||||
/// </summary>
|
||||
/// <param name="indexPath">The NSIndexPath pointing to the desired item.</param>
|
||||
/// <returns>The item selected by the NSIndexPath passed as parameter.</returns>
|
||||
protected TItem GetItem(NSIndexPath indexPath)
|
||||
{
|
||||
return _dataSource[indexPath.Row];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="PropertyChanged"/> event.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property that changed.</param>
|
||||
protected virtual void RaisePropertyChanged(string propertyName)
|
||||
{
|
||||
var handler = PropertyChanged;
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action act = () =>
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case 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);
|
||||
}
|
||||
|
||||
_view.InsertRows(paths, AddAnimation);
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
var count = e.OldItems.Count;
|
||||
var paths = new NSIndexPath[count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var index = NSIndexPath.FromRowSection(e.OldStartingIndex + i, 0);
|
||||
paths[i] = index;
|
||||
|
||||
var item = e.OldItems[i];
|
||||
|
||||
if (Equals(SelectedItem, item))
|
||||
{
|
||||
SelectedItem = default(TItem);
|
||||
}
|
||||
}
|
||||
|
||||
_view.DeleteRows(paths, DeleteAnimation);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
_view.ReloadData();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var isMainThread = Thread.CurrentThread == _mainThread;
|
||||
|
||||
if (isMainThread)
|
||||
{
|
||||
act();
|
||||
}
|
||||
else
|
||||
{
|
||||
NSOperationQueue.MainQueue.AddOperation(act);
|
||||
NSOperationQueue.MainQueue.WaitUntilAllOperationsAreFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseSelectionChanged()
|
||||
{
|
||||
var handler = SelectionChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property of this instance changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new item gets selected in the list.
|
||||
/// </summary>
|
||||
public event EventHandler SelectionChanged;
|
||||
}
|
||||
}
|
|
@ -117,9 +117,15 @@
|
|||
<Compile Include="..\..\GalaSoft.MvvmLight.Platform %28iOS%29\Helpers\ExtensionsApple.cs">
|
||||
<Link>Helpers\ExtensionsApple.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\GalaSoft.MvvmLight.Platform %28iOS%29\Helpers\ObservableCollectionViewSource.cs">
|
||||
<Link>Helpers\ObservableCollectionViewSource.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\GalaSoft.MvvmLight.Platform %28iOS%29\Helpers\ObservableTableViewController.cs">
|
||||
<Link>Helpers\ObservableTableViewController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\GalaSoft.MvvmLight.Platform %28iOS%29\Helpers\ObservableTableViewSource.cs">
|
||||
<Link>Binding\ObservableTableViewSource.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\AndroidTestApp\Binding\BindingAccountTest.cs">
|
||||
<Link>Binding\BindingAccountTest.cs</Link>
|
||||
</Compile>
|
||||
|
@ -191,6 +197,7 @@
|
|||
<Compile Include="AppDelegate.cs" />
|
||||
<None Include="GettingStarted.Xamarin" />
|
||||
<None Include="Info.plist" />
|
||||
<Compile Include="ObservableTable\ObservableTableViewSourceTest.cs" />
|
||||
<Compile Include="ObservableTable\ReuseIdTest.cs" />
|
||||
<Compile Include="ObservableTable\TestUiTableViewCell.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Foundation;
|
||||
using GalaSoft.MvvmLight.Helpers;
|
||||
using GalaSoft.MvvmLight.Test.ViewModel;
|
||||
using NUnit.Framework;
|
||||
using UIKit;
|
||||
|
||||
namespace GalaSoft.MvvmLight.Test.ObservableTable
|
||||
{
|
||||
[TestFixture]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class ObservableTableViewSourceTest
|
||||
{
|
||||
private UITableView _tableView;
|
||||
|
||||
public TestViewModel Vm
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_GetSourceWithCollectionCheckingItemCount()
|
||||
{
|
||||
Vm = GetViewModel();
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = Vm.ItemsCollection.GetTableViewSource(
|
||||
CreateCell,
|
||||
BindCell);
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
|
||||
Vm.ItemsCollection.Add(new TestItem("New one"));
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_GetSourceWithCollectionGettingLastItem()
|
||||
{
|
||||
Vm = new TestViewModel
|
||||
{
|
||||
ItemsCollection = new ObservableCollection<TestItem>()
|
||||
};
|
||||
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = Vm.ItemsCollection.GetTableViewSource(
|
||||
CreateCell,
|
||||
BindCell);
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
|
||||
// In unit tests, GetCell only gets called for the first inserted row (no layout pass)
|
||||
Vm.ItemsCollection.Add(new TestItem("One"));
|
||||
|
||||
var lastCell = _tableView.CellAt(Vm.ItemsCollection.First().RowIndexPath);
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Last().Title,
|
||||
lastCell.TextLabel.Text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_GetSourceWithListCheckingItemCount()
|
||||
{
|
||||
Vm = GetViewModel();
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = Vm.ItemsList.GetTableViewSource(
|
||||
CreateCell,
|
||||
BindCell);
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsList.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_NewSourceWithCollectionCheckingItemCount()
|
||||
{
|
||||
Vm = GetViewModel();
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = new ObservableTableViewSource<TestItem>
|
||||
{
|
||||
CreateCellDelegate = CreateCell,
|
||||
BindCellDelegate = BindCell,
|
||||
DataSource = Vm.ItemsCollection,
|
||||
};
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
|
||||
Vm.ItemsCollection.Add(new TestItem("New one"));
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_NewSourceWithCollectionGettingLastItem()
|
||||
{
|
||||
Vm = new TestViewModel
|
||||
{
|
||||
ItemsCollection = new ObservableCollection<TestItem>()
|
||||
};
|
||||
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = new ObservableTableViewSource<TestItem>
|
||||
{
|
||||
CreateCellDelegate = CreateCell,
|
||||
BindCellDelegate = BindCell,
|
||||
DataSource = Vm.ItemsCollection,
|
||||
};
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
|
||||
// In unit tests, GetCell only gets called for the first inserted row (no layout pass)
|
||||
Vm.ItemsCollection.Add(new TestItem("One"));
|
||||
|
||||
var lastCell = _tableView.CellAt(Vm.ItemsCollection.First().RowIndexPath);
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsCollection.Last().Title,
|
||||
lastCell.TextLabel.Text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ObservableTableViewSource_NewSourceWithListCheckingItemCount()
|
||||
{
|
||||
Vm = GetViewModel();
|
||||
_tableView = new UITableView();
|
||||
|
||||
var source = new ObservableTableViewSource<TestItem>
|
||||
{
|
||||
CreateCellDelegate = CreateCell,
|
||||
BindCellDelegate = BindCell,
|
||||
DataSource = Vm.ItemsList,
|
||||
};
|
||||
|
||||
_tableView.Source = source;
|
||||
|
||||
Assert.AreEqual(
|
||||
Vm.ItemsList.Count,
|
||||
_tableView.NumberOfRowsInSection(0));
|
||||
}
|
||||
|
||||
private void BindCell(UITableViewCell cell, TestItem item, NSIndexPath path)
|
||||
{
|
||||
item.RowIndexPath = path;
|
||||
cell.TextLabel.Text = item.Title;
|
||||
}
|
||||
|
||||
private UITableViewCell CreateCell(NSString reuseId)
|
||||
{
|
||||
return new UITableViewCell(UITableViewCellStyle.Default, reuseId);
|
||||
}
|
||||
|
||||
private TestViewModel GetViewModel()
|
||||
{
|
||||
return new TestViewModel
|
||||
{
|
||||
ItemsCollection = new ObservableCollection<TestItem>
|
||||
{
|
||||
new TestItem("123"),
|
||||
new TestItem("234"),
|
||||
new TestItem("345"),
|
||||
},
|
||||
ItemsList = new List<TestItem>
|
||||
{
|
||||
new TestItem("123"),
|
||||
new TestItem("234"),
|
||||
new TestItem("345"),
|
||||
new TestItem("456"),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче