Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIAccessibility usability improvements for VoiceOver #535

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
68 changes: 63 additions & 5 deletions MBProgressHUD.m
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ - (void)commonInit {
self.alpha = 0.0f;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.layer.allowsGroupOpacity = NO;
// Set this view's accessibility to false, as long as sub-elements are accessible
// Modal is used to prevent accessing elements behind "underneath" the progress HUD.
self.isAccessibilityElement = NO;
self.accessibilityViewIsModal = YES;

[self setupViews];
[self updateIndicators];
Expand Down Expand Up @@ -147,7 +151,7 @@ - (void)showAnimated:(BOOL)animated {
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
}
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
Expand All @@ -168,7 +172,7 @@ - (void)hideAnimated:(BOOL)animated {
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.minShowTimer = timer;
return;
}
}
}
// ... otherwise hide the HUD immediately
[self hideUsingAnimation:self.useAnimation];
Expand Down Expand Up @@ -206,6 +210,16 @@ - (void)didMoveToSuperview {
[self updateForCurrentOrientationAnimated:NO];
}

#pragma mark - Accessibility

- (void)postAccessibilityScreenChangedNotificationWith:(id)element {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, element);
}

- (void)postAccessibilityLayoutChangedNotificationWith:(id)element {
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, element);
}

#pragma mark - Internal show & hide operations

- (void)showUsingAnimation:(BOOL)animated {
Expand All @@ -222,11 +236,16 @@ - (void)showUsingAnimation:(BOOL)animated {
// Needed in case we hide and re-show with the same NSProgress object attached.
[self setNSProgressDisplayLinkEnabled:YES];

// Notify UIAccessibility that the HUD (self) is shown after animation completes.
if (animated) {
[self animateIn:YES withType:self.animationType completion:NULL];
[self animateIn:YES withType:self.animationType completion:^(BOOL finished) {
[self postAccessibilityScreenChangedNotificationWith:self];
}];
} else {
self.bezelView.alpha = 1.f;
self.backgroundView.alpha = 1.f;

[self postAccessibilityScreenChangedNotificationWith:self];
}
}

Expand All @@ -237,6 +256,7 @@ - (void)hideUsingAnimation:(BOOL)animated {
// call comes in while the HUD is animating out.
[self.hideDelayTimer invalidate];

// Note that we post UIAccessibility notifications in the -done method.
if (animated && self.showStarted) {
self.showStarted = nil;
[self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
Expand Down Expand Up @@ -296,6 +316,10 @@ - (void)done {

if (self.hasFinished) {
self.alpha = 0.0f;

// Use a screen change on the superview to let UIAccessibility focus on the last clicked element.
[self postAccessibilityScreenChangedNotificationWith:self.superview];

if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
Expand Down Expand Up @@ -399,15 +423,15 @@ - (void)updateIndicators {
}
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
if (!isRoundIndicator) {
// Update to determinante indicator
// Update to determinate indicator
[indicator removeFromSuperview];
indicator = [[MBRoundProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
if (mode == MBProgressHUDModeAnnularDeterminate) {
[(MBRoundProgressView *)indicator setAnnular:YES];
}
}
}
else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
// Update custom view indicator
[indicator removeFromSuperview];
Expand All @@ -417,6 +441,9 @@ - (void)updateIndicators {
else if (mode == MBProgressHUDModeText) {
[indicator removeFromSuperview];
indicator = nil;

// For a text only HUD, make sure UIAccessibility focuses on the label.
[self postAccessibilityLayoutChangedNotificationWith:self.label];
}
indicator.translatesAutoresizingMaskIntoConstraints = NO;
self.indicator = indicator;
Expand All @@ -428,6 +455,10 @@ - (void)updateIndicators {
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];

// If indicators are updated, notify UIAccessibility.
// This may seem redundant, but is needed if multiple mode changes are used.
[self postAccessibilityLayoutChangedNotificationWith:self];

[self updateViewsForColor:self.contentColor];
[self setNeedsUpdateConstraints];
}
Expand Down Expand Up @@ -710,6 +741,9 @@ - (void)setProgress:(float)progress {
UIView *indicator = self.indicator;
if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(self.progress) forKey:@"progress"];
// Setting accessibilityValue allows for a gradual and accurate description of progress.
// This is used in conjunction with the UpdatesFrequently Accessibility Trait.
indicator.accessibilityValue = [NSString stringWithFormat:@"%2.f %%", (progress * 100)];
}
}
}
Expand Down Expand Up @@ -824,6 +858,11 @@ @implementation MBRoundProgressView
#pragma mark - Lifecycle

- (id)init {
// Ensure that this is an accessibility element and set the trait to allow percentage completion to be accessible.
self.isAccessibilityElement = YES;
self.accessibilityLabel = @"Progress";
self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;

return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
}

Expand All @@ -832,6 +871,9 @@ - (id)initWithFrame:(CGRect)frame {
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.isAccessibilityElement = YES;
self.accessibilityLabel = @"Progress";
self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;
_progress = 0.f;
_annular = NO;
_progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
Expand All @@ -851,6 +893,7 @@ - (CGSize)intrinsicContentSize {
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
self.accessibilityValue = [NSString stringWithFormat:@"%2.f %%", (progress * 100)];
[self setNeedsDisplay];
}
}
Expand All @@ -877,6 +920,8 @@ - (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;

self.accessibilityFrame = [self convertRect: rect toCoordinateSpace: [[UIScreen mainScreen] coordinateSpace] ];

if (_annular) {
// Draw background
CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f;
Expand Down Expand Up @@ -945,6 +990,11 @@ @implementation MBBarProgressView
#pragma mark - Lifecycle

- (id)init {
// Ensure that this is an accessibility element and set the trait to allow percentage completion to be accessible.
self.isAccessibilityElement = YES;
self.accessibilityLabel = @"Progress";
self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;

return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
}

Expand All @@ -957,6 +1007,9 @@ - (id)initWithFrame:(CGRect)frame {
_progressRemainingColor = [UIColor clearColor];
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.isAccessibilityElement = YES;
self.accessibilityLabel = @"Progress";
self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently;
}
return self;
}
Expand All @@ -973,6 +1026,9 @@ - (CGSize)intrinsicContentSize {
- (void)setProgress:(float)progress {
if (progress != _progress) {
_progress = progress;
// Along with the UpdatesFrequently trait, this allows percentages to be read accessibly.
self.accessibilityValue = [NSString stringWithFormat:@"%2.f %%", (progress * 100)];

[self setNeedsDisplay];
}
}
Expand Down Expand Up @@ -1002,6 +1058,8 @@ - (void)drawRect:(CGRect)rect {
CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);

self.accessibilityFrame = [self convertRect: rect toCoordinateSpace:[[UIScreen mainScreen] coordinateSpace]];

// Draw background and Border
CGFloat radius = (rect.size.height / 2) - 2;
CGContextMoveToPoint(context, 2, rect.size.height/2);
Expand Down