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.