Xamarin.Tip – Xamarin.Forms Long Press Effect

Here’s a quick and helpful tool to use in your Xamarin.Forms applications! How many times have you wanted to add a long press handler? Seems like something that should be a simple Gesture built into the platform, but we have to fend for ourselves. Luckily the solution is pretty simple using Xamarin.Forms Effects!

Let’s first create our shared Effect in our shared code:

LongPressedEffect.cs

    /// <summary>
    /// Long pressed effect. Used for invoking commands on long press detection cross platform
    /// </summary>
    public class LongPressedEffect : RoutingEffect
    {
        public LongPressedEffect() : base("MyApp.LongPressedEffect")
        {
        }

        public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
        public static ICommand GetCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(CommandProperty);
        }

        public static void SetCommand(BindableObject view, ICommand value)
        {
            view.SetValue(CommandProperty, value);
        }


        public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
        public static object GetCommandParameter(BindableObject view)
        {
            return view.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(BindableObject view, object value)
        {
            view.SetValue(CommandParameterProperty, value);
        }
    }

Now we have 2 bindable properties – the Command that we want to bind when the long press is detected and the CommandParameter to pass into the Command.

We can use these in our native Effect implementations to invoke when the press is detected. Let’s create our Android implementation.

AndroidLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidLongPressedEffect), "LongPressedEffect")]
namespace AndroidAppNamespace.Effects
{
    /// <summary>
    /// Android long pressed effect.
    /// </summary>
    public class AndroidLongPressedEffect : PlatformEffect
    {
        private bool _attached;

        /// <summary>
        /// Initializer to avoid linking out
        /// </summary>
        public static void Initialize() { }

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.AndroidComponents.Effects.AndroidLongPressedEffect"/> class.
        /// Empty constructor required for the odd Xamarin.Forms reflection constructor search
        /// </summary>
        public AndroidLongPressedEffect()
        {
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
            if (!_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick += Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick += Control_LongClick;
                }
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
        {
            Console.WriteLine("Invoking long click command");
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick -= Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick -= Control_LongClick;
                }
                _attached = false;
            }
        }
    }

And now for iOS:

iOSLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSLongPressedEffect), "LongPressedEffect")]
namespace iOSNamespace.Effects
{
    /// <summary>
    /// iOS long pressed effect
    /// </summary>
    public class iOSLongPressedEffect : PlatformEffect
    {
        private bool _attached;
        private readonly UILongPressGestureRecognizer _longPressRecognizer;
        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.iOSComponents.Effects.iOSLongPressedEffect"/> class.
        /// </summary>
        public iOSLongPressedEffect()
        {
            _longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time
            if (!_attached)
            {
                Container.AddGestureRecognizer(_longPressRecognizer);
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        private void HandleLongClick()
        {
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                Container.RemoveGestureRecognizer(_longPressRecognizer);
                _attached = false;
            }
        }

    }

Now that we have our 2 implementations, let’s use it in our XAML!

MyPage.xaml

<Label Text="Long Press Me!" effects:LongPressedEffect.Command="{Binding ShowAlertCommand}" effects:LongPressedEffect.CommandParameter="{Binding .}">
    <Label.Effects>
        <effects:LongPressedEffect />
    </Label.Effects>
</Label>

Now you can start handling long presses on any control! If you want to add it to a ListView just attach it to either the ViewCell or the internal View of the cell.

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Advertisements

73 thoughts on “Xamarin.Tip – Xamarin.Forms Long Press Effect”

    1. Hi Alex,
      I am trying to implement the above in a test app but have issues with “The name ‘LongPressedEffect’ does not exist in the current context’ in both the Android and iOS cs files.
      I would be grateful and any pointers in the right direction if possible.

      Like

  1. I keep receiving an exception: System.NullReferenceException: Object reference not set to an instance of an object.

    This occurs just after the constructor of the LongPressedEffect object. Anyone else experience this issue?

    Like

      1. Hello, Alex.

        I am just creating a brand-new cross-platform app and adding a basic ListView and your code. I want to be absolutely sure that it is not something on my side.

