dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
21.99k stars 1.72k forks source link

ContextActions completely inaccessible for blind users on iOS #8775

Open dawidpieper opened 2 years ago

dawidpieper commented 2 years ago

Description

I noticed that it is impossible for blind users on iOS to open context actions on ListViews. Maui expects users to swipe left, but Voice Over does not allow to achieve this result. Context Items should be available on rotor or at least accessible by any other way as a temporary workaround. Currently Maui iOS apps using Context Actions cannot be considered accessible.

Steps to Reproduce

  1. Create any ListView and add ContextActions to it,

    1. Try to open Context Menu with Voice Over turned on,
    2. It is impossible.

    Voice Over properly reads all context items once menu is opened from the code level, but it is impossible to open it by user.

Version with bug

6.0.408 (current)

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

15.5

Did you find any workaround?

It should be possible to make a custom control or renderer to handle it.

Relevant log output

N/A
dawidpieper commented 2 years ago

Thank you very much for taking this issue so seriously. While this is going to be fixed, I would like to remind you about the same problem with SwipeView. #6648

dawidpieper commented 1 year ago

I attach small workaround which we utilize since May with success, on .NET Maui 7.x. It regards problem with SwipeView accessibility, but can be inspirational here as well.

Common

using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;

namespace eltenger.Controls {
    public partial class AccessibleSwipeView : SwipeView {
        public AccessibleSwipeView() : base() {
            ModifySwipe();
            UpdateContextActions();
        }

        protected override void OnChildAdded(Element e) {
            base.OnChildAdded(e);
            UpdateContextActions();
        }

        protected override void OnChildRemoved(Element e, Int32 i) {
            base.OnChildRemoved(e, i);
            UpdateContextActions();
        }

        protected override void OnPropertyChanged(String s) {
            base.OnPropertyChanged(s);
            if(s=="ContextActions") UpdateContextActions();
        }
    }
}

iOS

using System;
using System.Collections.Generic;
using Microsoft.Maui;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Controls;
using UIKit;

namespace eltenger.Controls {
    public partial class AccessibleSwipeView : SwipeView {

        bool _HandlerInitialized=false;

        protected override void OnHandlerChanged() {
            base.OnHandlerChanged();
            _HandlerInitialized=false;
            ModifySwipe();
        }

        protected override void OnParentChanged() {
            base.OnHandlerChanged();
            if(Parent is ViewCell) {
                _HandlerInitialized=false;
                ModifySwipe();
            }
        }

        void ModifySwipe() {
            if(Handler!=null && !_HandlerInitialized) {
                UIView nativeView = (UIView)Handler.PlatformView;
                if(Parent is ViewCell)
                    nativeView = (UIView)Parent.Handler.PlatformView;
                UpdateContextActions();
            }
        }

        public void UpdateContextActions() {
            if(Handler!=null) {
                UIView nativeView = (UIView)Handler.PlatformView;
                if(Parent is ViewCell)
                    nativeView = (UIView)Parent.Handler.PlatformView;
                var actions = new List<UIAccessibilityCustomAction>();
                foreach(var el in LeftItems) {
                    if(!el.IsVisible) continue;
                    Func<UIAccessibilityCustomAction, bool> dl = (UIAccessibilityCustomAction) => {
                        el.OnInvoked();
                        return true;
                    };
                    var action = new UIAccessibilityCustomAction(((SwipeItem)el).Text, dl);
                    actions.Add(action);
                }
                nativeView.AccessibilityCustomActions = actions.ToArray();
            }
        }
    }
}