.NET MAUI Gesture Recognizers
All gesture recognizers inherit from GestureRecognizer and are added via View.GestureRecognizers.
<Image>
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped" />
</Image.GestureRecognizers>
</Image>
var tap = new TapGestureRecognizer(); tap.Tapped += OnTapped; image.GestureRecognizers.Add(tap);
Summary
| Recognizer | Key Properties | Key Events / Commands | Notes |
|---|---|---|---|
TapGestureRecognizer | NumberOfTapsRequired, Buttons | Tapped, Command | Default 1 tap, primary button |
SwipeGestureRecognizer | Direction, Threshold | Swiped, Command | Threshold default 100 DIU |
PanGestureRecognizer | TouchPoints | PanUpdated | StatusType: Started/Running/Completed |
PinchGestureRecognizer | — | PinchUpdated | Scale, ScaleOrigin, Status |
DragGestureRecognizer | CanDrag | DragStarting, DropCompleted | Auto data for Text/Image controls |
DropGestureRecognizer | AllowDrop | DragOver, Drop | Platform-specific PlatformArgs |
PointerGestureRecognizer | — | PointerEntered/Exited/Moved/Pressed/Released | Matching commands; enables PointerOver visual state |
TapGestureRecognizer
| Property | Type | Default | Description |
|---|---|---|---|
NumberOfTapsRequired | int | 1 | Taps needed to fire |
Buttons | ButtonsMask | Primary | Primary, Secondary, or both |
Command | ICommand | — | Fires on tap |
CommandParameter | object | — | Passed to Command |
<Label Text="Tap me">
<Label.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="2" Buttons="Primary"
Command="{Binding DoubleTapCommand}" />
</Label.GestureRecognizers>
</Label>
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2, Buttons = ButtonsMask.Primary };
tap.Command = new Command(() => Debug.WriteLine("Double-tapped"));
label.GestureRecognizers.Add(tap);
In .NET 10
ClickGestureRecognizeris deprecated. UseTapGestureRecognizer(touch/stylus) andPointerGestureRecognizer(mouse hover/press) instead.
SwipeGestureRecognizer
| Property | Type | Default | Description |
|---|---|---|---|
Direction | SwipeDirection | — | Left, Right, Up, Down |
Threshold | uint | 100 | Minimum distance in DIU |
Command | ICommand | — | Fires on swipe |
SwipedEventArgs: Direction, Parameter.
<BoxView Color="Teal">
<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Threshold="150" Swiped="OnSwiped" />
<SwipeGestureRecognizer Direction="Right" Swiped="OnSwiped" />
</BoxView.GestureRecognizers>
</BoxView>
var swipe = new SwipeGestureRecognizer { Direction = SwipeDirection.Left, Threshold = 150 };
swipe.Swiped += (s, e) => Debug.WriteLine($"Swiped {e.Direction}");
boxView.GestureRecognizers.Add(swipe);
Add one recognizer per direction; a single recognizer handles only one.
PanGestureRecognizer
| Property | Type | Default | Description |
|---|---|---|---|
TouchPoints | int | 1 | Number of fingers required |
PanUpdatedEventArgs: StatusType (Started, Running, Completed), TotalX, TotalY, GestureId.
<Image Source="photo.png">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated" />
</Image.GestureRecognizers>
</Image>
var pan = new PanGestureRecognizer();
pan.PanUpdated += (s, e) => {
if (e.StatusType == GestureStatus.Running)
{ image.TranslationX = e.TotalX; image.TranslationY = e.TotalY; }
};
image.GestureRecognizers.Add(pan);
On iOS
TotalX/TotalYare relative to the start; on Android they are relative to the previous event. Normalize in your handler if needed.
PinchGestureRecognizer
PinchGestureUpdatedEventArgs: Scale, ScaleOrigin (Point), Status (Started, Running, Completed).
<Image Source="photo.png">
<Image.GestureRecognizers>
<PinchGestureRecognizer PinchUpdated="OnPinchUpdated" />
</Image.GestureRecognizers>
</Image>
var pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += (s, e) =>
{
if (e.Status == GestureStatus.Running)
image.Scale = Math.Clamp(image.Scale + (e.Scale - 1), 0.5, 3);
};
image.GestureRecognizers.Add(pinch);
DragGestureRecognizer
| Property | Type | Default | Description |
|---|---|---|---|
CanDrag | bool | true | Enables/disables dragging |
| Event | Args Type | Key Properties |
|---|---|---|
DragStarting | DragStartingEventArgs | Data (DataPackage), Cancel |
DropCompleted | DropCompletedEventArgs | DragDropResult |
Label and Image auto-populate data packages. For custom data, set DragStartingEventArgs.Data.
<Label Text="Drag me" BackgroundColor="LightBlue">
<Label.GestureRecognizers>
<DragGestureRecognizer CanDrag="True" DragStarting="OnDragStarting" />
</Label.GestureRecognizers>
</Label>
var drag = new DragGestureRecognizer { CanDrag = true };
drag.DragStarting += (s, e) =>
{
e.Data.Text = viewModel.ItemId;
e.Data.Properties["payload"] = viewModel.SelectedItem;
};
view.GestureRecognizers.Add(drag);
DropGestureRecognizer
| Property | Type | Default | Description |
|---|---|---|---|
AllowDrop | bool | false | Must be true to receive drops |
| Event | Args Type | Key Properties |
|---|---|---|
DragOver | DragEventArgs | AcceptedOperation, PlatformArgs |
Drop | DropEventArgs | Data (DataPackageView), PlatformArgs |
<StackLayout BackgroundColor="LightGray">
<StackLayout.GestureRecognizers>
<DropGestureRecognizer AllowDrop="True" DragOver="OnDragOver" Drop="OnDrop" />
</StackLayout.GestureRecognizers>
</StackLayout>
var drop = new DropGestureRecognizer { AllowDrop = true };
drop.Drop += async (s, e) => { var text = await e.Data.GetTextAsync(); };
target.GestureRecognizers.Add(drop);
PlatformArgs per platform:
| Platform | DragOver | Drop |
|---|---|---|
| Android | PlatformArgs.DragEvent | PlatformArgs.DragEvent |
| iOS / Mac Catalyst | UIDropInteraction args | UIDropInteraction args |
| Windows | WinUI DragEventArgs | WinUI DragEventArgs |
PointerGestureRecognizer
| Event | Command Property | Fires When |
|---|---|---|
PointerEntered | PointerEnteredCommand | Pointer enters view bounds |
PointerExited | PointerExitedCommand | Pointer leaves view bounds |
PointerMoved | PointerMovedCommand | Pointer moves within view |
PointerPressed | PointerPressedCommand | Button pressed in view |
PointerReleased | PointerReleasedCommand | Button released in view |
PointerEventArgs.GetPosition(relativeTo) returns a Point?. Adding this recognizer enables the PointerOver VisualState.
<Border StrokeShape="RoundRectangle 8">
<Border.GestureRecognizers>
<PointerGestureRecognizer PointerEnteredCommand="{Binding HoverInCommand}"
PointerExitedCommand="{Binding HoverOutCommand}"
PointerMoved="OnPointerMoved" />
</Border.GestureRecognizers>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="LightCyan" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
var pointer = new PointerGestureRecognizer();
pointer.PointerMoved += (s, e) => Debug.WriteLine($"Pointer at {e.GetPosition(null)}");
border.GestureRecognizers.Add(pointer);
Best for mouse/stylus. On touch devices, pointer events fire but hover is not tracked.
Combining Multiple Gestures
Add multiple recognizers to the same collection. The platform resolves conflicts.
<Image Source="card.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped" />
<PanGestureRecognizer PanUpdated="OnPan" />
<PinchGestureRecognizer PinchUpdated="OnPinch" />
<PointerGestureRecognizer PointerEntered="OnHover" />
</Image.GestureRecognizers>
</Image>
Combining pan + swipe on the same view can conflict on Android. Test or use one at a time.
Platform Differences
| Area | iOS / Mac Catalyst | Android | Windows |
|---|---|---|---|
| Pan TotalX/TotalY | Relative to start | Relative to previous event | Relative to start |
| Pointer hover | Mouse/trackpad only | Mouse only | Mouse/pen |
| Cross-app drag-drop | Supported (iPadOS/Mac) | Not supported | Supported |
| Secondary button tap | Supported | Long-press fallback | Supported |
Quick Rules
- •One
SwipeGestureRecognizerper direction. - •Use
PointerGestureRecognizerfor hover effects, notTapGestureRecognizer. - •Set
AllowDrop="True"on drop targets — it defaults tofalse. - •Normalize pan deltas across platforms if sub-pixel accuracy matters.
- •Prefer commands over events for MVVM; both work identically.
- •In .NET 10 use
TapGestureRecognizerinstead of deprecatedClickGestureRecognizer.