16

Could you please let me know how can I recognize long press gesture in Xamarin Forms application?

A few days before I used TapGestureRecognizer

TapGestureRecognizer imageTap = new TapGestureRecognizer();
imageTap.Tapped += (sender, args) => this.OnClickImage;
image.GestureRecognizers.Add(imageTap);

But I don't know how to make long press gesture according to this thread from xamarin forum

It should looks something like this, but it does not work.

var dumpParam = new RelayGesture((g, x) => DisplayAlert("Title", "Hello message", "Cancel"));

book.Cover.SetValue(Gestures.InterestsProperty, new GestureCollection() {
    new GestureInterest
        {
            GestureType = GestureType.LongPress
            GestureCommand = // what should I set?
            GestureParameter = dumpParam
        }
 });

How to set my custom handler method?

valdetero
  • 4,624
  • 1
  • 31
  • 46
Joseph Katzman
  • 1,959
  • 6
  • 21
  • 47
  • You can look this https://forums.xamarin.com/discussion/27323/how-can-i-recognize-long-press-gesture-in-xamarin-forms – Noor A Shuvo Apr 23 '17 at 10:59
  • @NoorAShuvo Yes. But I don't know hot to implement it in my case. I don'y use `XAML`. A few days before I used `TapGestureRecognizer` and `Tapped` event with `Xamarin.Froms.Image` I add it to `GestureRecognizers` of image, but I don't know how set my handle method to `GestureInterest` which describes into the link above. Could you provide an example? – Joseph Katzman Apr 23 '17 at 11:15
  • 1
    This request is being tracked in this open [Enhancement LongPressGestureRecognizer](https://github.com/xamarin/Xamarin.Forms/issues/3480). – ToolmakerSteve Jun 30 '19 at 20:10
  • ... until that is implemented, AFAIK the simplest solution is Alex Dunn's `RoutingEffect` approach to Long Press, as mentioned in https://stackoverflow.com/a/53752460/199364 – ToolmakerSteve Jun 30 '19 at 20:18
  • You might also consider working with effects. Alex Dunn wrote a good article on this: https://www.c-sharpcorner.com/article/xamarin-forms-use-bindableproperty-in-effects/ – raw Apr 18 '21 at 21:22

7 Answers7

25

You can do it cross platform way by attaching the below behavior, as long as it is Xamarin.Forms.Button or a sub-type of it.

using System;
using System.Threading;
using System.Windows.Input;
using Xamarin.Forms;

namespace App.Controls.Behaviors
{
    public class LongPressBehavior : Behavior<Button>
    {
        private readonly object _syncObject = new object();
        private const int Duration = 1000;

        //timer to track long press
        private Timer _timer;
        //the timeout value for long press
        private readonly int _duration;
        //whether the button was released after press
        private volatile bool _isReleased;

        /// <summary>
        /// Occurs when the associated button is long pressed.
        /// </summary>
        public event EventHandler LongPressed;

        public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command),
            typeof(ICommand), typeof(LongPressBehavior), default(ICommand));

        public static readonly BindableProperty CommandParameterProperty =
            BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(LongPressBehavior));

        /// <summary>
        /// Gets or sets the command parameter.
        /// </summary>
        public object CommandParameter
        {
            get => GetValue(CommandParameterProperty);
            set => SetValue(CommandParameterProperty, value);
        }

        /// <summary>
        /// Gets or sets the command.
        /// </summary>
        public ICommand Command
        {
            get => (ICommand)GetValue(CommandProperty);
            set => SetValue(CommandProperty, value);
        }

        protected override void OnAttachedTo(Button button)
        {
            base.OnAttachedTo(button);
            this.BindingContext = button.BindingContext;
            button.Pressed += Button_Pressed;
            button.Released += Button_Released;
        }

        protected override void OnDetachingFrom(Button button)
        {
            base.OnDetachingFrom(button);
            this.BindingContext = null;
            button.Pressed -= Button_Pressed;
            button.Released -= Button_Released;
        }

        /// <summary>
        /// DeInitializes and disposes the timer.
        /// </summary>
        private void DeInitializeTimer()
        {
            lock (_syncObject)
            {
                if (_timer == null)
                {
                    return;
                }
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
                _timer.Dispose();
                _timer = null;
                Debug.WriteLine("Timer disposed...");
            }
        }

        /// <summary>
        /// Initializes the timer.
        /// </summary>
        private void InitializeTimer()
        {
            lock (_syncObject)
            {
                _timer = new Timer(Timer_Elapsed, null, _duration, Timeout.Infinite);
            }
        }

        private void Button_Pressed(object sender, EventArgs e)
        {
            _isReleased = false;
            InitializeTimer();
        }

        private void Button_Released(object sender, EventArgs e)
        {
            _isReleased = true;
            DeInitializeTimer();
        }

        protected virtual void OnLongPressed()
        {
            var handler = LongPressed;
            handler?.Invoke(this, EventArgs.Empty);
            if (Command != null && Command.CanExecute(CommandParameter))
            {
                Command.Execute(CommandParameter);
            }
        }

        public LongPressBehavior()
        {
            _isReleased = true;
            _duration = Duration;
        }

        public LongPressBehavior(int duration) : this()
        {
            _duration = duration;
        }

        private void Timer_Elapsed(object state)
        {
            DeInitializeTimer();
            if (_isReleased)
            {
                return;
            }
            Device.BeginInvokeOnMainThread(OnLongPressed);
        }
    }
}

