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:
parent
17fe5a54d8
commit
dd18711493
4 changed files with 198 additions and 69 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue