JUCE Accessibility on `develop`

Have you looked at the TableListBox example in the WidgetsDemo? It uses custom components for the cells and they are navigable using VoiceOver.

We’ve just pushed some fairly significant changes to the develop branch with support for iOS and Android:

To test it out, the AccessibilityDemo project now has iOS Xcode and Android Studio exporters and can be built for simulators or physical devices.

4 Likes

This should be fixed on develop now:

1 Like

We have received this message from App Store Team after trying to upload our app:

Dear Developer,

We identified one or more issues with a recent delivery for your app, “AppTest" 1.0.0 (5). Please correct the following issues, then upload again.

ITMS-90338: Non-public API usage - The app references non-public selectors in AppTest: accessibilityHitTest:. If method names in your source code match the private Apple APIs listed above, altering your method names will help prevent this app from being flagged in future submissions. In addition, note that one or more of the above APIs may be located in a static library that was included with your app. If so, they must be removed. For further information, visit the Technical Support Information at Requesting Technical Support - Support - Apple Developer

Best regards,

The App Store Team

Maybe the accessibilityHitTest have to be renamed?

1 Like

Hi @ed95 - we’ve just encountered the problem reported by @dev1am as well.

Our message was very slightly different - see the bold text.


Dear Developer,

We identified one or more issues with a recent delivery for your app 
 Please correct the following issues, then upload again.

ITMS-90338: Non-public API usage - The app references non-public selectors in Frameworks/ISSKit.framework/ISSKit: accessibilityHitTest:, sessionRuntimeError:. If method names in your source code match the private Apple APIs listed above, altering your method names will help prevent this app from being flagged in future submissions. In addition, note that one or more of the above APIs may be located in a static library that was included with your app. If so, they must be removed. For further information, visit the Technical Support Information at http://developer.apple.com/support/technical/

Best regards,

The App Store Team


Looking forward to the fix,

Best wishes, Pete

Hi again @ed95,

FYI: sessionRuntimeError is used in juce_ios_CameraDevice.h which we use for our app
 clearly, Apple have updated their binary checker in the past couple of weeks! :slight_smile:

As @dev1am says, the fix should be as simple as renaming the methods. Hard to know how to avoid such name conflicts in the future, however!

Pete

@ed95 looks like accessibilityHitTest is a private UIKit API call - the JUCE solution will require some rework!

I suspect that any iOS app built with JUCE develop tip might fail App Store submission for this reason, at the moment.

Pete

I’m out this week but will look into this and get a fix out when I’m back. Thanks for the reports.

1 Like

Heya folks. Is it OK if I ask general accessibility questions on this thread?
I’m starting to add support to surge-xt with the goal that our late fall / early winter release is a ‘at least pretty good example’ of accessible synth. One of our things is we have quite a few custom widgets, but that’s OK, they are semantically sliders and stuff so I can read the juce code and see how things are hooked up. Today I got this far, for instance:

wahey

(that gif: https://user-images.githubusercontent.com/13387561/128048036-076a2584-0676-41ef-bb38-c88ce078c601.gif)

but I have a set of questions:

  1. As you can see from that animated gif when I jog the value using the jog, my slider moves, my synth adjusts, but the value in the mac accessibility inspector doesn’t seem to change. I get a call to ‘getCurrentValue’ but not ‘getCurrentValueAsString’

  2. The ‘AXDelete’ button automatically resets my slider to the minimum value of the range (it’s like zeroing out the slider it seems). I have a set-to-default function I would like to expose but the value interface doesn’t seem to have a default, and if I add a ‘press’ action to my slider none of the value interface stuff works

  3. If I add a ‘press’ action to my slider, none of the value interface stuff works. showMenu is fine but adding a :press while also have an ValueInterface interface seems to break stuff.

  4. Finally, I’ll be hacking away at this stuff over the next 8-10 weeks along with the other surge xt goals. If folks are interested in helping with surge (really even just from a ‘i know how to test an accessible synth and can give you feedback’ perspective) we always welcome folks to the team.

Thanks

1 Like

Thanks for bearing with me on this one. Removing the accessibilityHitTest private API method exposed some fundamental issues with the iOS accessibility implementation that needed to be fixed.

These commits will fix the app validation issues:

and this commit updates the iOS accessibility implementation:

Please give the new implementation a test and let me know if you run into any further problems.

1 Like

I’ve been testing out the accessibility functions on iOS and noticed that BurgerMenuComponent doesn’t seen to support it. I tried it out on DemoRunner on MacOS and get the same behaviour - the menu items are not being read out by VoiceOver.

One more thing about the modals: they are unreachable from the outside. If you navigate out of the plugin, you can’t go back inside using the keyboard. A possible fix:

AccessibleState AccessibilityHandler::getCurrentState() const
{
    if (component.isCurrentlyBlockedByAnotherModalComponent()
        && Component::getCurrentlyModalComponent()->isVisible()
        && !component.isParentOf (Component::getCurrentlyModalComponent()))
        return {};

    auto state = AccessibleState().withFocusable();

    return hasFocus (false) ? state.withFocused() : state;
}

Also, comboboxes are read weirdly. You can navigate to the label inside, and you’ll get the same text read twice. See for example the current path in FileBrowserComponent.

Should Component::setAccessible() apply to sub components as well? My app isn’t ready for accessibility, so I thought if I called Component::setAccessible(false) on the editor the entire thing wouldn’t be accessible. It just seems to disable keyboard navigation while still reading out whatever controls get keyboard focus.

Also, even though I did call Component::setAccessible(false), my app was still accessible using July 2nd juce, but now it’s not, using yesterday’s juce. Did something change in the way this function is supposed to work?

Yes, disabling accessibility on the parent should also apply to the children (similar to visibility, enablement etc) but it appears to have been broken at some point with the recent changes. I’ve restored the behaviour on develop here:

Additionally the NSWindow accessibility should be fixed here:

After this commit [59333870f] my app is no longer accessible, am I doing something wrong? I’ve also tried to remove all setAccessible(false) from all components but my app still not accessible (I’ve tried only on macOS 11.5).

Can you try the DemoRunner or AccessibilityDemo? Both of these are accessible for me using VoicerOver on macOS with the tip of develop.

    static AccessibilityRole getButtonRole (const Button& b)
    {
        if (b.getRadioGroupId() != 0)     return AccessibilityRole::radioButton;
        if (b.getClickingTogglesState())  return AccessibilityRole::toggleButton;

        return AccessibilityRole::button;
    }

    static AccessibilityActions getAccessibilityActions (Button& button)
    {
        auto actions = AccessibilityActions().addAction (AccessibilityActionType::press,
                                                         [&button] { button.triggerClick(); });

        if (button.getClickingTogglesState())
            actions = actions.addAction (AccessibilityActionType::toggle,
                                         [&button] { button.setToggleState (! button.getToggleState(), sendNotification); });

        return actions;
    }

I do not think this is a valid assumption. You can not assume button.getClickingTogglesState() is true for all toggle buttons. There need to be a flag in the button where you can specify it is a toggle button, it’s valid to change the buttons toggle state manually.

ups, I have just noticed that in Viewport constructor you have set contentHolder.setAccessible (false) and I have my main view in a viewport :sweat_smile:

VO automatically reads help text as default. You can customize the behavior in VO Utility > Verbosity > Hints.

I believe automatic reading of help text is unchecked in narrator setting as default. “Hear advanced detail such as help text.” You can also trigger narrator to read advance detail with narrator(caps lock)-0.