Xamarin.Forms did not handle touch gestures very well. They only had the TapGestureRecognizer
in the beginning. This is not enough to write advanced mobile apps. .NET MAUI added some more gesture recognizers but they still don’t provide all information you need.
Furthermore the API which they use (the GestureRecognizers
collection) has been copied from iOS and is not what a .NET developer would expect.
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 get all the information you need about the gesture.
You can work with the gestures completely from shared code. The platform specific code is all done in MR.Gestures.
Install MR.Gestures into your MAUI project(s) with NuGet and then add a call to ConfigureMRGestures
in your MauiProgram.cs:
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureMRGestures();
return builder.Build();
}
And now you can use the new elements from the namespace MR.Gestures
.
You can also clone the GestureSample app. 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.
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.
To add the event handler in XAML you have to:
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");
}
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;
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
.
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.
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.
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.
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). |
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). |
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). |
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). |
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). |
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). |
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). |
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). |
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). |
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. |
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.
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. |
Version | Changes |
---|---|
5.0.0 |
Make MR.Gestures free Use Loaded and Unloaded events to initialize/clean up[Android] Fix Editor , ListView , Picker , ScrollView and Slider [WinUI] Fix cells |
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 cellsAdd MacCatalyst for MAUI Works with all MAUI versions Fix ViewPosition to absolute and Touches to relative coordinatesClean 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* propertiesAdd 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 |
MR.Gestures 5.0 is free and open source. The older versions were proprietary and you needed to buy a license. I’ll leave the old questions here for the time being and remove them in the future.
I currently do not plan to use MAUI myself. In the past couple of months the effort for MR.Gestures support was more than the benefit. MAUI changed how the native controls are created and disposed and they did not document that process. It is quite difficult to always find workarounds for their bugs. So I decided to be done with it.
If somebody sends a PR I’ll consider merging it, but I will not do any further development on MR.Gestures myself.
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>
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 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.
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.
Xamarins Windows renderers (WinUI, 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.
The following questions are related to the license in MR.Gestures versions older than 5.0.
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())
.
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>";
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>";
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>";
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>";
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>";
The name of the app has to be configured differently on each platform.
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.
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.
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”.
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>")]
In Visual Studio for Mac open the MacOS project options and go to General / Main Settings. You need to set the “Name”.
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.
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.
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.
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 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.