Xamarin.Forms icon indicating copy to clipboard operation
Xamarin.Forms copied to clipboard

[iOS,UWP] Button Released event not fired if Release occurs outside of Button's Layout

Open mzhukovs opened this issue 7 years ago • 11 comments

Description

If you attach a Release event to a button in Xamarin Forms you'll notice that it won't get fired when running on iOS if you press the button, maintain your press and move your finger off of the button and then release.

Steps to Reproduce

  1. In any XF project, add a button to your XAML with x:Name="Btn"
  2. In code-behind, add: Btn.Pressed += (sender, e) => { Btn.BackgroundColor = Color.LightSeaGreen; }; Btn.Released+= (sender, e) => { Btn.BackgroundColor = Color.Orange; };
  3. Run on iOS simulator/device and press the button, maintain your press and move your finger off of the button and then release

Expected Behavior

Released event would fire and change the button background color to Orange

Actual Behavior

Released event does NOT fire and button background color remains LightSeaGreen that was set by the Pressed event.

Basic Information

  • Version with issue: latest
  • Last known good version: n/a
  • IDE: VS2017 Enterprise latest
  • Platform Target Frameworks:
    • iOS: 11.4 -UWP
  • Nuget Packages: irrelevant
  • Affected Devices: all

iOSButtonRelease.zip

mzhukovs avatar Jul 15 '18 05:07 mzhukovs

It looks like only Android fires the release event

PureWeen avatar Jul 18 '18 02:07 PureWeen

I was browsing the iOS code and saw that the Released event is only fired from OnButtonTouchUpInside.

mattleibow avatar Jan 13 '19 01:01 mattleibow

There should be a property in the event args that allows us to know whether the release occured inside or outside the button. That's important.

SuperCorks avatar Jan 29 '19 20:01 SuperCorks

Hello, any news on this bugfix or there is some workaround? Thanks

matteopiccioni avatar May 21 '19 13:05 matteopiccioni

I agree with @SuperCorks , then dev can decide whether to ignore and not fire release event if release occurred outside of button bounds.

mzhukovs avatar May 25 '19 12:05 mzhukovs

Hey all, Altough @PureWeen had reported that the event DOES fire on android, I would like to report that I am using the latest nuget version of XamarinForms and the event does NOT fire on android. steps to reproduce are the same as the OP of the issue suggested. thanks in advance.

Edit: A (not the best) workaround that I found for now is to have a boolean field that states if the button was not realeased properly, as following: ... private bool WORKAROUND_BtnReleasedProblem; ... private void Button_Released(object sender, EventArgs e) { WORKAROUND_BtnReleasedProblem = false; ViewBackgroundColor = Color.Transparent; WhiteBackground = !WhiteBackground; SetColor(); }

private void Button_Pressed(object sender, EventArgs e) { if (WORKAROUND_BtnReleasedProblem) Button_Released(sender, null); ViewBackgroundColor = BorderColor; WhiteBackground = !WhiteBackground; SetColor(); WORKAROUND_BtnReleasedProblem = true; }

thus when pressed again, the button would return to the previous state of 'released'

3xcellentTuber avatar Sep 07 '19 14:09 3xcellentTuber

Hi, just to mention that as an iOS workaround, I wrote a platform effect that triggers the released event when the touch up occurs outside the button bounds.

using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ResolutionGroupName("Effects")]
[assembly: ExportEffect(typeof(EffectTest.iOS.Effects.SpecialButtonEffect), nameof(EffectTest.iOS.Effects.SpecialButtonEffect))]
namespace EffectTest.iOS.Effects
{
    public class SpecialButtonEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            (Control as UIButton).TouchUpOutside += OnTouchUpOutside;
        }

        protected override void OnDetached()
        {
            (Control as UIButton).TouchUpOutside -= OnTouchUpOutside;
        }

        private void OnTouchUpOutside(object sender, EventArgs e)
        {
            (Element as IButtonController).SendReleased();
        }
    }
}

ghost avatar Dec 14 '19 23:12 ghost

I experience this problem on Android. Forms 4.5.

AdamDiament avatar Mar 10 '20 10:03 AdamDiament

Thanks for the suggestion @Peseur. I bumped into a similar issue with the ImageButton on iOS, reported in #10859, then discovered that the same happens with regular Buttons on iOS too, so ended up here. Your solution inspired me to write my own, which will not just send the Released-event, but will also fix reverting the visual state to normal (which was my original issue.

AlexanderMelchers avatar May 28 '20 19:05 AlexanderMelchers

Same problem on Android (XF 3.6) : Released event not triggered when releasing the button outside.

With a LongPressBehavior :

` public class LongPressBehavior : Behavior<Button> { private readonly object _syncObject = new object(); private const int Duration = 700;

    //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(DelegateCommand<object>), typeof(LongPressBehavior), default);

    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 DelegateCommand<object> Command
    {
        get => (DelegateCommand<object>)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);
        Console.WriteLine(Command is null);
        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);
    }
}`

PauchardThomas avatar Sep 16 '20 10:09 PauchardThomas

On android release event also not fired outside of button if it putted inside scroll view.

slava12154 avatar Aug 17 '22 09:08 slava12154