MR.Gestures Handle all the touch gestures in your .NET MAUI and Xamarin.Forms apps

Overview

.NET MAUI or Xamarin.Forms are great if you want to develop apps for different platforms. But when it comes to touch gestures, they are still very limited.
For the first year Xamarin.Forms only had the TapGestureRecognizer. Although they did add some others later, they still don’t provide all information you need. Furthermore the API which they use has been copied from iOS and is not what a .NET developer would expect.

.NET MAUI tries to position itself to be not only for mobile platforms, but also for Windows and macOS desktop. For the desktop you also need to know where the mouse pointer is - but they don’t have any mouse gesture support.

MR.Gestures tries to close that gap.

It adds Down, Up, Tapping, Tapped, DoubleTapped, LongPressing, LongPressed, Panning, Panned, Swiped, Pinching, Pinched, Rotating, Rotated, MouseEntered, MouseMoved, MouseExited and ScrollWheelChanged events to each and every layout, cell and view and to the ContentPage. These events will be raised when the user performs the corresponding gesture on the element.

In the EventArgs passed to the handlers you can see exactly what happened.

You can work with the gestures completely from the shared project. The platform specific code is all done in MR.Gestures.

Getting Started

If you just want to see how it works, then download the free GestureSample app from GitHub. In this app you can see how all the elements use all events. You can change the code however you like, add break points and see what it does. You will find all the examples from here in the GestureSample and many more.

To add MR.Gestures to your own app just follow these simple steps:

And now you can use the new elements from the namespace MR.Gestures.

If you don’t want to buy a license yet, you can try without setting a LicenseKey. Some gestures are also raised without license, but the EventArgs will be empty.

You could also rename your app to GestureSample and use the LicenseKey from that app.

Code Samples

All the elements which are usually in the Microsoft.Maui.Controls or Xamarin.Forms namespace can also be found in MR.Gestures. But those have additional event handlers and command properties.

Adding the event handlers works the same way as in standard MAUI or Xamarin.Forms controls in both XAML and code.

Event Handlers in XAML

To add the event handler in XAML you have to:

  1. Add the namespace MR.Gestures from the dll
  2. Use the element from that namespace instead of MAUI/Xamarin.Forms
  3. Add the handlers for the gestures you want to listen to

Sample XAML

Here on LongPressed the method Red_LongPressed will be called. The method must be defined in the code behind file like this

    void Red_LongPressed(object sender, MR.Gestures.LongPressEventArgs e)
    {
        Console.WriteLine("BoxViewXaml.Red_LongPressed method called");
    }

Event Handlers in Code

Of course you can do the same in code. Either with a lambda expression

    var box1 = new MR.Gestures.BoxView { Color = Color.Red };
    box1.LongPressed += (s, e) => Console.WriteLine("Code: Red LongPressed");

or by assigning the method as a handler.

    box1.LongPressed += Red_LongPressed;

Commands in XAML

If you use MVVM then you probably have most of your logic in your ViewModels and you don’t want to use event handlers in your views. Of course this is also possible. There are commands for every event and you can bind them to the respective properties in the VM.

    <mr:BoxView Color="Green"
        LongPressedCommand="{Binding LongPressedCommand}"
        LongPressedCommandParameter="Green" />

The properties for the commands are called like the events, just with Command appended. You can also define a parameter passed to the command with the *CommandParameter properties. If you suppress the CommandParameter, then the respective event args are passed to your command. In this case a MR.Gestures.LongPressEventArgs object.

For the Tapped, DoubleTapped and LongPressed events you may not need the event args and defining a CommandParameter could make sense. But for the more complicated events you will always need the event args or you won’t know what happened. E.g. it does not help to just know that an element has been swiped unless you also know in which direction. The Direction is contained in the SwipeEventArgs.

Commands in Code

You can bind the commands in code too.

    var box2 = new MR.Gestures.BoxView { Color = Color.Green };
    box2.SetBinding(MR.Gestures.BoxView.LongPressedCommandProperty, "LongPressedCommand");
    box2.LongPressedCommandParameter = "Green";

The syntax for binding in code is a bit complicated, therefore my favorite is using commands in XAML. You will also see this, when you look through the code in the GestureSample. Although there are samples in all these categories, most of them are written in XAML and bound to commands.