        Like

  2. Hi Alex,
    I sorted the above issue i had to delete the bin and obj folder in each project and rebuild.
    However I cannot get the binding to work could you please provide a sample on how to do it.
    Thank you.

    Like

  3. I was able to advance so that I am no longer experience the exception. My code compiles and runs. However, when I long press an item in my ListView, nothing happens. No breakpoints are hit, nothing appears in the Output window. Here is my ListView:

    My LongPressedCommand is defined and set.

    Like

      1. I will send to you the XAML code in a few minutes.

        It is interesting to note that the code compiles and executes. I put Breakpoints at the beginning of every method in these classes, as well as at the beginning of my command.

        When I long press, nothing happens. Nothing is printed to the Output window and no Breakpoint is hit.

        Like

      1. That’s what you’re binding your long pressed command to.
        CustomEffects:LongPressedEffect.Command=”{Binding LongPressedCommand}” CustomEffects:LongPressedEffect.CommandParameter=”{Binding .}”

        The Command is going to bind to the Command within the current BindingContext (which is your project list item since you put the effect on the individual cell) which you’ve named LongPressedCommand

        Like

      2. No, I wanted to see both. Your list is bound to a collection of `Project` models. But the `Project` model does not have the LongPressedCommand – the view model does, but you are trying to bind it the command to the LongPressedCommand on the `Project` model which does not exist

        Like

      3. So your options are to either create a command on the project model or change the Binding to point at the parent binding context rather than the individual project item.

        Like

      4. My Project is a regular POCO and knows nothing about the XMAL page or its View Model.
        My bindings are incorrect, in other words. Let me work on that…
        Thank you.

        Like

      5. Try something like this:

        Your listview already has the x:Name of “listView”, so you can change the binding to:
        CustomEffects:LongPressedEffect.Command=”{Binding Path=BindingContext.LongPressedCommand,
        Source={x:Reference listView}}”

        Like

  4. Ray Said> Would you happen to have snippets of code or a working demo of the effect and a ListView?

    Alex if you have the time that would be fantastic, i am trying to get this to work with a button.
    I have had a look on your git-hub and coudnt find anything.

    Like

      1. Testing the effect I have breakpoints on effect. It attaches event, but when doing a long press the event never fires. I guess the LongClick event in Xamarin Forms is not triggering. I’ve tested on all versions of Xamarin Forms 2.4.*.

        Like

  5. Andriod only project, view in PCL project. Snippet of the XAML

    I have switch between the original XF version I had (2.3.4.247) to various versions of 2.4 without any other changes and it stops working

    Like

    1. Have you tried using it outside a listview just to check? I think it may be conflicting with the listview item context actions since on Android it’s invoked with a long press. In 2.4 they added a fix for an Android bug that broke the context actions. That bug is actually the original reason I ever made the effect lol

      Like

    1. It does work if you actually bind the command to anything. I have a video of it too. The problem is your command is not actually bound to anything, so the effect sees the command as null. In your main page change LongPressedEffect.Command=”{Binding}” to LongPressedEffect.Command=”{Binding LongPressed}” since you named your command “LongPressed”.

      Like

      1. Yes!, thanks to Alex and Darren, Thats works! with labels, although with grid, stacklayout, viewcell does not works.. if you know how to make it work please tell me.

        Like

    2. I downloaded this project and after correcting the bind issue that Alex found, I can confirm that the Label does trigger the long-press effect. I also added an Image, for fun. The long-press effect worked for the image!

      However, after adding a ListView and adding the long-press effect to the TextCell, the long-press effect does not trigger. I need the effect on the ListView.

      Like

  6. I’ve tested on various controls on versions of Xamarin Forms from 2.3 – 2.4 and works fine.
    But StackLayout does not work. It attaches as a container to the LongClick event but never fires.
    I am assuming that allow me to attach the event means so should it be reported as a bug?

    Like

  7. Thank you Darren for your Longpressed sample app on GitHub and Alex for the binding fix.
    i am still trying to get this to work with a button (longpress to show\hide a stack layout) and also an Entry ( longpress to clear the entered text).
    i seem to be going round in circles could one of you point me in the right direction please.