In the XAML UI:

 <Button x:Name="MyButton" Text="Long Press Me!">
   <Button.Behaviors>
     <behaviors:LongPressBehavior LongPressed="MyButton_LongPressed"/>
   </Button.Behaviors>
 </Button>

XAML UI with Command Binding:

 <Button x:Name="MyButton" Text="Long Press Me!">
   <Button.Behaviors>
     <behaviors:LongPressBehavior Command="{Binding CommandInViewModel}"/>
   </Button.Behaviors>
 </Button>
zafar
  • 1,965
  • 1
  • 15
  • 14
  • 4
    Not only did this solve my problem, this taught me how to do something in Xamarin that I did not even know was possible. This answers more questions than the one I had. – Oli4 Dec 14 '18 at 07:13
  • Can you please extend the example in XAML UI as I want to use a Command via 'Command={Binding Command_in_VM}' but it does not set any Command either in the Button, (i.e. Command == null) and no execution happens. Although the same code works on a regular Button.. – x y Feb 21 '19 at 14:35
  • 1
    @xy the reason command binding was not working is that there was no binding context on the LongPressCommandBehavior. you have to set the `BindingContext` property on the LongPressCommandBehavior to the button's `BindingContext` in the `OnAttachedTo` method. I have updated the source and extended the XAML UI example to address this. – zafar Feb 22 '19 at 15:26
  • When I try and use this it crashes on ios - is this still a good way to implement this? I've seen people try and do this with effects instead of behaviours too - is that a better option? – Carl Sargunar Apr 07 '20 at 10:48
  • @CarlSargunar can you post the crash log? Yes, using effects is a better option, because it uses the native platform events to trigger the long press event – zafar Apr 09 '20 at 14:37
  • I actually managed to get it all working as a behaviour for now which at least solves my immediate problem :) Thanks though – Carl Sargunar Apr 17 '20 at 12:19
  • @zafar Thank you so much for this. I definitely learned something new. I do have a problem however in that the CommandParameter is always null no matter what I do. Any Ideas as to why by chance" – hjavaher Jun 21 '20 at 01:11
  • @hjavaher You need to set `CommandParameter=someValue` in the XAML. If you are facing issues with it, please post the link to your code. – zafar Jun 22 '20 at 01:34
  • @zafar, Thank you for replying buddy. unfortunately I cannot share the code as I would get fired but I essentially have what you have above with the added `CommandParameter="{Binding .}"` The command is getting executed but the parameter is null. I have checked the parameter type is the right one too :/ – hjavaher Jun 22 '20 at 03:42
  • @hjavaher Check the output debug log to see if there are any binding errors. – zafar Jun 26 '20 at 18:44
  • @zafar Sorry for the delay in responding buddy. It turns out that it is a binding issue. Not sure why or how since all the other bindings work just fine in the same section. I've tried a bunch of things like adding a source but nothing seems to be working for some reason :/ – hjavaher Jul 02 '20 at 23:52
  • @hjavaher Same issue here. Did you ever find a solution? – ledragon May 25 '21 at 13:12
  • 2
    I would suggest a minor edit here. You should assign an `OnBindingContextChanged` event handler in `OnAttached()` to handle issues with Binding context. Also remember to remove it in `OnDetached()` for cleanup. I will try to edit the answer to reflect this. But something like this: ` private void OnBindingContextChanged(object sender, EventArgs e) { var button = sender as Button; BindingContext = button?.BindingContext; }` and in OnAttached() add `button.BindingContextChanged += OnBindingContextChanged;` and remove in OnDetached with -= – Kikanye Oct 28 '21 at 16:57