Buy

MR.Gestures is licensed per app name.

  • If your app has the same name on all platforms, you only need one license key.
  • If you have hundreds of developers working on the same app, you only need one license key.
  • If you have different versions of your app with different names (e.g. different languages, customized for different clients or free and pro), then you need a separate key for each version.
  • If you migrate your app from Xamarin.Forms to .NET MAUI and the app name stays the same, you can reuse your old license key.

The license will be valid forever. You don’t need to refresh it every year.

The price for one license is EUR 40,00 (+VAT).

The payment process will be handled by MyCommerce. You will be forwarded to their site when you click the Buy button.

Please enter the EXACT name of your app.
This is NOT the bundle name. If you write it in lower case or append any whitespaces, it won’t work either. See the FAQs on how to configure it.

Once you purchased a license, instructions how to configure it will be displayed on the MyCommerce page. These instructions can also be found in the email they will send you and in the FAQs.

If you forget to configure the license key properly, then all the events will still be raised, but the properties of the EventArgs will be empty. That may be enough for the tap and long press events, but not for the more complicated ones.

Documentation

Events

You can handle eighteen different events with MR.Gestures.

Event Description
Down One or more fingers came down onto the touch screen.
Up One or more fingers were lifted from the screen.
Tapping A finger came down and up again but it is not sure yet if this may become a multi tap.
Tapped There was no second tap within 250ms so this was a single tap gesture.
DoubleTapped There have been two Tapping events within 250ms and no more came.
LongPressing A finger came down on the screen and did not move. The finger is still down.
LongPressed The finger was released and the gesture is finished.
Panning A finger came down and is moving on the screen.
This is also called a dragging gesture.
Panned The finger left the screen while it was moved slowly or not at all.
Swiped The finger left the screen while it was moved fast.
On Android and Windows Phone this is also called a flick.
Pinching Two fingers hit the screen and are moving towards or away from each other.
Pinched The fingers left the screen.
Rotating Two fingers hit the screen and are rotating on the screen.
Rotated The fingers left the screen.
MouseEntered The mouse pointer entered the area over the view.
MouseMoved The mouse pointer moved over the view.
MouseExited The mouse pointer was moved away from the view.
ScrollWheelChanged The scroll wheel was used while the mouse was over the views area.

The -ing event is raised when at least one finger is on the screen and a gesture is in progress. The -ed event is raised, when the gesture is finished and all fingers left the screen.

EventArgs

Your event handlers will receive one of nine different EventArgs objects with all the detailed information what happened.
Click on the class names to see all its properties.