    Like

  8. Hi Darren,
    Thank you once again for your help with this i now have it working as i like with a binding Boolean on a stack layout and a binding command sending the button name (i couldn’t work out how to send the button id).
    I know you have no iOS sample so this one maybe Alex can pick up on this if time available.
    When testing with iOS the hidden stacklayout will show but immediately hide again after the button release.
    any ideas?
    Thanks again for your time.

    Like

    1. So this is working for me on both OS’s (not all versions of Android) with Xam.Forms 2.5 and I’m using it in production. I’m wondering if you are having issues with a given OS version or if something else is overriding the long press handler like adding ContextActions to your ViewCells. Anything like that going on?

      Liked by 1 person

      1. It’s working beautifully. Thanks a lot for this effect!
        But it’s overriding my TapGestureRecognizer. I’m using it on an .

        Liked by 1 person

      2. It’s working beautifully. Thanks a lot for this effect!
        But it’s overriding my TapGestureRecognizer. I’m using it on an Image tag.

        Liked by 1 person

  9. Hi Alex

    I appreciate the fast response. I’m currently trying to implement this effect on Android and yes, i’m using a ContextAction on my ListView (ListView.ItemSelected). However, even without the context action, the desire effect does not work on Android for the List View. In this example: https://github.com/lepijulien/LongPressEffectOnListView, you can see my implementation of the effect on a Label and a ListView (the effect works on a Label). If you have the time to check it out, please tell me what I’m doing wrong for the implementation of the effect on a ListView.

    Liked by 1 person

  10. I’ve been trying this for a while now and I’ve managed to get it running on Labels, but it doesn’t really work with ListView in my project. Well, it does, just not in combination with ItemTapped or ItemSelected.

    Is it possible to use the LongPress effect AND ItemTapped together? I’ve managed to do it if the LongPress effect is on a Label, but then it only works if I hit the Label and the regular tap only works if I do not hit the Label.

    Are the two mutual exlusive? Do I need to create a TappedAndLongTappedEffect?

    Like

    1. I have it working with both, but it’s inconsistent for Android (some devices and some versions of the OS are only handling the item selected), but iOS item selected and long press on the viewcell or subviews works perfectly

      Like

      1. Thank you for the quick answer: But just to clarify, do you bind the effect on the ViewCell or something within it?

        Like

  11. Thank you for this example 🙂

    It works great but I had to modify the iOS implementation a bit.
    Because auf the UILongPressGestureRecognizer fired twice I’ve added the if-clause below to the HandleLongClick-Method:

    private void HandleLongClick()
    {
    if(_longPressRecognizer.State == UIGestureRecognizerState.Began)
    {
    var command = LongPressedEffect.GetCommand(Element);
    command?.Execute(LongPressedEffect.GetCommandParameter(Element));
    }
    }

    I had the same issue using native Swift too so I don’t think it’s a Xamarin-Problem

    Like

  12. Hello. I’m trying to implement this and it is not working in my application.

    I’ve tried to run the app examples posted here, like the one who Garren shared, and it is working correctly. I guess that my problem is that i’m not using MVVM in my app and the data binding is failing.

    It is required to have MVVM in my project or am I doing something wrong? I’m just creating the commands on the .cs file of my Page and binding them on the XAML like this: {Binding myCommand}

    Thanks.

    Like

    1. If your BindingContext is set to your page, then it will work. You can also just set it in your C# code directly instead of doing a mix of C# and XAML. Doesn’t require full MVVM

      Like

      1. Yeah I guessed that but still without working.

        As far as I know the binding context is set by default to the .cs of the page, but I’m not sure at all. I even tried to set ‘BindingContext = this” on the constructor but had no effect.

        Is there any other way to set the binding page?

        Like

      2. All right I got this working. The problem was that I need to initialize the command before the ‘InitializeComponent()’. I guess that’s because i’m not implementing the INotifyPropertyChanged.

        However, it is not working in the ViewCell of the listview or any view inside it.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s