1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

iOS: Use UIAccessibilityContainer methods for correct accessibility hierarchy and navigation

This commit is contained in:
ed 2021-08-13 09:04:39 +01:00
parent 17fe5a54d8
commit dd18711493
4 changed files with 198 additions and 69 deletions

View file

@ -53,13 +53,42 @@ constexpr auto juceUIAccessibilityContainerTypeList =
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
//==============================================================================
static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
{
const auto children = handler.getChildren();
NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
[accessibleChildren addObject: (id) handler.getNativeImplementation()];
for (auto* childHandler : children)
{
id accessibleElement = [&childHandler]
{
id native = (id) childHandler->getNativeImplementation();
if (childHandler->getChildren().size() > 0)
return [native accessibilityContainer];
return native;
}();
if (accessibleElement != nil)
[accessibleChildren addObject: accessibleElement];
}
return accessibleChildren;
}
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
: accessibilityElement (AccessibilityElement::create (handler))
{}
{
}
UIAccessibilityElement* getAccessibilityElement() const noexcept
{
@ -67,6 +96,71 @@ public:
}
private:
//==============================================================================
class AccessibilityContainer : public ObjCClass<UIAccessibilityElement>
{
public:
AccessibilityContainer()
: ObjCClass ("JUCEUIAccessibilityElementContainer_")
{
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
addMethod (@selector (accessibilityElements), getAccessibilityElements, "@@:");
addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:");
addIvar<AccessibilityHandler*> ("handler");
registerClass();
}
private:
static AccessibilityHandler* getHandler (id self)
{
return getIvar<AccessibilityHandler*> (self, "handler");
}
static BOOL getIsAccessibilityElement (id, SEL)
{
return NO;
}
static CGRect getAccessibilityFrame (id self, SEL)
{
if (auto* handler = getHandler (self))
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
}
static NSArray* getAccessibilityElements (id self, SEL)
{
if (auto* handler = getHandler (self))
return getContainerAccessibilityElements (*handler);
return nil;
}
static NSInteger getAccessibilityContainerType (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (handler->getTableInterface() != nullptr)
return juceUIAccessibilityContainerTypeDataTable;
const auto role = handler->getRole();
if (role == AccessibilityRole::popupMenu
|| role == AccessibilityRole::list
|| role == AccessibilityRole::tree)
{
return juceUIAccessibilityContainerTypeList;
}
}
return juceUIAccessibilityContainerTypeNone;
}
};
//==============================================================================
class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement>
{
@ -82,6 +176,8 @@ private:
private:
AccessibilityElement()
{
addMethod (@selector (dealloc), dealloc, "v@:");
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
addMethod (@selector (accessibilityContainer), getAccessibilityContainer, "@@:");
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
@ -90,14 +186,13 @@ private:
addMethod (@selector (accessibilityHint), getAccessibilityHelp, "@@:");
addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
addMethod (@selector (accessibilityElements), getAccessibilityChildren, "@@:");
addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain, "v@:");
addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss, "v@:");
addMethod (@selector (accessibilityElementIsFocused), isFocused, "c@:");
addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal, "c@:");
addMethod (@selector (accessibilityActivate), accessibilityPerformPress, "c@:");
addMethod (@selector (accessibilityActivate), accessibilityPerformActivate, "c@:");
addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement, "c@:");
addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement, "c@:");
@ -106,8 +201,6 @@ private:
addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber, @encode (CGRect), "@:i");
addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent, "@@:");
addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:");
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn, "@@:ii");
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
@ -116,18 +209,54 @@ private:
addProtocol (@protocol (UIAccessibilityReadingContent));
addIvar<UIAccessibilityElement*> ("container");
registerClass();
}
//==============================================================================
static UIAccessibilityElement* getContainer (id self)
{
return getIvar<UIAccessibilityElement*> (self, "container");
}
//==============================================================================
static void dealloc (id self, SEL)
{
if (UIAccessibilityElement* container = getContainer (self))
{
[container release];
object_setInstanceVariable (self, "container", nullptr);
}
sendSuperclassMessage<void> (self, @selector (dealloc));
}
static id getAccessibilityContainer (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* parent = handler->getParent())
return (id) parent->getNativeImplementation();
if (handler->getComponent().isOnDesktop())
return (id) handler->getComponent().getWindowHandle();
return (id) handler->getComponent().getWindowHandle();
if (handler->getChildren().size() > 0)
{
if (UIAccessibilityElement* container = getContainer (self))
return container;
static AccessibilityContainer cls;
id windowHandle = (id) handler->getComponent().getWindowHandle();
UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
object_setInstanceVariable (container, "handler", handler);
[container retain];
object_setInstanceVariable (self, "container", container);
return (id) getContainer (self);
}
if (auto* parent = handler->getParent())
return [(id) parent->getNativeImplementation() accessibilityContainer];
}
return nil;
@ -162,10 +291,10 @@ private:
case AccessibilityRole::image: return UIAccessibilityTraitImage;
case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
case AccessibilityRole::slider:
case AccessibilityRole::editableText:
case AccessibilityRole::menuItem:
case AccessibilityRole::menuBar:
case AccessibilityRole::popupMenu:
@ -190,7 +319,9 @@ private:
return UIAccessibilityTraitNone;
}();
if (handler->getCurrentState().isSelected())
const auto state = handler->getCurrentState();
if (state.isSelected() || state.isChecked())
traits |= UIAccessibilityTraitSelected;
if (auto* valueInterface = getValueInterface (self))
@ -228,6 +359,24 @@ private:
return NO;
}
static BOOL accessibilityPerformActivate (id self, SEL)
{
if (auto* handler = getHandler (self))
{
// occasionaly VoiceOver sends accessibilityActivate to the wrong element, so we first query
// which element it thinks has focus and forward the event on to that element if it differs
id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
if (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
return [focusedElement accessibilityActivate];
if (handler->hasFocus (false))
return accessibilityPerformPress (self, {});
}
return NO;
}
static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
{
if (auto* handler = getHandler (self))
@ -284,26 +433,6 @@ private:
return nil;
}
static NSInteger getAccessibilityContainerType (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (getTableInterface (self) != nullptr)
return juceUIAccessibilityContainerTypeDataTable;
const auto role = handler->getRole();
if (role == AccessibilityRole::popupMenu
|| role == AccessibilityRole::list
|| role == AccessibilityRole::tree)
{
return juceUIAccessibilityContainerTypeList;
}
}
return juceUIAccessibilityContainerTypeNone;
}
static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
{
if (auto* tableInterface = getTableInterface (self))
@ -363,12 +492,14 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, Inte
return UIAccessibilityNotifications{};
}();
const auto shouldMoveToHandler = (eventType == InternalAccessibilityEvent::elementCreated
|| eventType == InternalAccessibilityEvent::windowOpened
|| (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false)));
if (notification != UIAccessibilityNotifications{})
sendAccessibilityEvent (notification, shouldMoveToHandler ? (id) handler.getNativeImplementation() : nil);
{
const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
sendAccessibilityEvent (notification,
moveToHandler ? (id) handler.getNativeImplementation() : nil);
}
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const