Raised by Down and Up
Type Property Description
int[] TriggeringTouches The indexes of the fingers which were lowered/raised. Their locations are in Touches.
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers relative to ViewPosition (and in Up the last position before they were lifted).
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by Tapping, Tapped and DoubleTapped
Type Property Description
int NumberOfTaps The number of taps in a short period of time (~250ms).
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by LongPressing and LongPressed
Type Property Description
long Duration Duration of long press in milliseconds.
int NumberOfTaps The number of taps in a short period of time (~250ms).
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by Panning and Panned
Type Property Description
Point DeltaDistance The distance in X/Y that the finger was moved since this event was raised the last time.
Point TotalDistance The distance in X/Y that the finger was moved since the pan gesture began.
Point Velocity The velocity of the finger in X/Y.
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by Swiped
Type Property Description
Direction Direction The direction in which the finger moved when it was lifted from the screen. This is one of Left, Right, Up, Down or NotClear.
Point DeltaDistance The distance in X/Y that the finger was moved since the last Panning event.
Point TotalDistance The distance in X/Y that the finger was moved since the pan gesture began.
Point Velocity The velocity of the finger in X/Y.
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by Pinching and Pinched
Type Property Description
Point Center The center of the fingers on the screen.
double DeltaScale The relative distance between the two fingers on the screen compared to the last time this event was raised.
double DeltaScale The relative distance between the two fingers on the screen compared to the last time this event was raised.
double DeltaScaleX The relative horizontal distance between the two fingers on the screen compared to the last time this event was raised.
double DeltaScaleY The relative vertical distance between the two fingers on the screen compared to the last time this event was raised.
double Distance The distance between the first two fingers.
double DistanceX The horizontal distance between the first two fingers.
double DistanceY The vertical distance between the first two fingers.
double TotalScale The relative distance between the two fingers on the screen compared to when the gesture started.
double TotalScaleX The relative horizontal distance between the two fingers on the screen compared to when the gesture started.
double TotalScaleY The relative vertical distance between the two fingers on the screen compared to when the gesture started.
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by Rotating and Rotated
Type Property Description
double Angle The angle a line from the first to the second finger currently has on the screen.
Point Center The center of the fingers on the screen.
double DeltaAngle The angle the fingers were rotated on the screen compared to the last time this event was raised.
double TotalAngle The angle the fingers were rotated on the screen compared to the start of the gesture.
bool Cancelled Android and iOS sometimes cancel a touch gesture. In this case a *ed event is raised with Cancelled set to true.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches The number of fingers on the screen.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns the type of the coordinate in the same position in Touches.
This can be Touchdisplay, LeftMouseButton, RightMouseButton, MiddleMouseButton, Touchpad, XButton1, XButton2, Pen, MousePointer or Unknown.
Point[] Touches Returns the position of the fingers on the screen.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by MouseEntered, MouseMoved and MouseExited
Type Property Description
Point DeltaDistance The distance in X/Y that the mouse was moved since this event was raised the last time.
Point TotalDistance The distance in X/Y that the mouse was moved since the mouse entered the views area.
Point Velocity The velocity of the mouse in X/Y.
bool Cancelled True if the mouse pointer is removed due to battery saving on iOS.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches Always 1 because there is only one mouse pointer.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns one item which is MousePointer.
Point[] Touches Returns the position of the mouse pointer within the current element.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).
Raised by ScrollWheelChanged
Type Property Description
Point ScrollDelta Determines the direction in which the mouse wheel has been moved.
The actual values vary vastly per platform. With a mouse on windows they are always +/-120, with two fingers on the touchpad you get smaller values. On all other platforms these are <10.
bool Cancelled This event cannot be cancelled.
bool Handled This flag is meant to be used for event bubbling, but it does not work yet.
int NumberOfTouches Always 1 because there is only one mouse pointer.
IGestureAwareControl Sender The element which raised this event.
TouchSource[] Sources Returns one item which is MousePointer.
Point[] Touches Returns the position of the mouse pointer in the window.
Rectangle ViewPosition The absolute position and size of the Sender on the screen (window on desktop).

Settings

You can configure the behavior of MR.Gestures with different static Settings classes.
Some of those settings are the same for all platforms. These can be found in MR.Gestures.Settings. Platform dependant settings are in MR.Gestures.platform.Settings.

MR.Gestures.Settings

Property Default Description
MsUntilTapped 250 Milliseconds after a Tapping event that will be waited until Tapped is raised. If a second Tapping comes within this timeframe, then DoubleTapped will be raised instead.
If more than two taps come with less then this timeframe between them, neither Tapped nor DoubleTapped will be raised. It will only be indicated in TapEventArgs.NumberOfTaps in the Tapping event.
MinimumDeltaDistance 2 The minimum distance a finger has to be moved before a Panning event is raised.
Set it to a higher value to get fewer events or a lower value to be more accurate.
MinimumDeltaScale 0.01 The minimum change in Scale before a Pinching event is raised.
The default value 0.01 means that it has to change by at least 1%. Set it to a higher value to get fewer events or a lower value to be more accurate.
MinimumDeltaAngle 0.5 The minimum DeltaAngle before a Rotating event is raised.
Set it to a higher value to get fewer events or a lower value to be more accurate.
MinimumDistanceForScale 10.0 The minimum distance which two coordinates must be apart in order to calculate a DeltaScale*.
If DistanceX/Y is lower than this, then DeltaScaleX/Y will be 1.
MinimumMouseDeltaDistance 2 The minimum distance the mouse has to be moved before a MouseMoved event is raised.
Set it to a higher value to get fewer events or a lower value to be more accurate.

MR.Gestures.Android.Settings

Property Default Description
SwipeVelocityThreshold 0.1 The velocity of a lifted finger relative to the ScaledMaximumFlingVelocity must be at least this value to count as Swipe.
Set it to a higher value if you want the user to move faster.
The value must be between 0.0 and 1.0.

