Search code examples
iosxamarinxamarin.iosxamarin.formsswipe-gesture

Xamarin Forms ViewCell Swipe to Show Buttons


tl;dr: How do I use swipe to show buttons in Xamarin Forms like the iOS mail app

I am trying to implement swipe to show buttons for a Xamarin Forms iOS app similar to the UI of the iOS mail app or this https://components.xamarin.com/view/swtableviewcell. That component among many other examples I found look great for iOS native implementations but I need to show this UI via Xamarin forms.

Currently I have a custom swipe gesture recognizer like this:

    [assembly: ExportRenderer(typeof(SwipeViewCell), typeof(SwipeIosRenderer))]

namespace MyApp.iOS.Renderers
   {
    public class SwipeIosRenderer : ViewCellRenderer
       {


    UISwipeGestureRecognizer swipeRightGestureRecognizer;
    UISwipeGestureRecognizer swipeLeftGestureRecognizer;

    protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
    {
        base.OnElementChanged(e);


        swipeRightGestureRecognizer = new UISwipeGestureRecognizer(() => UpdateRight()) { Direction = UISwipeGestureRecognizerDirection.Right };
        swipeLeftGestureRecognizer = new UISwipeGestureRecognizer(() => UpdateLeft()) { Direction = UISwipeGestureRecognizerDirection.Left };

        if (e.NewElement == null)
        {

            if (swipeRightGestureRecognizer != null)
            {
                this.RemoveGestureRecognizer(swipeRightGestureRecognizer);
            }
            if (swipeLeftGestureRecognizer != null)
            {
                this.RemoveGestureRecognizer(swipeLeftGestureRecognizer);
            }
        }

        if (e.OldElement == null)
        {

            this.AddGestureRecognizer(swipeRightGestureRecognizer);
            this.AddGestureRecognizer(swipeLeftGestureRecognizer);                
        }

    }

    private void UpdateLeft()
    {

        Console.WriteLine("Left swipe");

    }
    private void UpdateRight()
    {

        Console.WriteLine("Right swipe");

    }
}

That is bound to viewcells in a list. Now that I can recognize the "swipe" gesture I need help on how to actually move the view cell over and show a button like the examples I gave above?

It would be great to able to do this within the views XAML but am open to anything. I have a UpdateLeft and UpdateRight function that gets called on the respective swipe motions too if that can be used?

**EDIT: I need to do this for both left AND right swipe. ContextActions only provide the left swipe functionality.

Hope that makes sense!


Solution

  • Would Context Actions work for you? I haven't tried on other platforms, but on iOS it will create a swipe menu just like the Mail app. You should be able to use XAML and bind to command properties as well.

    Edit: Since you clarified that you need the left and right side swipe buttons that do not exist in the ContextActions, you could utilize the existing SWTableViewCell component that already has the desired behavior and adapt it to Xamarin.Forms.

    iOSRenderer:

    public class SwipeIosRenderer : TextCellRenderer
    {
    
    static NSString rid = new NSString("SWTableViewCell");
    
    public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
    {
        var forms_cell = (SwipeCell)item;
    
        SWTableViewCell native_cell = reusableCell as SWTableViewCell;
        if (native_cell == null)
        {
            native_cell = new SWTableViewCell(UITableViewCellStyle.Default, rid);
    
            if (forms_cell != null)
            {
                var cellDelegate = new CellDelegate(forms_cell);
                native_cell.Delegate = cellDelegate;
    
                if (forms_cell.LeftContextActions != null)
                {
                    var left = new NSMutableArray();
                    foreach (var btn in forms_cell.LeftContextActions)
                    {
                        AddButton(left, btn);
                    }
                    native_cell.LeftUtilityButtons = NSArray.FromArray<UIButton>(left);
                }
    
                if (forms_cell.RightContextActions != null)
                {
                    var right = new NSMutableArray();
                    foreach (var btn in forms_cell.RightContextActions)
                    {
                        AddButton(right, btn);
                    }
                    native_cell.RightUtilityButtons = NSArray.FromArray<UIButton>(right);
                    }
                }
                native_cell.TextLabel.Text = forms_cell.Text;
        }
        var fs = forms_cell.ImageSource as FileImageSource;
        if (fs != null)
        {
            native_cell.ImageView.Image = UIImage.FromBundle(fs.File);
        }
        return native_cell;
    }
    void AddButton(NSMutableArray array,Button btn){
        if (!String.IsNullOrEmpty(btn.Image?.File))
        {
            array.AddUtilityButton(btn.BorderColor.ToUIColor(), UIImage.FromBundle(btn.Image.File));
        }
        else
        {
            array.AddUtilityButton(btn.BorderColor.ToUIColor(), btn.Text);
        }
    }
    
    public class CellDelegate : SWTableViewCellDelegate
    {
        SwipeCell forms_cell;
    
        public CellDelegate(SwipeCell forms_cell)
        {
            this.forms_cell = forms_cell;
        }
    
        public override void DidTriggerLeftUtilityButton(SWTableViewCell cell, nint index)
        {
            if (forms_cell.LeftContextActions.Count > index)
            {
                var c = forms_cell.LeftContextActions[(int)index];
                var cmd = c.Command;
                if (cmd != null)
                {
                    cmd.Execute(c.CommandParameter);
                }
            }
        }
    
        public override void DidTriggerRightUtilityButton(SWTableViewCell cell, nint index)
        {
            if (forms_cell.RightContextActions.Count > index)
            {
                var c = forms_cell.RightContextActions[(int)index];
                var cmd = c.Command;
                if (cmd != null)
                {
                    cmd.Execute(c.CommandParameter);
                }
            }
        }
    }
    

    Example XAML:

    <ListView x:Name="SwipeList">
            <ListView.ItemTemplate>
                <DataTemplate>
    
                <test:SwipeCell Text="{Binding Data}" ImageSource="{Binding Image}">
                        <test:SwipeViewCell.LeftContextActions>
                            <Button Text="L1" Command="{Binding LeftAction}" BorderColor="Aqua"/>
                            <Button Command="{Binding LeftAction2}" BorderColor="Gray" Image="xamarin.png"/>
                        </test:SwipeViewCell.LeftContextActions>
                        <test:SwipeViewCell.RightContextActions>
                            <Button Text="R1" Command="{Binding RightAction}" BorderColor="Blue" />
                            <Button Text="R2" Command="{Binding RightAction2}" BorderColor="Purple" />
                        </test:SwipeViewCell.RightContextActions>
                    </test:SwipeViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    

    Example Code Behind

    public class MyListItem
    {
        Page page;
        public MyListItem(Page page)
        {
            this.page = page;
            this.LeftAction= new Command(() => this.page.DisplayAlert("Left 1", this.Data, "OK"));
            this.LeftAction2= new Command(() => this.page.DisplayAlert("Left 2", this.Data, "OK"));
            this.RightAction= new Command(() => this.page.DisplayAlert("Right 1", this.Data, "OK"));
            this.RightAction2= new Command(() => this.page.DisplayAlert("Right 2", this.Data, "OK"));
        }
        public string Image{ get; set; }
        string data;
        public string Data
        {
            get
            {
                return data;
            }
            set
            {
                data = value;
            }
        }
        ICommand leftAction;
        public ICommand LeftAction
        {
            get
            {
                return leftAction;
            }
            set
            {
                leftAction = value;
            }
        }
        ICommand leftAction2;
        public ICommand LeftAction2
        {
            get
            {
                return leftAction2;
            }
            set
            {
                leftAction2 = value;
            }
        }
        ICommand rightAction;
        public ICommand RightAction
        {
            get
            {
                return rightAction;
            }
            set
            {
                rightAction = value;
            }
        }
        ICommand rightAction2;
        public ICommand RightAction2
        {
            get
            {
                return rightAction2;
            }
            set
            {
                rightAction2 = value;
            }
        }
        public override string ToString()
        {
            return this.Data;
        }
        }
        public TestPage()
        {
            InitializeComponent();
            this.SwipeList.ItemsSource = new List<MyListItem>(){
                new MyListItem(this){Data="A"},
                new MyListItem(this){Data="B", Image="xamarin.png"},
                new MyListItem(this){Data="C"},
                new MyListItem(this){Data="D"},
            };
        }