使用Mvvmcross进行UITableView分组的现代方法
我在Xamarin + mvvmcross中找到了几种(不是太多)不同的方法来分组细胞。 我已经尝试了几个,但是我遇到了一个问题:当我的真实服务返回更新的集合(稍有延迟)时,绑定并没有真正起作用,列表仍然是空的。 如果要运行虚假服务,它会立即返回结果,列表中会填充数据。
我尝试了几种方法,但没有人推动我前进,所以我不确定是否需要任何代码。 只是询问是否有关于分组的现代实现的样本/提示。
编辑:这是一个代码示例。 当前版本基于Stuart的答案: 无法绑定到mvvmcross中的ios表部分单元格,子单元格按预期绑定
视图模型:
public override async void OnShow() { var calendarList = await DataService.GetListAsync(); CalendarList = new List(calendarList.OrderBy(a => a.Date)); }
因此,viewmodel获取模型列表,按日期排序并将其设置为CalendarList。 CalendarList只是一个抛出通知的List(new正在执行此工作)。
查看,初始化:
public override void ViewDidLoad() { base.ViewDidLoad(); var source = new TableSource(CalendarList); this.AddBindings(new Dictionary { {source, "ItemsSource CalendarList" } }); CalendarList.Source = source; CalendarList.ReloadData(); }
查看,TableSource
public class TableSource : MvxSimpleTableViewSource { private static readonly NSString CalendarCellIdentifier = new NSString("CalendarCell"); private List<IGrouping> GroupedCalendarList; public TableSource(UITableView calendarView) : base(calendarView, "CalendarCell", "CalendarCell") { } public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var foo = GroupedCalendarList[indexPath.Section].ToList(); var item = foo[indexPath.Row]; var cell = GetOrCreateCellFor(tableView, indexPath, item); var bindable = cell as IMvxDataConsumer; if (bindable != null) bindable.DataContext = item; return cell; } public override nint RowsInSection(UITableView tableview, nint section) { var numberRows = GroupedCalendarList[(int)section].Count(); return numberRows; } public override UIView GetViewForHeader(UITableView tableView, nint section) { var label = new UILabel(); label.Text = GroupedCalendarList[(int)section].FirstOrDefault().ScheduledStart.Value.Date.ToString(); label.TextAlignment = UITextAlignment.Center; return label; } public override nint NumberOfSections(UITableView tableView) { return GroupedCalendarList.Count; } public override void ReloadTableData() { if (ItemsSource == null) return; var calendarList = new List(ItemsSource as List); List<IGrouping> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList(); if (ItemsSource != null) GroupedCalendarList = new List<IGrouping>(groupedCalendarList); } }
其中一个症状是VM更新列表时不会调用ReloadTableData()。
编辑2:好吧,我已经尝试使用ObservableCollection,我看到调用了ReloadTableData,但UI仍然是空的。 如果有人能提供工作样本 – 那就太好了。
更新的VM:
public override async void OnShow() { var calendarList = await DataService.GetListAsync(); CalendarList.Clear(); foreach (var item in calendarList.OrderBy(a => a.ScheduledStart.Value.Date)) { CalendarList.Add(item); // Lets bruteforce and send notification on each add. In real life it's better to use extension ObservableCollection.AddRange(). } }
更新后的视图
public override void ReloadTableData() { if (ItemsSource == null) return; var calendarList = ItemsSource as IEnumerable; var groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList(); GroupedCalendarList = new List<IGrouping>(groupedCalendarList); Mvx.Trace("Trying to add new item " + calendarList.Count()); }
在这种情况下,输出充满
诊断:9.54尝试添加新项目77
但UI列表仍然是空的。
EDIT3:如果用添加Task.Run()替换VM.OnShow()。Wait(); 到VM的ctor(所以它会延迟View.ViewDidLoad()直到加载数据) – 然后列表显示正确。
public ViewModel() { Task.Run(async () => { var calendarList = await DataService.GetListAsync(); CalendarList = new ObservableCollection(calendarList); }).Wait(); // This guy is called before View.ViewDidLoad() and grouped list is shown properly }
这里可能存在许多问题。
- 我不建议像你一样绑定。 而是创建一个绑定集并使用:
var set = this.CreateBindingSet
-
使用可观察的集合,然后在完成添加到observable集合调用后:
RaisePropertyChanged(() => Collection);
-
为了便于对数据进行分组,我重写了ItemsSource,将数据推送到值的字典和键数组中,以便更容易地计算出这些部分。
-
因为你只想提供文本,只需覆盖TitleForHeader:
public override string TitleForHeader(UITableView tableView, nint section)
如果你不共享一些代码,很难给出好的建议。 但是从阅读你的问题开始,我认为你使用的是List
而不是ObservableCollection
。 UI需要知道集合何时更新,因此需要INotifyCollectionChanged
和INotifyPropertyChanged
实现。 ObservableCollection
实现了这些接口。
视图模型:
确保CalendarList
的类型为ObservableCollection
。 在此更改后,您将不需要方法ReloadTable()
。 在类MvxSimpleTableViewSource
他的基本MvxTableViewSource
ReloadTableData
在ItemsSource
的setter
中调用,您可以在以下链接中看到:
public override async void OnShow() { var calendarList = await DataService.GetListAsync(); CalendarList = new ObservableCollection(calendarList.OrderBy(a => a.Date)); }
我瞎了! 感谢来自MvvmCross Slack频道的@nmilcoff突出问题。 我忘了调用base.ReloadTableData()。
当然还要感谢Pilatus和Springham提供的好的提示。
这是解决方案:
public override void ReloadTableData() { if (ItemsSource == null) return; var calendarList = new List(ItemsSource as List); List> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList(); if (ItemsSource != null) GroupedCalendarList = new List>(groupedCalendarList); base.ReloadTableData(); // Here we are }
您似乎在发布数据请求后立即更新数据列表。
这是不正确的,这是异步操作,您应该在http请求完成时更新您的数据。
有一个完成异步数据请求的示例:
string strURL = "https://api.bitcoinaverage.com/ticker/"; MyHTTPRequestManager.Instance.GetDataFromUrl (strURL,(string dataStr)=>{ Console.WriteLine("Getting data succeed"); Console.WriteLine("The dataStr = "+dataStr); //update your dataList here InvokeOnMainThread(delegate { //Update your tableView or collectionView here, all UI stuff must be invoke on Main thread }); });
这是MyHTTPRequestManager.cs:
public class MyHTTPRequestManager { public delegate void GettingDataCallback(string dataStr); private static MyHTTPRequestManager instance = null; public static MyHTTPRequestManager Instance{ get{ if(null == instance) instance = new MyHTTPRequestManager(); return instance; } } public void GetDataFromUrl(string strURL,GettingDataCallback callback) { Console.WriteLine ("Begin request data."); System.Net.HttpWebRequest request; request = (System.Net.HttpWebRequest)WebRequest.Create(strURL); System.Net.HttpWebResponse response; response = (System.Net.HttpWebResponse)request.GetResponse(); System.IO.StreamReader myreader = new System.IO.StreamReader(response.GetResponseStream(), Encoding.UTF8); string responseText = myreader.ReadToEnd(); myreader.Close(); Console.WriteLine ("Getting succeed, invoke callback."); callback.Invoke (responseText); } }
这是结果屏幕截图:
希望它可以帮到你。