View file

@ -262,6 +262,23 @@ private:
return nil;
}
static NSArray* getAccessibilityChildren (id self, SEL)
{
if (auto* handler = getHandler (self))
{
auto children = handler->getChildren();
auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
for (auto* childHandler : children)
[accessibleChildren addObject: (id) childHandler->getNativeImplementation()];
return accessibleChildren;
}
return nil;
}
static NSArray* getAccessibilitySelectedChildren (id self, SEL)
{
return getSelectedChildren ([self accessibilityChildren]);

View file

@ -193,8 +193,10 @@ protected:
NSString* nsString = juceStringToNS (title);
#if ! JUCE_IOS
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
return @"";
#endif
return nsString;
}
@ -218,23 +220,6 @@ protected:
return NO;
}
static NSArray* getAccessibilityChildren (id self, SEL)
{
if (auto* handler = getHandler (self))
{
auto children = handler->getChildren();
NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
for (auto* childHandler : children)
[accessibleChildren addObject: (id) childHandler->getNativeImplementation()];
return accessibleChildren;
}
return nil;
}
static NSInteger getAccessibilityRowCount (id self, SEL)
{
if (auto* tableInterface = getTableInterface (self))

View file

@ -138,9 +138,8 @@ using namespace juce;
- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text;
- (BOOL) isAccessibilityElement;
- (id) accessibilityElementAtIndex: (NSInteger) index;
- (NSInteger) accessibilityElementCount;
- (NSInteger) indexOfAccessibilityElement: (id) element;
- (CGRect) accessibilityFrame;
- (NSArray*) accessibilityElements;
@end
//==============================================================================
@ -550,25 +549,24 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
return NO;
}
- (id) accessibilityElementAtIndex: (NSInteger) index
- (CGRect) accessibilityFrame
{
if (owner != nullptr)
if (auto* handler = owner->getComponent().getAccessibilityHandler())
return (id) handler->getNativeImplementation();
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
}
- (NSArray*) accessibilityElements
{
if (owner != nullptr)
if (auto* handler = owner->getComponent().getAccessibilityHandler())
return getContainerAccessibilityElements (*handler);
return nil;
}
- (NSInteger) accessibilityElementCount
{
return owner != nullptr ? 1 : 0;
}
- (NSInteger) indexOfAccessibilityElement: (id) element
{
return 0;
}
@end
//==============================================================================
@ -618,9 +616,7 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags,
#if JUCE_COREGRAPHICS_DRAW_ASYNC
if (! getComponentAsyncLayerBackedViewDisabled (component))
{
[[view layer] setDrawsAsynchronously: YES];
}
#endif
if (isSharedWindow)