2
votes

I'm trying to set up a table of items (UITableView) with a button in every cell that navigates to a detailed profile of its item.

But I'm not sure what's the correct approach to this in MvvmCross. Some of my ideas:

  • Expose the ItemCellView's button outlet as public and bind it inside GetOrCreateCellFor
  • Pass the ShowItemDetailsCommand into each ItemCellView and bind it there
  • Use a simple callback from ItemCellView to the ItemsView instead of a binding
  • Get a separate MvxViewModel for each cell and call the navigation service from there
public class Item
{
    public string Name { get; set; }
}
public class ItemsViewModel : MvxViewModel
{
    public List<Item> Items { get; }
    public MvxCommand ShowItemDetailsCommand { get; }

    readonly IMvxNavigationService _navigationService;
    readonly IDatabaseService _databaseService;

    public ItemsViewModel(IMvxNavigationService navigationService, IDatabaseService databaseService)
    {
        ShowItemDetailsCommand = new MvxCommand(ShowItemDetails);

        _navigationService = navigationService;
        _databaseService = databaseService;

        Items = _databaseService.SelectItems();
    }

    void ShowItemDetails()
    {
        // not sure how "item" gets here so far
        _navigationService.Navigate<ItemDetailsViewModel, Item>(item);
    }
}
public partial class ItemsView : MvxTableViewController<ItemsViewModel>
{
    public ItemsView() : base("ItemsView", null) {}

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

        TableView = View as UITableView;

        var source = new TableViewSource(TableView);
        var bindings = this.CreateBindingSet<ItemsView, ItemsViewModel>();
        bindings.Bind(source).To(vm => vm.Items);
        bindings.Apply();

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

    public class TableViewSource : MvxTableViewSource
    {
        public TableViewSource(UITableView tableView) : base(tableView)
        {
            TableView.RegisterNibForCellReuse(UINib.FromName("ItemCellView", NSBundle.MainBundle), ItemCellView.kCellId);
        }

        protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
        {
            return TableView.DequeueReusableCell(ItemCellView.kCellId, indexPath) as ItemCellView;
        }
    }
}
public partial class ItemCellView : MvxTableViewCell
{
    public const string kCellId = "item_cell";
    // also has an [Outlet] UIButton in the .designer.cs part

    public ItemCellView(IntPtr handle) : base(handle)
    {
        this.DelayBind(() =>
        {
            var bindings = this.CreateBindingSet<ItemCellView, Item>();
            bindings.Bind(Name).To(i => i.Name);
            bindings.Apply();
        });
    }
}
1

1 Answers

2
votes

You should bind your ItemCellView's button in your cell's construct method:

// MyBtn is my Cell's button outlet
protected ItemCellView(IntPtr handle) : base(handle)
{
    this.DelayBind(() =>
    {
        var bindings = this.CreateBindingSet<MyTableViewCell, Item>();

        // Use this bind to set your button's title
        bindings.Bind(MyBtn).For("Title").To(item => item.Name);

        // This bind is used for binding a command in the Item model
        // CommandParameter can pass your parameter
        bindings.Bind(MyBtn).To(item => item.ShowItemDetailsCommand).CommandParameter(DataContext);

        bindings.Apply();
    });
}

Since in the Cell the DataContext has been changed to your Item model, The binding command should be configured in the model class:

public class Item
{
    private readonly Lazy<IMvxNavigationService> _navigationService = new Lazy<IMvxNavigationService>(Mvx.Resolve<IMvxNavigationService>);

    public string Name { set; get; }

    private ICommand showItemDetailsCommand;
    public ICommand ShowItemDetailsCommand
    {
        get
        {
            return showItemDetailsCommand ?? (showItemDetailsCommand = new MvxCommand<Item>(ShowItemDetails));
        }
    }
    async void ShowItemDetails(Item item)
    {
        await _navigationService.Value.Navigate<SecondViewModel, Item>(item);
    }
}

At last The SecondViewModel you want to push will receive this parameter through the event Prepare():

public class SecondViewModel : MvxViewModel<Item>
{
    public override void Prepare(Item parameter)
    {

    }
}