From a206f17ab3de0abba16fd134ccc06cb18f9cf105 Mon Sep 17 00:00:00 2001 From: Louis Romero Date: Thu, 23 Apr 2015 19:09:12 +0200 Subject: [PATCH] Fallback to regular usage of UIAccessibilityElement when the accessibility container is changed externally. NIViewAccessibilityElement overrides UIAccessibilityElement behavior by delaying computing its accessibility frame (which is in screen corrdinates). In that case, the accessibility frame is computed upon request based on the position at that moment of the actual visual component made accessible. This holds true until the accessibility container is changed externally. For example, when the accessibility element is in a table view hierarchy). In that case, the initial accessibility container is changed to a UITableViewCellAccessibilityElement and frameInContainer becomes invalid. This commit invalidates NIViewAccessibilityElement's special treatment and fallback to the regular behavior. --- src/attributedlabel/src/NIAttributedLabel.m | 60 +++++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/attributedlabel/src/NIAttributedLabel.m b/src/attributedlabel/src/NIAttributedLabel.m index 91a180844..964e05adb 100644 --- a/src/attributedlabel/src/NIAttributedLabel.m +++ b/src/attributedlabel/src/NIAttributedLabel.m @@ -106,25 +106,59 @@ CGSize NISizeOfAttributedStringConstrainedToSize(NSAttributedString* attributedS * - The accessibilityContainer must be a UIView. * - The accessibilityFrame is recomputed every time from the frameInContainer * and the accessibilityContainer. + * + * These differences cease to be as soon as the initial accessibility container + * is changed externally, which is internally tracked by isFrameInContainerValid. */ @interface NIViewAccessibilityElement : UIAccessibilityElement +// Designated initializer. +- (instancetype)initWithAccessibilityContainer:(id)container frameInContainer:(CGRect)frameInContainer; + // This frame is in the accessibilityContainer coordinates. -@property (nonatomic) CGRect frameInContainer; +@property (nonatomic, readonly) CGRect frameInContainer; + +@end + +@interface NIViewAccessibilityElement () + +// Whether frameInContainer is valid, that is, the container is still the one +// used to compute frameInContainer. +@property (nonatomic) BOOL isFrameInContainerValid; @end @implementation NIViewAccessibilityElement -- (instancetype)initWithAccessibilityContainer:(id)container { +- (instancetype)initWithAccessibilityContainer:(id)container frameInContainer:(CGRect)frameInContainer { NIDASSERT([container isKindOfClass:[UIView class]]); - return [super initWithAccessibilityContainer:container]; + if ((self = [super initWithAccessibilityContainer:container])) { + _frameInContainer = frameInContainer; + _isFrameInContainerValid = YES; + } + return self; +} + +- (instancetype)initWithAccessibilityContainer:(id)container { + if ((self = [self initWithAccessibilityContainer:container frameInContainer:CGRectZero])) { + self.isFrameInContainerValid = NO; + } + return self; +} + +- (void)setAccessibilityContainer:(id)accessibilityContainer { + self.isFrameInContainerValid = NO; + [super setAccessibilityContainer:accessibilityContainer]; } - (CGRect)accessibilityFrame { - UIView* view = [self accessibilityContainer]; - CGRect frame = [view convertRect:self.frameInContainer toView:nil]; - return [view.window convertRect:frame toWindow:nil]; + if (self.isFrameInContainerValid) { + UIView* view = [self accessibilityContainer]; + NIDASSERT([view isKindOfClass:[UIView class]]); + CGRect frame = [view convertRect:self.frameInContainer toView:nil]; + return [view.window convertRect:frame toWindow:nil]; + } + return [super accessibilityFrame]; } @end @@ -1435,9 +1469,12 @@ - (NSArray *)accessibleElements { NSString* label = [self.mutableAttributedString.string substringWithRange:result.range]; for (NSValue* rectValue in rectsForLink) { - NIViewAccessibilityElement* element = [[NIViewAccessibilityElement alloc] initWithAccessibilityContainer:self]; + NIViewAccessibilityElement* element = [[NIViewAccessibilityElement alloc] initWithAccessibilityContainer:self frameInContainer:rectValue.CGRectValue]; element.accessibilityLabel = label; - element.frameInContainer = rectValue.CGRectValue; + // Set the frame to fallback on if |element|'s accessibility container is changed externally. + CGRect rectValueInWindowCoordinates = [self convertRect:rectValue.CGRectValue toView:nil]; + CGRect rectValueInScreenCoordinates = [self.window convertRect:rectValueInWindowCoordinates toWindow:nil]; + element.accessibilityFrame = rectValueInScreenCoordinates; element.accessibilityTraits = UIAccessibilityTraitLink; [accessibleElements addObject:element]; } @@ -1445,9 +1482,12 @@ - (NSArray *)accessibleElements { // Add this label's text as the "bottom-most" accessibility element, i.e. the last element in the // array. This gives link priorities. - NIViewAccessibilityElement* element = [[NIViewAccessibilityElement alloc] initWithAccessibilityContainer:self]; + NIViewAccessibilityElement* element = [[NIViewAccessibilityElement alloc] initWithAccessibilityContainer:self frameInContainer:self.bounds]; element.accessibilityLabel = self.attributedText.string; - element.frameInContainer = self.bounds; + // Set the frame to fallback on if |element|'s accessibility container is changed externally. + CGRect boundsInWindowCoordinates = [self convertRect:self.bounds toView:nil]; + CGRect boundsInScreenCoordinates = [self.window convertRect:boundsInWindowCoordinates toWindow:nil]; + element.accessibilityFrame = boundsInScreenCoordinates; element.accessibilityTraits = UIAccessibilityTraitNone; [accessibleElements addObject:element];