MR.Gestures.iOS.Settings

Property Default Description
SwipeVelocityThreshold 800 / 800 The velocity (UIPanGestureRecognizer.VelocityInView) of a lifted finger in X or Y direction must be at least this value to count as Swipe.
Set it to a higher value if you want the user to move faster.

MR.Gestures.UWP.Settings

Property Default Description
SwipeVelocityThreshold 1 The velocity of a lifted finger must be at least this value to count as Swipe.
The velocity comes from ManipulationCompletedRoutedEventArgs.Velocities.Linear and is in device-independent pixel per millisecond.
Set it to a higher value if you want the user to move faster.

MR.Gestures.WPF.Settings

Property Default Description
SwipeVelocityThreshold 100 The velocity of a lifted finger must be at least this value to count as Swipe.
Set it to a higher value if you want the user to move faster.
LongPressDuration 1000 The number of milliseconds that a finger is on the display / button pressed and not moved until it counts as long press.

MR.Gestures.MacOS.Settings

Property Default Description
SwipeVelocityThreshold 200 / 200 The velocity (NSPanGestureRecognizer.VelocityInView) of a lifted finger in X or Y direction must be at least this value to count as Swipe.
Set it to a higher value if you want the user to move faster.
TrackpadCoordinateRatio 5 / 5 The Touches coordinates are usually only in the range 0 to device size. These values are quite small if you want to pan something around the whole screen.
With TrackpadCoordinateRatio you can get bigger values for the Touches coordinates.
Set it to a higher value to get bigger values for Touches.

Compatibility

.NET MAUI

Some elements need to listen to some gestures themselves to work properly. Depending on the exact implementation by Microsoft and the native platforms, these events may be consumed.
Some of the views are simply too small to be touched. Therefore the gestures are also not forwarded to us.

.NET MAUI itself still has many bugs. I found some problems in the GestureSample, but I did not check for each one, if they are caused by me or MAUI. All events are raised on all platforms, but I didn't check all the details like if the EventArgs values are correct, all events are raised on all controls and if any functionality is missing.

Xamarin.Forms

MacOS:
Apple still has no multi touch displays for macOS, so I made the touch gestures on the Mac to work as I would expect them to. If you think anything is awkward or doesn't behave as you would expect, please let me know.
Down, Up, Tapping, Tapped, DoubleTapped, LongPressing, LongPressed, Panning, Panned, Swiped, MouseEntered, MouseMoved, MouseExited and ScrollWheelChanged can be triggered with the mouse. For Pinching, Pinched, Rotating and Rotated you need a Trackpad and two or more fingers. Panning, Panned and Swiped can also be triggered with the Trackpad with two or more fingers.
As the Trackpad has coordinates of its own which are not related to the touched view at all, the Points in Touches work a bit different then on the other platforms / using the mouse. When I returned the coordinates of the Trackpad (which is recommended by Apple), the values were very small for Pan gestures. So I added MR.Gestures.MacOS.Settings.TrackpadCoordinateRatio and multiply all coordinates by that setting. The default is 5/5, but you can change it however you want to get bigger or smaller values for the Touches.
Apple also doesn't use the usual coordinate system in MacOS. They have 0/0 at the LOWER left. So keep this in mind when you work with the view, touch or other coordinates. Most likely you'll have to do some different logic on the Mac than on the other platforms. Fortunately this is consistent with VisualElement.TranslationY and other Y coordinates.
This also means that angles are counter clock wise instead of clock wise on the other platforms.
There's also a bug in Xamarin.Forms.MacOS. Their ScrollViewRenderer doesn't have a virtual method OnElementChanged to override. Therefore I cannot hook into that elements' touch handlers. I submitted a PR to Xamarin in October 2017 and they merged it, but somehow it got removed again.

Here are all the elements and how they work in detail on each platform. Hover over any yellow or red icons to see the details.

Element iOS Android UWP WPF MacOS
ContentPage
AbsoluteLayout
ContentView
FlexLayout
Frame
Grid
RelativeLayout
ScrollView
StackLayout
TabbedPage
ActivityIndicator
BoxView
Button
DatePicker
Editor
Entry
Image
ImageButton
Label
ListView
Picker
ProgressBar
SearchBar
Slider
Stepper
Switch
TableView
TimePicker
WebView
EntryCell
ImageCell
SwitchCell
TextCell
ViewCell
All events are fully supported.
Some events do not work fully.
No events are raised.

