Skip to content
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