Which view should you use for UIAccessibilityConvertFrameToScreenCoordinates?

1.1k views Asked by At

Can anybody clearly explain the UIView that UIAccessibilityConvertFrameToScreenCoordinates expects for its second argument? Sometimes it behaves as I would expect, drawing VoiceOver's focus rectangle over the appropriate element, but other times using this helper function just seems like voodoo!

Apple's documentation doesn't seem to have an explanation. It states that the argument must be the view that contains the CGRect, but in some implementations it isn't clear which view is CG's current context. Is there an easy way to work this out?

As an example, I'm creating a custom UITableViewCell which has a custom VoiceOver focus order by overriding the cell's UIAccessibilityContainer implementation (see below). The accessibilityFrame is way off no matter what I try. I've tried sending a reference to the associated UITableView, the parent UIView containing the interface, and the window itself. I feel like I've gone through the entire view stack!

The following isn't the full code for the custom UITableViewCell class, but should give you an idea of what I'm trying to do.

#pragma mark - Accessibility (UIAccessibilityContainer implementation)

- (id)accessibilityElementAtIndex:(NSInteger)index
{
    return [self.accessibilityObjects objectAtIndex:index];
}

- (NSInteger)accessibilityElementCount
{
    return self.accessibilityObjects.count;
}

- (NSInteger)indexOfAccessibilityElement:(id)element
{
    return [self.accessibilityObjects indexOfObject:element];
}

- (void *)addAccessibilityElement:(UIView *)accessibleView{
    if (self.accessibilityObjects == nil) {
        self.accessibilityObjects = [NSMutableArray array];
    }

    UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
    accessibilityElement.accessibilityLabel = accessibleView.accessibilityLabel;
    //accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibleView.frame, self.contentView);
    //accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibleView.frame, self.parentView); // parentView is a reference to the view controller's parent view
    accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibleView.frame, self.tableView); // tableView is a reference to the cell's UITableView

    [self.accessibilityObjects addObject:accessibilityElement];
}

I hope this makes sense to somebody. Thanks in advance.

2

There are 2 answers

0
MobA11y On

Let's look at the purpose of this function from the documentation:

Converts the specified rectangle from view coordinates to screen coordinates.

So, you are passing this function the rectangle of the view, within its parent view, and then the parent view itself. Given this information, you get an output that is the screen coordinates of the view. So, the answer is, the owning view of the rectangle, from which you calculated the relative rectangle. This would usually be it's hierarchically immediate parent view, but could potentially be higher up the view hierarchy.

If for, example, your rectangle were {0, 0, view.width, view.height}, and you passed in the view to this argument, the output would be the same Rectangle that you passed in, with the initial x and y coordinates adjusted for its position on screen.

1
Jon Gibbins On

Figured it out and forgot to post. The key to this appears to be in overriding the view's accessibilityFrame method, since VoiceOver probably needs to poll the view for the frame information. So, you can't just set the accessibilityFrame once, as if it's a property of the view.