使用Mvvmcross进行UITableView分组的现代方法

时间:2016-08-09 22:59:02

标签: c# uitableview xamarin xamarin.ios mvvmcross

我在Xamarin + mvvmcross中找到了几种(不太多)不同的方法来分组细胞。我已经尝试了几个,但是我遇到了一个问题:当我的真实服务返回更新的集合(稍有延迟)时,绑定并没有真正起作用,列表仍然是空的。如果要运行虚假服务,它会立即返回结果,列表中会填充数据。

我尝试了几种方法,但没有人推动我前进,所以我不确定是否需要任何代码。只是询问是否有关于分组的现代实现的样本/提示。

编辑:这是一个代码示例。目前的版本基于Stuart的答案:Unable to bind to ios table section cells in mvvmcross, child cells bind as expected

ViewModel:

    public override async void OnShow()
    {
        var calendarList = await DataService.GetListAsync();

        CalendarList = new List<Model>(calendarList.OrderBy(a => a.Date));
    }

因此,viewmodel获取模型列表,按日期排序并将其设置为CalendarList。 CalendarList只是一个抛出通知的List(新的就是这个工作)。

查看,初始化:

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        var source = new TableSource(CalendarList);

        this.AddBindings(new Dictionary<object, string>
        {
            {source, "ItemsSource CalendarList" }
        });

        CalendarList.Source = source;
        CalendarList.ReloadData();
    }

View,TableSource

    public class TableSource : MvxSimpleTableViewSource
    {
        private static readonly NSString CalendarCellIdentifier = new NSString("CalendarCell");
        private List<IGrouping<DateTime, Model>> 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<Model>(ItemsSource as List<ActivityModel>);

            List<IGrouping<DateTime, Model>> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

            if (ItemsSource != null)
                GroupedCalendarList = new List<IGrouping<DateTime, Model>>(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<Model>;

            var groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

            GroupedCalendarList = new List<IGrouping<DateTime, Model>>(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<ActivityModel>(calendarList);
        }).Wait(); // This guy is called before View.ViewDidLoad() and grouped list is shown properly
    }

4 个答案:

答案 0 :(得分:3)

这里可能存在许多问题。

  1. 我不建议像你一样绑定。而是创建一个绑定集并使用:
  2. var set = this.CreateBindingSet<TClass, TViewModel>(); set.Bind(source).To(vm => vm.Collection); set.Apply();

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

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

    3. 由于您只想提供文本,只需覆盖TitleForHeader: public override string TitleForHeader(UITableView tableView, nint section)

答案 1 :(得分:1)

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

<强>视图模型:

确保CalendarList的类型为ObservableCollection<Model>。在此更改后,您将不需要方法ReloadTable()。在课程MvxSimpleTableViewSource中,您可以在MvxTableViewSource ReloadTableData中调用他的基础setter ItemsSource,您可以在以下链接中看到:

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<Model>(calendarList.OrderBy(a => a.Date));
}

答案 2 :(得分:1)

我是瞎子!感谢来自MvvmCross Slack channel的@nmilcoff突出显示问题。我忘了打电话给base.ReloadTableData()。

当然,感谢皮拉图斯和斯普林厄姆提供了很好的提示。

以下是解决方案:

    public override void ReloadTableData()
    {
        if (ItemsSource == null) return;
        var calendarList = new List<Model>(ItemsSource as List<ActivityModel>);

        List<IGrouping<DateTime, Model>> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

        if (ItemsSource != null)
            GroupedCalendarList = new List<IGrouping<DateTime, Model>>(groupedCalendarList);

        base.ReloadTableData(); // Here we are
    }

答案 3 :(得分:0)

似乎您在发布数据请求后立即更新数据列表。

这是不正确的,这是异步操作,您应该在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);
    }
}

这是结果屏幕截图:

Result pic

希望它可以帮到你。