0

I have a class which subclasses UIAccessibilityElement for the purposes of enabling accessibility in my Cocos2D app.

At run time, the instances of this class are all navigable via iOS' accessibility features and this is great, but I have a specific need to be able to change the language and accent used by iOS (this will only work on iOS7+) for each element.

I note that to do this I am supposed to override the:

- (NSString*) accessibilityElementLanguage;

message, which I have done.

I've confirmed via the accessibility inspector in the simulator that the correct language ID is being returned (it is displayed in the inspector).

When voiceover reads the text associated with the element however, it insists on using the default device voice and language, so Spanish words are understandably sounding completely incorrect.

I'm not sure what I'm doing wrong. When the language setting failed to work, I tried a workaround whereby I would handle a accessibilityElementDidBecomeFocused callback and read the text using my own code, but now I don't even get the callbacks for the focus events unless I am running the simulator with the accessibility inspector enabled, and I touch one of my elements.

I'm sure I have something simple wrong here. Any help would be appreciated.

The code for the class interface is:

#import <UIKit/UIKit.h>
#import "CCSwitchableNode.h"
#import <UIKit/UIAccessibility.h>

@interface AccessibleSwitchableNode : UIAccessibilityElement <UIAccessibilityIdentification>

- (id) initWithNode:(id<CCSwitchableNode>)node inContainer:(id)container;

+ (AccessibleSwitchableNode*) accessibleSwitchableWithNode:(id<CCSwitchableNode>)node inContainer:(id)container;

@property (nonatomic, retain) id<CCSwitchableNode> node;

@end

And the implementation:

#import "AccessibleSwitchableNode.h"
#import <UIKit/UIAccessibility.h>
#import <UIKit/UIAccessibilityElement.h>

@implementation AccessibleSwitchableNode {

    BOOL isFocused_Local;

}

- (id) initWithNode:(id<CCSwitchableNode>)node inContainer:(id)container {
    self = [super initWithAccessibilityContainer:container];

    if (self != nil) {
        isFocused_Local = NO;

        self.node = node;

        // This code is a quick hack to translate the position from Cocos2d in one specific orientation
        // to UIKit.  To be finessed.
        //
        CGPoint uiPoint = [[CCDirector sharedDirector] convertToUI:[node switchableNodePosition]];
        uiPoint.x = [CocosUtil screenWidth] - uiPoint.x;

        self.accessibilityFrame =
        CGRectMake(uiPoint.y-([node switchableNodeSize].height/2.0f),
                   uiPoint.x-([node switchableNodeSize].width/2.0f),
                   [node switchableNodeSize].height,
                   [node switchableNodeSize].width);

        self.accessibilityLabel = [node textForSpeaking];
        self.accessibilityValue = [node textForSpeaking];
        self.accessibilityTraits = UIAccessibilityTraitButton;
        self.accessibilityActivationPoint = CGPointMake(uiPoint.y, uiPoint.x);
    }

    return self;
}

+ (AccessibleSwitchableNode*) accessibleSwitchableWithNode:(id<CCSwitchableNode>)node inContainer:(id)container {
    return [[[AccessibleSwitchableNode alloc] initWithNode:node inContainer:container] autorelease];
}

- (void) dealloc {
    self.node = nil;

    [super dealloc];
}

// Called when the object is first added, but has no effect on pronunciation.
//
- (NSString*) accessibilityLanguage {
    return [self.node languageForText];
}

// Never called unless on the simulator with the inspector active.
//
- (void) accessibilityElementDidBecomeFocused {
    NSLog(@"accessibilityElementDidBecomeFocusedd: %@", self.accessibilityLabel);
    isFocused_Local = YES;
}

// Never called unless on the simulator with the inspector active.
//
- (void) accessibilityElementDidLoseFocus {
    NSLog(@"accessibilityElementDidLoseFocus: %@", self.accessibilityLabel);
    isFocused_Local = NO;
}

// Never called unless on the simulator with the inspector active.
//
- (BOOL) accessibilityElementIsFocused {
    NSLog(@"accessibilityElementIsFocused: %@", self.accessibilityLabel);
    return isFocused_Local;
}

// Never called unless on the simulator with the inspector active.
//
- (BOOL) accessibilityActivate {
    if ([self.node isSwitchSelectable] == YES) {
        [self.node switchSelect];

        return YES;
    } else {
        return NO;
    }
}

- (BOOL) isAccessibilityElement {
    return [self.node isSwitchSelectable];
}

@end

The protocol for the Cocos2D nodes that I am adding to the AccessibilityElements is:

#import <Foundation/Foundation.h>

@protocol CCSwitchableNode <NSObject>

// Should return the size of the node on screen.
//
- (CGSize) switchableNodeSize;

// Should return the position (center) of the node in screen coordinates.
//
- (CGPoint) switchableNodePosition;