6

Make use of XLabs.Forms nuget package, which make long press and other gesture in PCL code only. Use of XLabs.Forms package will reduce the need of custom rendering in individual platforms... Add XAML code in .xaml file and attached event handler in .xaml.cs file.. It is working fine in Android..

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="MultiImage.Page1"             
         xmlns:lc="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
         xmlns:lb="clr-namespace:XLabs.Forms.Behaviors;assembly=XLabs.Forms">
<ContentPage.Content>
    <lc:GesturesContentView ExcludeChildren="False" GestureRecognized="GesturesContentView_GestureRecognized">
         <lb:Gestures.Interests>
                <lb:GestureCollection>
                    <lb:GestureInterest GestureType="SingleTap"/>
                    <lb:GestureInterest GestureType="LongPress"/>
                    <lb:GestureInterest GestureType="DoubleTap"/>
                </lb:GestureCollection>
          </lb:Gestures.Interests>
          <Image Source="Myimage.png" Aspect="AspectFit" HeightRequest="100"/>
        </lc:GesturesContentView>
</ContentPage.Content>

C# backend code:

private void GesturesContentView_GestureRecognized(object sender, GestureResult e)
{           
    switch (e.GestureType)
    {
        case GestureType.LongPress:
            //Add code here
            break;
        case GestureType.SingleTap:
            // Add code here                    
            break;
        case GestureType.DoubleTap:
            // Add code here
            break;
        default:
            break;
    }
valdetero
  • 4,624
  • 1
  • 31
  • 46
  • When I try to use this example, the tags are underlined red and I get the error "Invalid type: expected type is 'BindableProperty', actual type is 'GestureCollection". Any idea what the problem might be? – lepton Nov 28 '17 at 10:20
  • make sure, you have latest version of Xlabs.forms and you have defined the namespaces of Xlabs.forms.controls and Xlabs.forms.behaviour – shubham aggarwal Dec 17 '17 at 18:19
6

I recently came across this problem and found a useful post on the topic https://alexdunn.org/2017/12/27/xamarin-tip-xamarin-forms-long-press-effect/

This makes use of the RoutingEffect and goes through an example of how to create both iOS and Android implementation. The simplicity of this allows you to attach it to any view in your app without recreating code.

Richard Ockerby
  • 465
  • 5
  • 14
3

Surfing the internet I found the solution. There are few steps which you should reproduce.

1) Inherit the control you need the gestures on (i.e. if you want to add gesture to Xamarin.Forms.Image, create you own ImageWithLongPressGesture class).

public class ImageWithLongPressGesture : Xamarin.Forms.Image
{
    public EventHandler LongPressActivated;

    public void HandleLongPress(object sender, EventArgs e)
    {
        //Handle LongPressActivated Event
    }
}

2) Expose public events for the needed gestures.

3) Create a Renderer for each platform.

