maui: [regression/8.0.3] `TapGestureRecognizer` no longer works on `Button`

Description

We updated our app to MAUI 8 and built in on Android, and everything worked fine. However we were shocked when we started testing the iOS app. Almost none of the buttons were working.

After more testing we concluded that any button behaviour that relied on a TapGestureRecognizer didn’t work any more. Even if we manually bound the Clicked event instead, it would not work if we also had e.g. AnimationBehavior (which uses a TapGestureRecognizer under the hood by default) attached.

I looked through the code, and I can’t see any recent change to the iOS Button code or the TapGestureRecognizer code that could have caused this change, or I’d have tried to PR a fix myself.

The only buttons that kept working were ones that bound Clicked in XAML, but as our app contains only a single static settings page, and the rest of the content is dynamically loaded and rendered, the MAUI 8 version of app is completely unusable on iOS.

Steps to Reproduce

  1. Create a new MAUI 8 app.

  2. Add a new page e.g. ButtonTestPage.cs:

    public class ButtonTestPage : ContentPage
    {
      public ButtonTestPage()
      {
        var button = new Button();
        button.GestureRecognizers.Add(new TapGestureRecognizer() {
          Command = new Command(() => DisplayAlert("Yay!", "It works!", "Ok"))
        });
        Content = button;
      }
    }
    
  3. Ensure that page is initially loaded.

  4. Build an Android version and an iOS version.

  5. Test both:

    • Button works fine in Android
    • Button doesn’t work in iOS.

Link to public reproduction project repository

No response

Version with bug

8.0.3

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

7.0.101

Affected platforms

iOS

Affected platform versions

Observed on iOS 15, 16, 17, potentially affects all.

Did you find any workaround?

For now we’ve made two extension methods, that we use whenever we need to add tap handling to any element. The code below is not exactly what we use, as we wrote something that better integrates with CommunityToolkit.Maui.Markup’s fluent API, and adds extra behaviours for graphical and haptic feedback. However, the essence of the workaround is that we leverage extension method resolution order to use an EventToCommandBehavior instead of a TapGestureRecognizer specifically for Buttons. The big advantage is that when this issue gets fixed, we only need to remove the Button specific extension method, no other code changes are required:

public static class TapExtensions
{
  public static void AddTapGesture<TButton>(this TButton self, Action onTapped)
    where TButton : Button
  {
    self.Behaviors.Add(new EventToCommandBehavior
        {
          EventName = nameof(Button.Clicked),
          Command = new Command(onTapped)
        })
  }
  
  public static void AddTapGesture<TView>(this TView self, Action onTapped)
    where TView : View, IGestureRecognizers
  {
    self.GestureRecognizers.Add(new TapGestureRecognizer() {
      Command = onTapped
    });
  }
}

Relevant log output

No response

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Comments: 16 (12 by maintainers)

Most upvoted comments

No, I get that, and you point is valid. Now we use those other ways as a workaround, but we preferred using the most broadly applicable API, which turned out to be TapGestureRecognizer. We are aware that it’s not the optimal approach in all circumstances.

Why would you use a TapGestureRecognizer rather than set the Command directly on the button?

From what I understand, a TapGestureRecognizer is intended for views that don’t handle Clicked or Command events (Ex. Image). Since Button already has a Command property, you could use that without requiring extra overhead. Not to say this shouldn’t be addressed (as it apparently worked at once point) but there could be other ways to handle what you’re trying to do.