使用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 } 

这里可能存在许多问题。

  1. 我不建议像你一样绑定。 而是创建一个绑定集并使用:

var set = this.CreateBindingSet(); set.Bind(source).To(vm => vm.Collection); set.Apply();

  1. 使用可观察的集合,然后在完成添加到observable集合调用后: RaisePropertyChanged(() => Collection);

  2. 为了便于对数据进行分组,我重写了ItemsSource,将数据推送到值的字典和键数组中,以便更容易地计算出这些部分。

  3. 因为你只想提供文本,只需覆盖TitleForHeader: public override string TitleForHeader(UITableView tableView, nint section)

如果你不共享一些代码,很难给出好的建议。 但是从阅读你的问题开始,我认为你使用的是List而不是ObservableCollection 。 UI需要知道集合何时更新,因此需要INotifyCollectionChangedINotifyPropertyChanged实现。 ObservableCollection实现了这些接口。


视图模型:

确保CalendarList的类型为ObservableCollection 。 在此更改后,您将不需要方法ReloadTable() 。 在类MvxSimpleTableViewSource他的基本MvxTableViewSource ReloadTableDataItemsSourcesetter中调用,您可以在以下链接中看到:

https://github.com/MvvmCross/MvvmCross/blob/4.0/MvvmCross/Binding/iOS/Views/MvxTableViewSource.cs#L54

 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); } } 

这是结果屏幕截图:

结果图

希望它可以帮到你。