Anthony Ryan
Anthony Ryan

Reputation: 395

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!

Upvotes: 2

Views: 3363

Answers (2)

Ryano
Ryano

Reputation: 495

Context Actions wasn't exactly what my client wanted. The row menu didn't appear on swipe. It appeared when they hold tap on the row, and the menu appeared at the top of the screen.

I was able to accomplish the swipe row behaviour with the new Xamarin.Forms SwipeView

Pass the current row into the CommandParameter, and use it in the event handler.

FYI: For some reason the SwipeView has a default BackgroundColor of white, which you can override with something else to match your theme.

Xaml:

            <ListView Margin="-20,0,0,0" x:Name="photosListView" ItemSelected="OnItemSelected" VerticalOptions="FillAndExpand" SeparatorColor="Gray" VerticalScrollBarVisibility="Default" HasUnevenRows="true"  SeparatorVisibility="Default" Background="{StaticResource PrimaryDark}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <SwipeView BackgroundColor="{StaticResource PrimaryDark}" >
                                <SwipeView.RightItems>
                                    <SwipeItems>
                                        <SwipeItem Text="Delete" BackgroundColor="LightPink" Clicked="OnDeleteRow" CommandParameter="{Binding .}" />
                                    </SwipeItems>
                                </SwipeView.RightItems>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto" />
                                        <ColumnDefinition Width="*" />
                                    </Grid.ColumnDefinitions>

                                    <StackLayout Orientation="Horizontal">
                                        <CheckBox IsVisible="{Binding SelectEnabled}" Color="{StaticResource White}" IsChecked="{Binding Selected}" Margin="20,0,-15,0"  CheckedChanged="OnItemCheckedChanged" />
                                        <Grid WidthRequest="70" HeightRequest="50">
                                            <Grid.Margin>
                                                <OnPlatform x:TypeArguments="Thickness" Android="15,0,0,0" iOS="10,0,0,0" />
                                            </Grid.Margin>
                                            <Image Aspect="AspectFill"  Source="{Binding ThumbImageSource}" HorizontalOptions="Fill" />
                                        </Grid>
                                    </StackLayout>

                                    <StackLayout Grid.Column="1" Spacing="0" Padding="0" Margin="0,5,0,0">
                                        <Label Text="{Binding Photo.Description}" TextColor="{StaticResource TextColour}" FontSize="16" FontAttributes="Bold"  />
                                        <Label Text="{Binding DateTakenString}" TextColor="{StaticResource TextColour}" FontSize="14" />
                                    </StackLayout>
                                </Grid>
                            </SwipeView>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>

cs:

    public async void OnDeleteRow(object sender, EventArgs e)
    {
        if (await GetDeleteRowConfirmationFromUser())
        {
            SwipeItem si = sender as SwipeItem;
            PhotoListItem itemToDelete = si.CommandParameter as PhotoListItem;
            LocalDatabaseService db = new LocalDatabaseService();
            db.DeletePhoto(itemToDelete.Photo);
            _listItems.Remove(itemToDelete);
        }
    }

Upvotes: 0

Andrew Tavera
Andrew Tavera

Reputation: 388

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"},
        };
    }

Upvotes: 5

Related Questions