You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从自定义CollectionViewCell调用MvxCommand的问题

正确实现UICollectionViewCell调用ViewModel中MvxCommand的方法

作为刚接触iOS开发又在啃MvvmCross的新手,这种绑定问题确实容易卡壳——我当初也踩过类似的坑!你现在的问题核心是打破了MVVM的解耦原则(直接传ViewModel给Source),其实MvvmCross提供了更优雅的绑定方式,完全不需要手动持有ViewModel引用。下面给你两种最常用的正确实现方案:

方案一:绑定CollectionView的SelectedItem到ViewModel属性

这种方法适合需要保留选中状态的场景,通过绑定SelectedItem,让ViewModel自动响应选中变化并触发命令:

1. ViewModel端代码

先在ViewModel里定义选中项属性和导航命令:

public class MainMenuViewModel : MvxViewModel
{
    private MenuItem _selectedMenuItem;
    // 绑定CollectionView的SelectedItem到这个属性
    public MenuItem SelectedMenuItem
    {
        get => _selectedMenuItem;
        set
        {
            SetProperty(ref _selectedMenuItem, value);
            if (value != null)
            {
                // 触发导航命令
                NavigateToPageCommand.Execute(value);
                // 重置选中状态,避免重复点击触发
                SelectedMenuItem = null;
            }
        }
    }

    // 带参数的导航命令,参数是菜单实体
    public IMvxCommand<MenuItem> NavigateToPageCommand { get; }

    public MainMenuViewModel(IMvxNavigationService navigationService)
    {
        // 初始化命令,处理导航逻辑
        NavigateToPageCommand = new MvxAsyncCommand<MenuItem>(async item =>
        {
            switch (item.PageType)
            {
                case PageType.Home:
                    await navigationService.Navigate<HomeViewModel>();
                    break;
                case PageType.Settings:
                    await navigationService.Navigate<SettingsViewModel>();
                    break;
                // 其他页面逻辑...
            }
        });
    }
}

2. ViewController端代码

在ViewController里完成CollectionView的绑定,不需要给Source传ViewModel:

public class MainMenuViewController : MvxViewController<MainMenuViewModel>
{
    private UICollectionView _collectionView;

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        // 初始化UICollectionView的布局和实例(这里省略布局代码,你已经完成了2列3行的网格)
        _collectionView = new UICollectionView(View.Bounds, new UICollectionViewFlowLayout());
        
        // 使用MvxCollectionViewSource,不需要传ViewModel
        var source = new MvxCollectionViewSource(_collectionView, typeof(MenuCell));
        _collectionView.Source = source;
        _collectionView.ReloadData();

        // 建立绑定
        var bindingSet = this.CreateBindingSet<MainMenuViewController, MainMenuViewModel>();
        // 绑定ItemsSource到ViewModel的菜单列表
        bindingSet.Bind(source).For(s => s.ItemsSource).To(vm => vm.MenuItems);
        // 绑定SelectedItem到ViewModel的SelectedMenuItem属性
        bindingSet.Bind(_collectionView).For(v => v.SelectedItem).To(vm => vm.SelectedMenuItem);
        bindingSet.Apply();
    }
}

方案二:直接绑定ItemClick事件到ViewModel命令

如果不需要选中状态,只是点击单元格就导航,这种方法更直接——利用MvxCollectionViewSource自带的ItemClick绑定:

1. ViewModel端代码

只需要保留导航命令(和方案一的命令逻辑一致),不需要额外定义SelectedMenuItem属性。

2. ViewController端代码

修改绑定逻辑,直接绑定Source的ItemClick到ViewModel命令:

var bindingSet = this.CreateBindingSet<MainMenuViewController, MainMenuViewModel>();
bindingSet.Bind(source).For(s => s.ItemsSource).To(vm => vm.MenuItems);
// 直接绑定ItemClick事件到ViewModel的导航命令
bindingSet.Bind(source).For(s => s.ItemClick).To(vm => vm.NavigateToPageCommand);
bindingSet.Apply();

为什么不要传ViewModel给Source?

你之前的做法会打破MVVM的解耦原则:ViewModel不应该依赖View,View也不应该直接持有ViewModel的硬引用。MvvmCross的绑定系统就是为了让两者通过数据关联,而不是直接引用,这样代码更易维护、更符合架构设计。

额外注意点

  • 确保你的MenuCell继承自MvxCollectionViewCell,这样绑定系统才能正常工作;
  • 如果自定义Cell里有独立按钮,也可以直接给按钮绑定命令,比如bindingSet.Bind(cell.MenuButton).To(vm => vm.NavigateCommand);,同样不需要传ViewModel;
  • 导航时记得通过构造函数注入IMvxNavigationService,这是MvvmCross推荐的标准导航方式。

内容的提问来源于stack exchange,提问作者Swisscheese

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:10:18