Xamarin.forms: [Bug] If users don't specify any TabIndexes or TabStops then just let the native OS do its thing

Created on 13 Jan 2020  路  14Comments  路  Source: xamarin/Xamarin.Forms

Description

By default iOS/Android to a fairly good job articulating TabIndex orders based on view orders and the changes in 4.0 broke this implicit behavior.

We need to look at just letting the operating system do its thing if the user hasn't opted into any TabIndex or TabStop behaviors

Steps to Reproduce

  1. Compare default TabIndex behavior on a 3.6 app to a 4.0 app

Expected Behavior

both apps should act the same

Actual Behavior

4.0 is too heavy handed and replaces default behavior

Basic Information

  • Version with issue: 4.0
  • Last known good version: 3.6
  • IDE:
  • Platform Target Frameworks:

    • iOS:

    • Android:

    • UWP:

Workaround

  • On iOS the workaround has been to override ViewLoad and return a different view then PageContainer
3.3.0 a11y 馃攳 4 in-progress high impact bug

All 14 comments

Our tab order is hopelessly broken, but the workaround suggested in the description worked for me perfectly. Without this workaround, our app is a nightmare in terms screen reader order. Please make this one a priority!

I'll add that this problem centers around using VoiceOver on iOS for me. When moving from item to item with VoiceOver on, the order does not make logical since. VoiceOver focus become stuck in random places (seemingly due to labels or buttons being nested in views) and generally jumping around unpredictably. Again, the workaround described here fixes the VoiceOver experience.

Anyone have ideas for a possible Android workaround?

The Android workaround is a little more wild, unfortunately

The only solution we were able to come up with was to use reflection to override the default value of TabStop to false.

public static class XamarinFormsHacks
    {
        // Compiler generated fields have this format.
        private const string AutoPropertyBackingFieldFormat = "<{0}>k__BackingField";

        public static void ForceVisualElementIsTabStopToDefaultToFalse()
        {
            var defaultValueCreatorBackingFieldName = string.Format(AutoPropertyBackingFieldFormat, "DefaultValueCreator");

            var fieldInfo = typeof(BindableProperty)
                .GetField(defaultValueCreatorBackingFieldName, BindingFlags.NonPublic | BindingFlags.Instance)
                ?? throw new MissingMethodException(DefaultValueCreatorFieldMissingMessage);

            fieldInfo.SetValue(VisualElement.IsTabStopProperty, (BindableProperty.CreateDefaultValueDelegate)CreateDefaultValueDelegate);

            static object CreateDefaultValueDelegate(BindableObject _) => false;
        }
    }

Thanks @PureWeen and @samhouts for continuing to pursue this even though I did not manage to find the time to lodge my own bug reports

@arevellfraedom Your Android workaround did the job nicely :) Thank you for sharing!

Just be aware @ryl that this work around breaks external keyboard navigation, which we only figured out just yesterday so we are looking to work around a different way

Note that I have commented in https://github.com/xamarin/Xamarin.Forms/pull/9702 that I think that "If users don't specify any TabIndexes or TabStops then just let the native OS do it's thing" should also apply to how keyboard focus changes are processed on Android. The current implementation makes the keyboard focus problem when TabIndex and TabStop are not set

Ugh, Just tried VoiceOver as part of ending of testperiod with a medical app here, have bumped from 3.6 to 4.x, and now we have critical error with the app being totally unpredictable on iOS with accessability. Taborder is completely broken and some objects can't be reached anymore. Is there a workaround??? what have happended? do we need to specify TabIndex on ALL objects now?

@conradh The workaround listed toward the bottom of the original issue description seems to be working for iOS. Something like this...

[assembly: ExportRenderer(typeof(ContentPage), typeof(A11yPageRenderer))]
namespace App.iOS.Renderers {

  // https://github.com/xamarin/Xamarin.Forms/issues/9191
  // This PageRenderer uses the workaround described in the issue above to fix broken tab order when VoiceOver is on.
  public class A11yPageRenderer : ContentPageRenderer {

    public override void LoadView() {
      View = new UIView(UIScreen.MainScreen.Bounds);
    }

  }

}

@conradh (and anyone else here) If you could test your project with the nuget attached here and let me know if it causes any additional problems or doesn't resolve your problem at all, that would be great! Thanks!

https://github.com/xamarin/Xamarin.Forms/pull/9702#issuecomment-593727682

Awesome, will get back after testing, probably later this afternoon (UTC +01)

Summary:

RYI + Nuget

I Click an item so that new page is pushed on stack, Focus gets to first element on the page. Then i immediatley tab backwards and expect focus to reach titlebar title, but tab backwards sets focus to an object on the previous page (The last button on that page). tab forward and your are back on the page contents top element again, tab forward a couple of times, and then backwards, it tabs correctly all the way up to titlebar and the top left back-arrow. Now i need to return back to previous page, and open this page again to reproduce it again.

Also seems like some Invisible Buttons are included in taborder...

Only Ryl's renderer:
works ok.

Only new Nuget
Not all objects are included in taborder. Get stuck.

Invisible objects are included in taborder

Conclusion:
The new Nuget would break accessability in our app, but the ContePageRenderer together with v4.4 seems to work OK!

@samhouts @ryl
I find ContentPageRenderer solves all "new" problems after upgrading fro 3.6, except an issue on our MainPage (TabbedPage).
The issue is that the user gets stuck tabbing round in circles between the first object on the page, and the tabs at the bottom, not reaching the listview items. Maybe this is intentional? Wasn't like that before anyway.

The nuget was too buggy to use for use. Would need workarounds like specifying tabindex then i guess. and ensuring all invisible objects also are IsEnabled=false to get it working.

Was this page helpful?
0 / 5 - 0 ratings