I am leaving YonglunLiu's response as the answer because he gave the tip that allowed me to create this solution.
The page I wanted to be able to swipe left on was contained inside of a Grid, so I created a derived class called SwipeGrid. It doesn't contain any logic inside of it. It is just the subclass:
using Xamarin.Forms;
namespace MyApp.Controls
{
public class SwipeGrid: Grid
{
}
}
iOS doesn't need any help with swiping. It works great all by itself. For this reason in iOS you just make a SwipeGridRenderer that doesn't change anything. I do not know if you can just not have this class. I think it is required.
using MyApp.Controls;
using MyApp.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(SwipeGrid), typeof(SwipeGridRenderer))]
namespace MyApp.iOS
{
public class SwipeGridRenderer : ViewRenderer<Grid, UIView>
{
}
}
Android is the real issue and so I had to create a SwipeGridRenderer with the following:
using System;
using Android.Content;
using Android.Widget;
using MyApp.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using MyApp.Controls;
using Android.Views;
[assembly: ExportRenderer(typeof(SwipeGrid), typeof(SwipeGridRenderer))]
namespace MyApp.Droid
{
public class SwipeGridRenderer : VisualElementRenderer<Grid>
{
private int ScaledTouchSlop;
private MotionEvent DownEvent;
private bool Swiping;
public SwipeGridRenderer(Context context) : base(context)
{
ScaledTouchSlop = ViewConfiguration.Get(context).ScaledTouchSlop;
Swiping = false;
}
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
switch (ev.Action)
{
case MotionEventActions.Cancel:
case MotionEventActions.Up:
Swiping = false;
break;
case MotionEventActions.Down:
Swiping = false;
DownEvent = MotionEvent.Obtain(ev);
break;
case MotionEventActions.Move:
float diffX = DownEvent.GetX() - ev.GetX();
Swiping = diffX > ScaledTouchSlop;
if (Swiping)
OnTouchEvent(DownEvent);
break;
default:
break;
}
return Swiping ? true : base.OnInterceptTouchEvent(ev);
}
public override bool OnTouchEvent(MotionEvent e)
{
if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel)
Swiping = false;
return base.OnTouchEvent(e);
}
}
}
If you understand what is going on you will detect that this will only detect swiping to the left. It also doesn't constrain that swiping to being very horizontal. I had a very specific thing I was trying to do and this is the minimum needed to get it done.
You could modify this to do a lot more.
You may also notice that I capture the down event and then send it inside of the move event. This is because at the moment you get the down touch we have not yet detected that the user is swiping because we need movement for that.
If you just start capturing the moves once you detect the swipe, the gesture recognizer doesn't recognize the swipe because it never got a down at the start of the gesture.
This requires that a down event be sent to the Grid before sending moves from that point to the Up event.
You may also notice that we don't really process the event in the OnTouchEvent handler other than detecting Up or Cancel to cancel capturing touch events. This is because I am relying on the gesture recognizer to see the swipe and process it like normal.