// Should return YES if the node is currently able to accept taps by the user.
//
- (BOOL) isSwitchSelectable;

// Should cause the node, be it a button of some sort, or something else, to act as if it has been
// tapped.  This will be called when a switch is used to "select" an item on the screen.
//
- (void) switchSelect;

// Should return YES if the node would prefer to highlight itself to the user as selectable.  If this
// happens, the SwitchControlLayer will call the setSwitchHighlighted: as appropriate in
// order to turn on or off the highlight.
//
- (BOOL) hasOwnHighlight;

// Should return a NSString containing the text to be spoken when the node is highlighted.  If no text is
// to be spoken, then nil should be returned.
//
- (NSString*) textForSpeaking;

// Should return a NSString containing the language locale to use when speaking the nodes text.
//
- (NSString*) languageForText;

@optional

// This optional method should be implemented in classes that can return YES from the hasOwnHighlight
// method.  The SwitchControlLayer will call this instead of using it's own built-in highlight
// to turn on, or off the highlight.
//
- (void) setSwitchHighlighted:(BOOL)highlighted;

@end
PKCLsoft
  • 1,359
  • 2
  • 26
  • 35
  • I may have answered my question. It appears that if I enable voiceover it all works as I expected it to. If voiceover is off, and you are using switch access instead, then it doesn't work. If anyone has a way to get this to work without enabling voiceover, I'd love to know. – PKCLsoft Jul 14 '14 at 08:09
  • For those that want access to my full implementation, it can be found in https://bitbucket.org/pkclsoft/accessiblecocos2d There are still issues with iOS7 as in my OP, but it looks like these will be addressed in iOS8. – PKCLsoft Aug 19 '14 at 14:12

2 Answers2

1

This may surprise and frighten you, but even Apple ships bugs. If you're encountering unexpected or subjectively "wrong" behavior, consider filing a Radar with comprehensive reproduction steps and a sample project.

Justin
  • 20,509
  • 6
  • 47
  • 58
  • 2
    No it doesn't surprise or frighten me. I've been around way too long for that. I still have my GS/OS reference manuals with my had written notes in them to detail errors and issues. I filed a bug yesterday with a sample project and am also trying to get support via one of my DTS tickets. Thanks for the comment but I wouldn't call this an answer to the problem itself. – PKCLsoft Jul 15 '14 at 23:06
  • Upvoted your comment. Please do share back when you receive a reply from DTS. – Justin Jul 15 '14 at 23:16
  • My intent is to turn this into a standalone project that I'll share on github or similar so that other Cocos2D developers have a (hopefully) easy path to make their apps accessible. By all means, I'll include the results from my DTS query in my sample project which will be shared. – PKCLsoft Jul 15 '14 at 23:55
  • One or two people have approached me with questions about Cocos2D accessibility over the years. I'm very curious to see how it turns out. Thank you for making an attempt! – Justin Jul 16 '14 at 00:56
  • I find it fascinating that you're developing this specifically for switch users. Surely screen reader users could benefit as well … – Justin Jul 16 '14 at 00:58
  • 1
    Well that's just it. I __had__ developed my own API for switch users but have realised that I can support far more people if I do away with mine, and use Apple's. The downside is that some of the Apple API is only available on iOS7+. I much prefer to use Apple's API. As you say, there are other aspects to accessibility. My original efforts were in response to a specific request from users of my first special needs app. – PKCLsoft Jul 16 '14 at 01:38
  • OK. Beyond disappointed. DTS have responded, and basically stated that there is no API for switch control, implying that trying to use what __is_ documented, I am actually using the VoiceOver API so don't expect it to work for switch control. This means I have to do my own switch control interface for my current app which is far from ideal. – PKCLsoft Jul 17 '14 at 01:07
  • You're writing to the `UIAccessibility` protocol, not VoiceOver or Switch Control. It's up to each accessibility client to determine how to make the most the attributes you expose. Nonetheless, you may request enhancements to both the generic accessibility APIs and the Switch Control product. If one isn't using the other the way you'd expect, file away. – Justin Jul 17 '14 at 03:19
  • Finishing this off here. DTS did recommend keeping the existing bug report active so maybe there's hope for iOS8... – PKCLsoft Jul 17 '14 at 04:43
1

As per some of my comments above, the short answer is that iOS7 does not support the various callbacks and API's for Switch Control in the same way that VoiceOver does. What works for VoiceOver will not necessarily work for Switch Control.

Having filed a bug with Apple, and interacted with them there, testing my code against iOS8 shows that support for Switch Control has improved by beta 5, but is still incomplete. The accessibilityElementDidBecomeFocused callbacks appear to work now however Switch Control still refuses to use the accessibilityElementLanguage callback.

PKCLsoft
  • 1,359
  • 2
  • 26
  • 35