4) In the Renderer, handle the gestures and bubble them to your control.

[assembly: ExportRenderer(typeof(ImageWithLongPressGesture), typeof(LongPressGestureRecognizerImageRenderer))]
namespace App1.Droid.DroidRenderers
{
    public class LongPressGestureRecognizerImageRenderer : ImageRenderer
    {
        ImageWithLongPressGesture view;

        public LongPressGestureRecognizerImageRenderer()
        {
            this.LongClick += (sender, args) => {
                Toast.MakeText(this.Context, "Long press is activated.", ToastLength.Short).Show();
            };
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
        {
            base.OnElementChanged(e);

            if(e.NewElement != null)
            {
                view = e.NewElement as ImageWithLongPressGesture;
            }
        }
    }
}

This solution is a hybrid of answer on xamarin forms forum and Touch and Gestures presentation by Telerik.

valdetero
  • 4,624
  • 1
  • 31
  • 46
Joseph Katzman
  • 1,959
  • 6
  • 21
  • 47
  • This solution does not work for android. It works for iOS completely fine and I am using it in my project however for android nothing ever happens, the gesture is never handled. The renderer is 100% hooked up because I have been able to get alternative implementations to work (however they don't let me send my callback to the frame so are essentially useless). Any ideas on how to get this to work? I'm using a frame not an image but I'm sure that doesnt matter. Just to be clear `this.LongClick` is added to every frame just never fired off... – Axemasta Mar 01 '18 at 15:38
0
//To Add Programatically:

StackLayout _Containter = new StackLayout();
StackLayout _StackLayout = new StackLayout();
 _StackLayout.Children.Add(new Label(){Text="Execute Me"});

GesturesContentView Gv = new GesturesContentView();
_StackLayout.SetValue(XLabs.Forms.Behaviors.Gestures.InterestsProperty, new GestureCollection() {
                      new GestureInterest() { GestureType = GestureType.SingleTap },
                      new GestureInterest() { GestureType = GestureType.LongPress },
                      new GestureInterest() { GestureType = GestureType.DoubleTap }
            });
Gv.GestureRecognized += Gv_GestureRecognized;
Gv.ExcludeChildren = false;
    Gv.Content = _StackLayout;
_Containter.Children.Add(Gv);
  • 1
    NOTE: This uses Xamarin-Forms-Labs, which is no longer actively maintained. Unfortunately, there does not appear to be any supported replacement yet. – ToolmakerSteve Sep 03 '18 at 22:22
  • This request is being tracked in this open [Enhancement LongPressGestureRecognizer](https://github.com/xamarin/Xamarin.Forms/issues/3480). – ToolmakerSteve Jun 30 '19 at 20:08
0

In order to get this to work properly on iOS, you need to use XLabs.Forms.XFormsAppiOS.Init(); in your AppDelegate.cs file just before the LoadApplication(new App()); statement.

0

The posted code from @zafar works if you register BindingContextChanged event. (My post is only an add, to the original post from @zafar.)

Problem was: if using CommandParameter="{Binding .}" resulting Parameter was always null.

You need to Register BindingContextChanged event in the OnAttachedTo function.

        [...]
        protected override void OnAttachedTo(Button button)
        {
            base.OnAttachedTo(button);
            this.BindingContext = button.BindingContext;
            button.BindingContextChanged += handleBindingContextChanged; //this was missing
            button.Pressed += Button_Pressed;
            button.Released += Button_Released;
        }

        private void handleBindingContextChanged(object sender, EventArgs e)
        {
            this.BindingContext = ((Button)sender).BindingContext;
        }

        protected override void OnDetachingFrom(Button button)
        {
            base.OnDetachingFrom(button);
            this.BindingContext = null;
            button.Pressed -= Button_Pressed;
            button.Released -= Button_Released;
            button.BindingContextChanged -= handleBindingContextChanged; //also don't forget this
        }
        [...]

sry for the errors, this is my first post (not enough Reputation for commenting).

Ragnar474
  • 1
  • 1