Release Notes

Version Changes
4.0.0 Remove version for Xamarin.Forms and MAUI .NET6
Add MAUI .NET7 and 8
Add xml comments for intellisense
Run all handlers on the UI thread (fixes issue #44)
Run cleanup code only when page is removed from navigation stack (fixes issue #45)
3.0.0 Add Frame, ListView, TableView and cells
Add MacCatalyst for MAUI
Works with all MAUI versions
Fix ViewPosition to absolute and Touches to relative coordinates
Clean up when element is unloaded
[Android] Prevent Tapped when scrolling (XF and MAUI)
3.0.0-pre1 Support for .NET MAUI
Added Border, CheckBox, GraphicsView, HorizontalStackLayout, IndicatorView, RadioButton and VerticalStackLayout
2.2.1 [iOS] Fixed a crash in iOS < 13.4.
2.2.0 Add MouseEntered, MouseMoved, MouseExited and ScrollWheelChanged
Add Sources to all EventArgs
Update to Xamarin.Forms 5.0
[WPF] fixed ListView
2.1.3 Add ImageButton
Add support for target framework MonoAndroid10.0 including AndroidX
Update to Xamarin.Forms 4.5 (required for AndroidX)
2.1.2 [iOS] workaround for Xamarin.iOS bug which breaks all UIGestureRecognizers on iOS 13.4
[iOS] use WKWebView instead of UIWebView to make Apple happy on app submissions
2.1.1 fixed [Android] InvalidCastException when setting LicenseKey
fixed [UWP] LongPressing in hidden element
fixed [Android] MR.Gestures ScrollView NullReferenceException
2.1.0 added a FlexLayout
added DeltaScaleX, TotalScaleX, DeltaScaleY and TotalScaleY to the PinchEventArgs
fix changing event handlers does not raise PropertyChanged and thus does not start to handle those events
[Android, iOS, WPF] fix for DeltaDistance, TotalDistance and Velocity have been 0/0 when Panning was raised the first time.
2.0.0 support for WPF
built for .NET Standard 2.0
updated to Xamarin.Forms 3.0
remove support for Windows Phone Silverlight, Windows Phone Runtime and Windows Runtime
one multi targeting dll per platform
added TabbedPage
if a touch point is moved after LongPressing, LongPressed is canceled
[iOS] rotate and pinch gestures with more than two fingers
[Android] defaults to AppCompat, change renderers, adds Legacy* controls and made AppCompat* obsolete
[Android] if Velocity = 0/0 in Panned event, use Velocity of last Panning event
[macOS] ContentPage is now supported (ScrollView is still missing the PR I submitted eight months ago)
1.5.4 [Android, iOS + Mac] fix ViewPosition coordinates.
[UWP] LongPressed was only raised when also watching LongPressing.
1.5.3 [Android] revert to old code to retrieve the app name, no changes to AndroidManifest needed.
1.5.2 [Android] use new constructors for the Android renderes introduced in XF 2.5, fixes a bug when using a MR.Gestures.Frame.
1.5.0 support for MacOS
.NET Standard 1.0
1.4.3-pre1 [Android] use fast renderers introduced by Xamarin in XF 2.3.5
[Android] fix "InvalidOperationException: Collection was modified" in LongPress event
1.4.2.2 [Android] fix "InvalidOperationException: Collection was modified" in LongPress event
1.4.2 [iOS] fixes selection of a Cell on a ContentPage handling Tapped
1.4.1 [Android] fix: NullReferenceException when swiping
[Android] fix: EventArgs are empty if Panned is handled, but not Panning
[iOS] fix: after short pan gestures, Tapped is raised too
[iOS] fix: a short pan may be called without Touches (UIPanGestureRecognizer.NumberOfTouches == 0)
1.4.0 SwipeEventArgs is a PanEventArgs and therefore also contains DeltaDistance, TotalDistance and Velocity
Add controls for AppCompat (MR.Gestures.AppCompat*)
No events are raised if InputTransparent == true (on iOS there are also no events if any container has InputTransparent)
[iOS] The Panned and Swiped events contain the last Touches before the finger was raised
[Windows] The LongPressed event contain the last Touches before the finger was raised
1.3.5 Fixes a bug with LongPressing/LongPressed on some Android devices.
1.3.4 Added an internal constructor which Mono Droid needs to instantiate Java objects.
1.3.3 Worked around an issue when a NullReferenceException was thrown when I read a dependency property.
1.3.2 Worked around an issue when the renderers' Dispose method was called after Element was set to null.
Removed debug messages
1.3.1 Raises Up with Cancelled = true when a finger is dragged off an element
Fix NullReferenceException on iOS
1.3.0 Fix a bug with Tapping/Tapped on Android
1.3.0-pre1 Add support for Universal Windows Platform
Updated to Xamarin.Forms 2.0
1.2.5 Support for multiple fingers in Tapping, Tapped, DoubleTapped, LongPressing and LongPressed on Android.
1.2.4 Reset panning gesture on swipe
[WinRT] fix a bug with elements on TabbedPage / CarouselPage
[WinRT] try/catch around calls to Windows.UI.Input.GestureRecognizer
1.2.3 Updated to Xamarin.Forms 1.5.0.6446
1.2.2 Fixes a bug where the gestures were not properly reset if you handled Panning but not Panned, Pinching without Pinched or Rotating without Rotated.
1.2.1 Fixes a bug on Android. A ScrollView with Orientation="Horizontal" did not scroll.
1.2.0 Much better compatibility with Windows Runtime
Update XF to 1.4.3.6374 and get rid of Xamarin.Forms.Windows
Refactor renderers so that custom renderers can now be written on all platforms without having to inherit from mine
1.2.0-pre1 Add support for WinPhone 8.1 and Windows Store
Drop support for 32 bit iOS apps
Two finger pan simultaneously with pinch and rotate on all platforms
Introduce gesture throttling on all platforms with MR.Gestures.Settings.MinimumDelta* properties
Add Center to all EventArgs
1.1.0 Add the Down and Up events
[Android] Fix a NullReferenceException if a control is disposed in its asynchronous gesture handler
1.0.8 [Android] handle all touch events on all elements additionally to Xamarins handlers
[Android] better resource management (not so big memory leaks when Xamarin does not dispose of elements)
[Android] use DisplayMetrics.Density instead of hardcoded 2 for calculating ViewPosition, Touches and PanEventArgs.DeltaDistance
[Android] don't start pan when lifting one finger after multi touch gesture
1.0.7 Fixed a bug where events on Cells on Android phones were raised too often.
1.0.6 Updated to Xamarin.Forms 1.4.0.6341
1.0.6-pre1 Updated to Xamarin.Forms 1.4.0.6336-pre1
1.0.5 Updated to Xamarin.Forms 1.3.5.6335
1.0.5-pre1 iOS compatibility much better
WinPhone memory leaks fixed with NavigationPage
added Settings.MsUntilTapped
1.0.4 Updated to Xamarin.Forms 1.3.4.6332
1.0.4-pre4 Updated to Xamarin.Forms 1.3.4.6331-pre4
1.0.3 Updated to Xamarin.Forms 1.3.3.6323
1.0.2 Updated to Xamarin.Forms 1.3.2.6316
1.0.1 Updated to Xamarin.Forms 1.3.1.6296
1.0.0 Initial version for Xamarin.Forms 1.3.0.6292

FAQ

How do I configure my license key?

In .NET MAUI you add a call to ConfigureMRGestures in your MauiProgram.cs:

    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureMRGestures("your license key");

        return builder.Build();
    }

You can try MR.Gestures without license key. Then you just don’t pass anything to the ConfigureMRGestures method. But you still need to call the method to initialize the handlers.

In Xamarin.Forms you need to set the LicenseKey in all platform specific projects. The best place for it is in each project between the calls to Xamarin.Forms.Forms.Init(...) and LoadApplication(new App()).

Android

In your Android project open MainActivity.cs and in the OnCreate method after the call to global::Xamarin.Forms.Forms.Init(...) add the following line:

    MR.Gestures.Android.Settings.LicenseKey = "<your license key>";
iOS

In the iOS project this has to be done in AppDelegate.cs, in the FinishedLaunching method. Add this line:

    MR.Gestures.iOS.Settings.LicenseKey = "<your license key>";
Universal Windows Platform

In UWP you add the code to the OnLaunched method in App.xaml.cs.
For the .NET Native compilation you also have to tell Xamarin.Forms which assemblies it should scan for custom controls and renderers. Therefore you also have to change the call to Forms.Init slightly:

    Xamarin.Forms.Forms.Init(e, new[] { typeof(MR.Gestures.ContentPage).Assembly });

    MR.Gestures.UWP.Settings.LicenseKey = "<your license key>";
WPF

Here the call to Forms.Init is in the MainWindow constructor in MainWindow.xaml.cs.
Again you should pass the MR.Gestures assembly to Forms.Init:

    Xamarin.Forms.Forms.Init(e, new[] { typeof(MR.Gestures.ContentPage).Assembly });

    MR.Gestures.WPF.Settings.LicenseKey = "<your license key>";
MacOS

In the MacOS project, Forms.Init is called from AppDelegate.DidFinishLaunching and that’s also where you set the LicenseKey:

    MR.Gestures.MacOS.Settings.LicenseKey = "<your license key>";

How do I configure my app name?

The name of the app has to be configured differently on each platform.

Android

In the Android project open MainActivity.cs. The MainActivity class should have a Activity attribute. The Label parameter is the app name.

If you configured your activity manually in the Properties / AndroidManifest.xml file, then you need to change the android:label attribute there.

iOS

Microsoft likes to change where to find the app name for iOS. They move it every couple of versions. So the safest way is to edit the info.plist directly.

In the very beginning of XF (2014) they used to write it to CFBundleDisplayName. By the end of 2015 they changed it to CFBundleName. I tried to keep up by reading both values, using CFBundleName if it is configured, otherwise CFBundleDisplayName. But in 2021 they suddenly return the Assembly name when no CFBundleName is configured, so this stopped working.

So please write the app name to both CFBundleName AND CFBundleDisplayName in the info.plist.

Universal Windows Platform

Here you need to open the Package.appxmanifest file in your platform project. On the “Application” tab change the “Display Name” and on the “Packaging” tab the “Package display name”.

WPF

The app name is configured in the Properties / AssemblyInfo.cs file. You can use either the AssemblyTitle or AssemblyProduct attribute. If the former is set, the latter will be ignored.

[assembly: AssemblyTitle("<your app name>")]
MacOS

In Visual Studio for Mac open the MacOS project options and go to General / Main Settings. You need to set the “Name”.

How do I install MR.Gestures?

You can install it with NuGet. The NuGet package MR.Gestures is for both .NET MAUI and Xamarin.Forms.

In Visual Studio for Windows the easiest way is to right click your solution and choose “Manage NuGet Packages for Solution…”, then search for MR.Gestures and install it to all projects which use MAUI / Xamarin.Forms.

On the Mac you need to install the package for each project separately.

Is there a trial version available?

I already released the GestureSample app with complete source code on GitHub. That app shows how to use MR.Gestures with each and every available element.

If you want to try it in your own app or the final name of your app has not been decided yet, you can simply call your app GestureSample and use the LicenseKey from that app.

My event handler is called, but some event arg properties are not set.

You did not set the LicenseKey properly or it does not match your app name. Please check if you set the correct LicenseKey in all platform specific projects and that your app name matches the key.

The mouse events don’t work on my iPhone

The iPhone doesn’t support mice. You need an iPad with iPadOS 13.0 or later. I use UIHoverGestureRecognizer internally and that was only added in iOS 13.0.

The Tapped and DoubleTapped events are not raised

In MR.Gestures 1.* the LicenseKey needed to be set properly for these events to be raised. Without LicenseKey the NumberOfTaps is always 0. Tapped was only raised if NumberOfTaps was 1 and DoubleTapped only if it was 2.

With version 2.0 these events are also raised without LicenseKey (although NumberOfTaps is still 0).

What’s the license agreement of MR.Gestures?

MR.Gestures is a library to be included in others apps. So Yes, with the license I grant the right to use MR.Gestures in your own app, no matter if it’s commercial or free. I wrote MR.Gestures myself and I hold all rights on it. I do not grant the right to tamper with the library in any way, decompile or resell the whole or only parts of it.

MR.Gestures is licensed per app name. I.e. if your app has the same name on all platforms, you only need one license key. If you have different versions of your app with different names (e.g. different languages, customized for different clients/environments or free and pro), then you need a separate key for each version.

You can use it for as many developers on as many computers for as long as you like.

I want to swipe with two fingers. How can I handle that gesture?

The native standard gesture recognizers only work with the standard amount of fingers. I.e. two fingers for pinch and rotate and one for the others.

I managed to get some gestures on some platforms running with more fingers, but this is not consistent over all platforms.

In the future I may use a different API which is nearer to the wire. But this is a very big change so I’m not sure if I’ll do that yet.

I try to move/zoom/rotate an element, but it jumps on the screen.

The Touches coordinates in the EventArgs are always relative to the View which handles the event. If you manipulate the TranslationX, TranslationY, Scale or Rotation(X/Y) properties during a gesture, then the Touches and Delta* values cannot be calculated anymore.

So you always have to listen to the events of a container element and manipulate those properties of a child.

So instead of

    <mr:Image
        PanningCommand="{Binding PanningCommand}"
        TranslationX="{Binding TranslationX}"
        TranslationY="{Binding TranslationY}" />

you should write

    <mr:ContentView PanningCommand="{Binding PanningCommand}">
        <Image
            TranslationX="{Binding TranslationX}"
            TranslationY="{Binding TranslationY}" />
    </mr:ContentView>

Event Propagation (aka Event Bubbling)

If multiple elements are nested within each other and they all handle the same events, this is called event propagation. Ideally you should be able to tell, which element should handle the event. When I wrote MR.Gestures, I also wanted to implement this. This is why I added the Handled flag to the EventArgs.

Unfortunately I couldn’t get it working reliably on all platforms. Especially with some of the elements natively handling (and consuming) the events, this proved impossible to implement in a generic way.

Therefore I decided to strive for a solution where all MR.Gestures elements raise the respective events. This way you can decide yourself, which element should handle it. The easiest way is to start a Timer in one handler and do your work unless another handler will be called within a given timeframe.

The gestures do not work on cells on Windows.

Xamarins Windows renderers (UWP and WPF) for cells do not provide anything I can hook into to add my functionality. So I had to add that functionality to the renderers for MR.Gestures.ListView and MR.Gestures.TableView.

So if you want to listen to touch gestures on cells, the containing ListView or TableView must also be used from MR.Gestures even if you don’t add any gesture listeners on that element itself.

I get “Could not load file or assembly ‘MR.Gestures’” or “Could not load type MR.Gestures…”.

Please check your references. With version 2.0 all projects need a reference to MR.Gestures. In version 1.* the standard/portable project needs to reference MR.Gestures and the platform specific projects need references to both MR.Gestures and MR.Gestures.platform.

Every Xamarin.Forms developer should be familiar with the procedure of closing Visual Studio, deleting all bin and obj folders manually and then trying again. I have a .bat file which does that. Cleaning the solution within VS or just recompiling fails to delete old files most of the time.

Please also check, if you set your Settings.LicenseKey like it is described above. If this is not set, then the linker may strip it out of your executable.

What data do you have from me and what do you do with it?

I don’t collect any data from people who are not my customers. I also don’t send newsletters, so there’s no need to unsubscribe anything.

If you buy a MR.Gestures license, then my distributor will collect all data which they need to process the payment. Once the payment is done, I receive an email with your contacts (technical and payment), app name and issued license key. I do not receive your credit card number or any other sensitive information. I keep this data for future reference in case anybody needs support from me. Also if you send me any emails yourself, I will keep those emails for future reference. You can request the deletion of these emails, but if I can’t find your data, then you’re no customer of mine and I may be slower with support requests.

There is no cookie banner on this page, because it does not use any cookies at all.

Contact

My name is Michael Rumpler and I’m a freelance developer located in Austria. I started with C# in 2004. Although I also coded a lot in JavaScript, Java and some others, C# is my language of choice.

If you have any questions, suggestions, you find bugs or whatever, then please read this page again. If you can’t find anything about your problem here, than search in the Xamarin forum.

If you still can’t find anything, then you can contact me in various ways:

Imprint

Michael Rumpler
Trauttmansdorffstrasse 15
A-2544 Leobersdorf
Austria

VAT ID: ATU55244603