added iOS source code
[wl-app.git] / iOS / Pods / MBProgressHUD / MBProgressHUD.m
1 //
2 // MBProgressHUD.m
3 // Version 1.1.0
4 // Created by Matej Bukovinski on 2.4.09.
5 //
6
7 #import "MBProgressHUD.h"
8 #import <tgmath.h>
9
10
11 #ifndef kCFCoreFoundationVersionNumber_iOS_7_0
12     #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20
13 #endif
14
15 #ifndef kCFCoreFoundationVersionNumber_iOS_8_0
16     #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15
17 #endif
18
19 #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
20
21 CGFloat const MBProgressMaxOffset = 1000000.f;
22
23 static const CGFloat MBDefaultPadding = 4.f;
24 static const CGFloat MBDefaultLabelFontSize = 16.f;
25 static const CGFloat MBDefaultDetailsLabelFontSize = 12.f;
26
27
28 @interface MBProgressHUD () {
29     // Deprecated
30     UIColor *_activityIndicatorColor;
31     CGFloat _opacity;
32 }
33
34 @property (nonatomic, assign) BOOL useAnimation;
35 @property (nonatomic, assign, getter=hasFinished) BOOL finished;
36 @property (nonatomic, strong) UIView *indicator;
37 @property (nonatomic, strong) NSDate *showStarted;
38 @property (nonatomic, strong) NSArray *paddingConstraints;
39 @property (nonatomic, strong) NSArray *bezelConstraints;
40 @property (nonatomic, strong) UIView *topSpacer;
41 @property (nonatomic, strong) UIView *bottomSpacer;
42 @property (nonatomic, weak) NSTimer *graceTimer;
43 @property (nonatomic, weak) NSTimer *minShowTimer;
44 @property (nonatomic, weak) NSTimer *hideDelayTimer;
45 @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink;
46
47 // Deprecated
48 @property (assign) BOOL taskInProgress;
49
50 @end
51
52
53 @interface MBProgressHUDRoundedButton : UIButton
54 @end
55
56
57 @implementation MBProgressHUD
58
59 #pragma mark - Class methods
60
61 + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
62     MBProgressHUD *hud = [[self alloc] initWithView:view];
63     hud.removeFromSuperViewOnHide = YES;
64     [view addSubview:hud];
65     [hud showAnimated:animated];
66     return hud;
67 }
68
69 + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
70     MBProgressHUD *hud = [self HUDForView:view];
71     if (hud != nil) {
72         hud.removeFromSuperViewOnHide = YES;
73         [hud hideAnimated:animated];
74         return YES;
75     }
76     return NO;
77 }
78
79 + (MBProgressHUD *)HUDForView:(UIView *)view {
80     NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
81     for (UIView *subview in subviewsEnum) {
82         if ([subview isKindOfClass:self]) {
83             MBProgressHUD *hud = (MBProgressHUD *)subview;
84             if (hud.hasFinished == NO) {
85                 return hud;
86             }
87         }
88     }
89     return nil;
90 }
91
92 #pragma mark - Lifecycle
93
94 - (void)commonInit {
95     // Set default values for properties
96     _animationType = MBProgressHUDAnimationFade;
97     _mode = MBProgressHUDModeIndeterminate;
98     _margin = 20.0f;
99     _opacity = 1.f;
100     _defaultMotionEffectsEnabled = YES;
101
102     // Default color, depending on the current iOS version
103     BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
104     _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];
105     // Transparent background
106     self.opaque = NO;
107     self.backgroundColor = [UIColor clearColor];
108     // Make it invisible for now
109     self.alpha = 0.0f;
110     self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
111     self.layer.allowsGroupOpacity = NO;
112
113     [self setupViews];
114     [self updateIndicators];
115     [self registerForNotifications];
116 }
117
118 - (instancetype)initWithFrame:(CGRect)frame {
119     if ((self = [super initWithFrame:frame])) {
120         [self commonInit];
121     }
122     return self;
123 }
124
125 - (instancetype)initWithCoder:(NSCoder *)aDecoder {
126     if ((self = [super initWithCoder:aDecoder])) {
127         [self commonInit];
128     }
129     return self;
130 }
131
132 - (id)initWithView:(UIView *)view {
133     NSAssert(view, @"View must not be nil.");
134     return [self initWithFrame:view.bounds];
135 }
136
137 - (void)dealloc {
138     [self unregisterFromNotifications];
139 }
140
141 #pragma mark - Show & hide
142
143 - (void)showAnimated:(BOOL)animated {
144     MBMainThreadAssert();
145     [self.minShowTimer invalidate];
146     self.useAnimation = animated;
147     self.finished = NO;
148     // If the grace time is set, postpone the HUD display
149     if (self.graceTime > 0.0) {
150         NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
151         [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
152         self.graceTimer = timer;
153     } 
154     // ... otherwise show the HUD immediately
155     else {
156         [self showUsingAnimation:self.useAnimation];
157     }
158 }
159
160 - (void)hideAnimated:(BOOL)animated {
161     MBMainThreadAssert();
162     [self.graceTimer invalidate];
163     self.useAnimation = animated;
164     self.finished = YES;
165     // If the minShow time is set, calculate how long the HUD was shown,
166     // and postpone the hiding operation if necessary
167     if (self.minShowTime > 0.0 && self.showStarted) {
168         NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
169         if (interv < self.minShowTime) {
170             NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
171             [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
172             self.minShowTimer = timer;
173             return;
174         } 
175     }
176     // ... otherwise hide the HUD immediately
177     [self hideUsingAnimation:self.useAnimation];
178 }
179
180 - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
181     // Cancel any scheduled hideDelayed: calls
182     [self.hideDelayTimer invalidate];
183
184     NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
185     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
186     self.hideDelayTimer = timer;
187 }
188
189 #pragma mark - Timer callbacks
190
191 - (void)handleGraceTimer:(NSTimer *)theTimer {
192     // Show the HUD only if the task is still running
193     if (!self.hasFinished) {
194         [self showUsingAnimation:self.useAnimation];
195     }
196 }
197
198 - (void)handleMinShowTimer:(NSTimer *)theTimer {
199     [self hideUsingAnimation:self.useAnimation];
200 }
201
202 - (void)handleHideTimer:(NSTimer *)timer {
203     [self hideAnimated:[timer.userInfo boolValue]];
204 }
205
206 #pragma mark - View Hierrarchy
207
208 - (void)didMoveToSuperview {
209     [self updateForCurrentOrientationAnimated:NO];
210 }
211
212 #pragma mark - Internal show & hide operations
213
214 - (void)showUsingAnimation:(BOOL)animated {
215     // Cancel any previous animations
216     [self.bezelView.layer removeAllAnimations];
217     [self.backgroundView.layer removeAllAnimations];
218
219     // Cancel any scheduled hideDelayed: calls
220     [self.hideDelayTimer invalidate];
221
222     self.showStarted = [NSDate date];
223     self.alpha = 1.f;
224
225     // Needed in case we hide and re-show with the same NSProgress object attached.
226     [self setNSProgressDisplayLinkEnabled:YES];
227
228     if (animated) {
229         [self animateIn:YES withType:self.animationType completion:NULL];
230     } else {
231 #pragma clang diagnostic push
232 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
233         self.bezelView.alpha = self.opacity;
234 #pragma clang diagnostic pop
235         self.backgroundView.alpha = 1.f;
236     }
237 }
238
239 - (void)hideUsingAnimation:(BOOL)animated {
240     if (animated && self.showStarted) {
241         self.showStarted = nil;
242         [self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
243             [self done];
244         }];
245     } else {
246         self.showStarted = nil;
247         self.bezelView.alpha = 0.f;
248         self.backgroundView.alpha = 1.f;
249         [self done];
250     }
251 }
252
253 - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
254     // Automatically determine the correct zoom animation type
255     if (type == MBProgressHUDAnimationZoom) {
256         type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
257     }
258
259     CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
260     CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);
261
262     // Set starting state
263     UIView *bezelView = self.bezelView;
264     if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
265         bezelView.transform = small;
266     } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
267         bezelView.transform = large;
268     }
269
270     // Perform animations
271     dispatch_block_t animations = ^{
272         if (animatingIn) {
273             bezelView.transform = CGAffineTransformIdentity;
274         } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
275             bezelView.transform = large;
276         } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
277             bezelView.transform = small;
278         }
279 #pragma clang diagnostic push
280 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
281         bezelView.alpha = animatingIn ? self.opacity : 0.f;
282 #pragma clang diagnostic pop
283         self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
284     };
285
286     // Spring animations are nicer, but only available on iOS 7+
287 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
288     if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
289         [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
290         return;
291     }
292 #endif
293     [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
294 }
295
296 - (void)done {
297     // Cancel any scheduled hideDelayed: calls
298     [self.hideDelayTimer invalidate];
299     [self setNSProgressDisplayLinkEnabled:NO];
300
301     if (self.hasFinished) {
302         self.alpha = 0.0f;
303         if (self.removeFromSuperViewOnHide) {
304             [self removeFromSuperview];
305         }
306     }
307     MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
308     if (completionBlock) {
309         completionBlock();
310     }
311     id<MBProgressHUDDelegate> delegate = self.delegate;
312     if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
313         [delegate performSelector:@selector(hudWasHidden:) withObject:self];
314     }
315 }
316
317 #pragma mark - UI
318
319 - (void)setupViews {
320     UIColor *defaultColor = self.contentColor;
321
322     MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
323     backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
324     backgroundView.backgroundColor = [UIColor clearColor];
325     backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
326     backgroundView.alpha = 0.f;
327     [self addSubview:backgroundView];
328     _backgroundView = backgroundView;
329
330     MBBackgroundView *bezelView = [MBBackgroundView new];
331     bezelView.translatesAutoresizingMaskIntoConstraints = NO;
332     bezelView.layer.cornerRadius = 5.f;
333     bezelView.alpha = 0.f;
334     [self addSubview:bezelView];
335     _bezelView = bezelView;
336     [self updateBezelMotionEffects];
337
338     UILabel *label = [UILabel new];
339     label.adjustsFontSizeToFitWidth = NO;
340     label.textAlignment = NSTextAlignmentCenter;
341     label.textColor = defaultColor;
342     label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];
343     label.opaque = NO;
344     label.backgroundColor = [UIColor clearColor];
345     _label = label;
346
347     UILabel *detailsLabel = [UILabel new];
348     detailsLabel.adjustsFontSizeToFitWidth = NO;
349     detailsLabel.textAlignment = NSTextAlignmentCenter;
350     detailsLabel.textColor = defaultColor;
351     detailsLabel.numberOfLines = 0;
352     detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
353     detailsLabel.opaque = NO;
354     detailsLabel.backgroundColor = [UIColor clearColor];
355     _detailsLabel = detailsLabel;
356
357     UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
358     button.titleLabel.textAlignment = NSTextAlignmentCenter;
359     button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
360     [button setTitleColor:defaultColor forState:UIControlStateNormal];
361     _button = button;
362
363     for (UIView *view in @[label, detailsLabel, button]) {
364         view.translatesAutoresizingMaskIntoConstraints = NO;
365         [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
366         [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
367         [bezelView addSubview:view];
368     }
369
370     UIView *topSpacer = [UIView new];
371     topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
372     topSpacer.hidden = YES;
373     [bezelView addSubview:topSpacer];
374     _topSpacer = topSpacer;
375
376     UIView *bottomSpacer = [UIView new];
377     bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
378     bottomSpacer.hidden = YES;
379     [bezelView addSubview:bottomSpacer];
380     _bottomSpacer = bottomSpacer;
381 }
382
383 - (void)updateIndicators {
384     UIView *indicator = self.indicator;
385     BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
386     BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
387
388     MBProgressHUDMode mode = self.mode;
389     if (mode == MBProgressHUDModeIndeterminate) {
390         if (!isActivityIndicator) {
391             // Update to indeterminate indicator
392             [indicator removeFromSuperview];
393             indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
394             [(UIActivityIndicatorView *)indicator startAnimating];
395             [self.bezelView addSubview:indicator];
396         }
397     }
398     else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
399         // Update to bar determinate indicator
400         [indicator removeFromSuperview];
401         indicator = [[MBBarProgressView alloc] init];
402         [self.bezelView addSubview:indicator];
403     }
404     else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
405         if (!isRoundIndicator) {
406             // Update to determinante indicator
407             [indicator removeFromSuperview];
408             indicator = [[MBRoundProgressView alloc] init];
409             [self.bezelView addSubview:indicator];
410         }
411         if (mode == MBProgressHUDModeAnnularDeterminate) {
412             [(MBRoundProgressView *)indicator setAnnular:YES];
413         }
414     } 
415     else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
416         // Update custom view indicator
417         [indicator removeFromSuperview];
418         indicator = self.customView;
419         [self.bezelView addSubview:indicator];
420     }
421     else if (mode == MBProgressHUDModeText) {
422         [indicator removeFromSuperview];
423         indicator = nil;
424     }
425     indicator.translatesAutoresizingMaskIntoConstraints = NO;
426     self.indicator = indicator;
427
428     if ([indicator respondsToSelector:@selector(setProgress:)]) {
429         [(id)indicator setValue:@(self.progress) forKey:@"progress"];
430     }
431
432     [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
433     [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
434
435     [self updateViewsForColor:self.contentColor];
436     [self setNeedsUpdateConstraints];
437 }
438
439 - (void)updateViewsForColor:(UIColor *)color {
440     if (!color) return;
441
442     self.label.textColor = color;
443     self.detailsLabel.textColor = color;
444     [self.button setTitleColor:color forState:UIControlStateNormal];
445
446 #pragma clang diagnostic push
447 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
448     if (self.activityIndicatorColor) {
449         color = self.activityIndicatorColor;
450     }
451 #pragma clang diagnostic pop
452
453     // UIAppearance settings are prioritized. If they are preset the set color is ignored.
454
455     UIView *indicator = self.indicator;
456     if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
457         UIActivityIndicatorView *appearance = nil;
458 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
459         appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil];
460 #else
461         // For iOS 9+
462         appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
463 #endif
464         
465         if (appearance.color == nil) {
466             ((UIActivityIndicatorView *)indicator).color = color;
467         }
468     } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) {
469         MBRoundProgressView *appearance = nil;
470 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
471         appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
472 #else
473         appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
474 #endif
475         if (appearance.progressTintColor == nil) {
476             ((MBRoundProgressView *)indicator).progressTintColor = color;
477         }
478         if (appearance.backgroundTintColor == nil) {
479             ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1];
480         }
481     } else if ([indicator isKindOfClass:[MBBarProgressView class]]) {
482         MBBarProgressView *appearance = nil;
483 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
484         appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
485 #else
486         appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
487 #endif
488         if (appearance.progressColor == nil) {
489             ((MBBarProgressView *)indicator).progressColor = color;
490         }
491         if (appearance.lineColor == nil) {
492             ((MBBarProgressView *)indicator).lineColor = color;
493         }
494     } else {
495 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
496         if ([indicator respondsToSelector:@selector(setTintColor:)]) {
497             [indicator setTintColor:color];
498         }
499 #endif
500     }
501 }
502
503 - (void)updateBezelMotionEffects {
504 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
505     MBBackgroundView *bezelView = self.bezelView;
506     if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return;
507
508     if (self.defaultMotionEffectsEnabled) {
509         CGFloat effectOffset = 10.f;
510         UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
511         effectX.maximumRelativeValue = @(effectOffset);
512         effectX.minimumRelativeValue = @(-effectOffset);
513
514         UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
515         effectY.maximumRelativeValue = @(effectOffset);
516         effectY.minimumRelativeValue = @(-effectOffset);
517
518         UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
519         group.motionEffects = @[effectX, effectY];
520
521         [bezelView addMotionEffect:group];
522     } else {
523         NSArray *effects = [bezelView motionEffects];
524         for (UIMotionEffect *effect in effects) {
525             [bezelView removeMotionEffect:effect];
526         }
527     }
528 #endif
529 }
530
531 #pragma mark - Layout
532
533 - (void)updateConstraints {
534     UIView *bezel = self.bezelView;
535     UIView *topSpacer = self.topSpacer;
536     UIView *bottomSpacer = self.bottomSpacer;
537     CGFloat margin = self.margin;
538     NSMutableArray *bezelConstraints = [NSMutableArray array];
539     NSDictionary *metrics = @{@"margin": @(margin)};
540
541     NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil];
542     if (self.indicator) [subviews insertObject:self.indicator atIndex:1];
543
544     // Remove existing constraints
545     [self removeConstraints:self.constraints];
546     [topSpacer removeConstraints:topSpacer.constraints];
547     [bottomSpacer removeConstraints:bottomSpacer.constraints];
548     if (self.bezelConstraints) {
549         [bezel removeConstraints:self.bezelConstraints];
550         self.bezelConstraints = nil;
551     }
552
553     // Center bezel in container (self), applying the offset if set
554     CGPoint offset = self.offset;
555     NSMutableArray *centeringConstraints = [NSMutableArray array];
556     [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]];
557     [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]];
558     [self applyPriority:998.f toConstraints:centeringConstraints];
559     [self addConstraints:centeringConstraints];
560
561     // Ensure minimum side margin is kept
562     NSMutableArray *sideConstraints = [NSMutableArray array];
563     [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
564     [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
565     [self applyPriority:999.f toConstraints:sideConstraints];
566     [self addConstraints:sideConstraints];
567
568     // Minimum bezel size, if set
569     CGSize minimumSize = self.minSize;
570     if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) {
571         NSMutableArray *minSizeConstraints = [NSMutableArray array];
572         [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]];
573         [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]];
574         [self applyPriority:997.f toConstraints:minSizeConstraints];
575         [bezelConstraints addObjectsFromArray:minSizeConstraints];
576     }
577
578     // Square aspect ratio, if set
579     if (self.square) {
580         NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0];
581         square.priority = 997.f;
582         [bezelConstraints addObject:square];
583     }
584
585     // Top and bottom spacing
586     [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
587     [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
588     // Top and bottom spaces should be equal
589     [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]];
590
591     // Layout subviews in bezel
592     NSMutableArray *paddingConstraints = [NSMutableArray new];
593     [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
594         // Center in bezel
595         [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]];
596         // Ensure the minimum edge margin is kept
597         [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]];
598         // Element spacing
599         if (idx == 0) {
600             // First, ensure spacing to bezel edge
601             [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
602         } else if (idx == subviews.count - 1) {
603             // Last, ensure spacing to bezel edge
604             [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
605         }
606         if (idx > 0) {
607             // Has previous
608             NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
609             [bezelConstraints addObject:padding];
610             [paddingConstraints addObject:padding];
611         }
612     }];
613
614     [bezel addConstraints:bezelConstraints];
615     self.bezelConstraints = bezelConstraints;
616     
617     self.paddingConstraints = [paddingConstraints copy];
618     [self updatePaddingConstraints];
619     
620     [super updateConstraints];
621 }
622
623 - (void)layoutSubviews {
624     // There is no need to update constraints if they are going to
625     // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set.
626     // This also avoids an issue on iOS 8, where updatePaddingConstraints
627     // would trigger a zombie object access.
628     if (!self.needsUpdateConstraints) {
629         [self updatePaddingConstraints];
630     }
631     [super layoutSubviews];
632 }
633
634 - (void)updatePaddingConstraints {
635     // Set padding dynamically, depending on whether the view is visible or not
636     __block BOOL hasVisibleAncestors = NO;
637     [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) {
638         UIView *firstView = (UIView *)padding.firstItem;
639         UIView *secondView = (UIView *)padding.secondItem;
640         BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero);
641         BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero);
642         // Set if both views are visible or if there's a visible view on top that doesn't have padding
643         // added relative to the current view yet
644         padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f;
645         hasVisibleAncestors |= secondVisible;
646     }];
647 }
648
649 - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints {
650     for (NSLayoutConstraint *constraint in constraints) {
651         constraint.priority = priority;
652     }
653 }
654
655 #pragma mark - Properties
656
657 - (void)setMode:(MBProgressHUDMode)mode {
658     if (mode != _mode) {
659         _mode = mode;
660         [self updateIndicators];
661     }
662 }
663
664 - (void)setCustomView:(UIView *)customView {
665     if (customView != _customView) {
666         _customView = customView;
667         if (self.mode == MBProgressHUDModeCustomView) {
668             [self updateIndicators];
669         }
670     }
671 }
672
673 - (void)setOffset:(CGPoint)offset {
674     if (!CGPointEqualToPoint(offset, _offset)) {
675         _offset = offset;
676         [self setNeedsUpdateConstraints];
677     }
678 }
679
680 - (void)setMargin:(CGFloat)margin {
681     if (margin != _margin) {
682         _margin = margin;
683         [self setNeedsUpdateConstraints];
684     }
685 }
686
687 - (void)setMinSize:(CGSize)minSize {
688     if (!CGSizeEqualToSize(minSize, _minSize)) {
689         _minSize = minSize;
690         [self setNeedsUpdateConstraints];
691     }
692 }
693
694 - (void)setSquare:(BOOL)square {
695     if (square != _square) {
696         _square = square;
697         [self setNeedsUpdateConstraints];
698     }
699 }
700
701 - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink {
702     if (progressObjectDisplayLink != _progressObjectDisplayLink) {
703         [_progressObjectDisplayLink invalidate];
704         
705         _progressObjectDisplayLink = progressObjectDisplayLink;
706         
707         [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
708     }
709 }
710
711 - (void)setProgressObject:(NSProgress *)progressObject {
712     if (progressObject != _progressObject) {
713         _progressObject = progressObject;
714         [self setNSProgressDisplayLinkEnabled:YES];
715     }
716 }
717
718 - (void)setProgress:(float)progress {
719     if (progress != _progress) {
720         _progress = progress;
721         UIView *indicator = self.indicator;
722         if ([indicator respondsToSelector:@selector(setProgress:)]) {
723             [(id)indicator setValue:@(self.progress) forKey:@"progress"];
724         }
725     }
726 }
727
728 - (void)setContentColor:(UIColor *)contentColor {
729     if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) {
730         _contentColor = contentColor;
731         [self updateViewsForColor:contentColor];
732     }
733 }
734
735 - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled {
736     if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) {
737         _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled;
738         [self updateBezelMotionEffects];
739     }
740 }
741
742 #pragma mark - NSProgress
743
744 - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
745     // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread,
746     // so we're refreshing the progress only every frame draw
747     if (enabled && self.progressObject) {
748         // Only create if not already active.
749         if (!self.progressObjectDisplayLink) {
750             self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
751         }
752     } else {
753         self.progressObjectDisplayLink = nil;
754     }
755 }
756
757 - (void)updateProgressFromProgressObject {
758     self.progress = self.progressObject.fractionCompleted;
759 }
760
761 #pragma mark - Notifications
762
763 - (void)registerForNotifications {
764 #if !TARGET_OS_TV
765     NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
766
767     [nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
768                name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
769 #endif
770 }
771
772 - (void)unregisterFromNotifications {
773 #if !TARGET_OS_TV
774     NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
775     [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
776 #endif
777 }
778
779 #if !TARGET_OS_TV
780 - (void)statusBarOrientationDidChange:(NSNotification *)notification {
781     UIView *superview = self.superview;
782     if (!superview) {
783         return;
784     } else {
785         [self updateForCurrentOrientationAnimated:YES];
786     }
787 }
788 #endif
789
790 - (void)updateForCurrentOrientationAnimated:(BOOL)animated {
791     // Stay in sync with the superview in any case
792     if (self.superview) {
793         self.frame = self.superview.bounds;
794     }
795
796     // Not needed on iOS 8+, compile out when the deployment target allows,
797     // to avoid sharedApplication problems on extension targets
798 #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
799     // Only needed pre iOS 8 when added to a window
800     BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0;
801     if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return;
802
803     // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check.
804     // This just ensures we don't get a warning about extension-unsafe API.
805     Class UIApplicationClass = NSClassFromString(@"UIApplication");
806     if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return;
807
808     UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
809     UIInterfaceOrientation orientation = application.statusBarOrientation;
810     CGFloat radians = 0;
811     
812     if (UIInterfaceOrientationIsLandscape(orientation)) {
813         radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2;
814         // Window coordinates differ!
815         self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
816     } else {
817         radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f;
818     }
819
820     if (animated) {
821         [UIView animateWithDuration:0.3 animations:^{
822             self.transform = CGAffineTransformMakeRotation(radians);
823         }];
824     } else {
825         self.transform = CGAffineTransformMakeRotation(radians);
826     }
827 #endif
828 }
829
830 @end
831
832
833 @implementation MBRoundProgressView
834
835 #pragma mark - Lifecycle
836
837 - (id)init {
838     return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
839 }
840
841 - (id)initWithFrame:(CGRect)frame {
842     self = [super initWithFrame:frame];
843     if (self) {
844         self.backgroundColor = [UIColor clearColor];
845         self.opaque = NO;
846         _progress = 0.f;
847         _annular = NO;
848         _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
849         _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
850     }
851     return self;
852 }
853
854 #pragma mark - Layout
855
856 - (CGSize)intrinsicContentSize {
857     return CGSizeMake(37.f, 37.f);
858 }
859
860 #pragma mark - Properties
861
862 - (void)setProgress:(float)progress {
863     if (progress != _progress) {
864         _progress = progress;
865         [self setNeedsDisplay];
866     }
867 }
868
869 - (void)setProgressTintColor:(UIColor *)progressTintColor {
870     NSAssert(progressTintColor, @"The color should not be nil.");
871     if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) {
872         _progressTintColor = progressTintColor;
873         [self setNeedsDisplay];
874     }
875 }
876
877 - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor {
878     NSAssert(backgroundTintColor, @"The color should not be nil.");
879     if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) {
880         _backgroundTintColor = backgroundTintColor;
881         [self setNeedsDisplay];
882     }
883 }
884
885 #pragma mark - Drawing
886
887 - (void)drawRect:(CGRect)rect {
888     CGContextRef context = UIGraphicsGetCurrentContext();
889     BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
890
891     if (_annular) {
892         // Draw background
893         CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f;
894         UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
895         processBackgroundPath.lineWidth = lineWidth;
896         processBackgroundPath.lineCapStyle = kCGLineCapButt;
897         CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
898         CGFloat radius = (self.bounds.size.width - lineWidth)/2;
899         CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
900         CGFloat endAngle = (2 * (float)M_PI) + startAngle;
901         [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
902         [_backgroundTintColor set];
903         [processBackgroundPath stroke];
904         // Draw progress
905         UIBezierPath *processPath = [UIBezierPath bezierPath];
906         processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare;
907         processPath.lineWidth = lineWidth;
908         endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
909         [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
910         [_progressTintColor set];
911         [processPath stroke];
912     } else {
913         // Draw background
914         CGFloat lineWidth = 2.f;
915         CGRect allRect = self.bounds;
916         CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f);
917         CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
918         [_progressTintColor setStroke];
919         [_backgroundTintColor setFill];
920         CGContextSetLineWidth(context, lineWidth);
921         if (isPreiOS7) {
922             CGContextFillEllipseInRect(context, circleRect);
923         }
924         CGContextStrokeEllipseInRect(context, circleRect);
925         // 90 degrees
926         CGFloat startAngle = - ((float)M_PI / 2.f);
927         // Draw progress
928         if (isPreiOS7) {
929             CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth;
930             CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
931             [_progressTintColor setFill];
932             CGContextMoveToPoint(context, center.x, center.y);
933             CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
934             CGContextClosePath(context);
935             CGContextFillPath(context);
936         } else {
937             UIBezierPath *processPath = [UIBezierPath bezierPath];
938             processPath.lineCapStyle = kCGLineCapButt;
939             processPath.lineWidth = lineWidth * 2.f;
940             CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f);
941             CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
942             [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
943             // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f.
944             CGContextSetBlendMode(context, kCGBlendModeCopy);
945             [_progressTintColor set];
946             [processPath stroke];
947         }
948     }
949 }
950
951 @end
952
953
954 @implementation MBBarProgressView
955
956 #pragma mark - Lifecycle
957
958 - (id)init {
959     return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
960 }
961
962 - (id)initWithFrame:(CGRect)frame {
963     self = [super initWithFrame:frame];
964     if (self) {
965         _progress = 0.f;
966         _lineColor = [UIColor whiteColor];
967         _progressColor = [UIColor whiteColor];
968         _progressRemainingColor = [UIColor clearColor];
969         self.backgroundColor = [UIColor clearColor];
970         self.opaque = NO;
971     }
972     return self;
973 }
974
975 #pragma mark - Layout
976
977 - (CGSize)intrinsicContentSize {
978     BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
979     return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f);
980 }
981
982 #pragma mark - Properties
983
984 - (void)setProgress:(float)progress {
985     if (progress != _progress) {
986         _progress = progress;
987         [self setNeedsDisplay];
988     }
989 }
990
991 - (void)setProgressColor:(UIColor *)progressColor {
992     NSAssert(progressColor, @"The color should not be nil.");
993     if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) {
994         _progressColor = progressColor;
995         [self setNeedsDisplay];
996     }
997 }
998
999 - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor {
1000     NSAssert(progressRemainingColor, @"The color should not be nil.");
1001     if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) {
1002         _progressRemainingColor = progressRemainingColor;
1003         [self setNeedsDisplay];
1004     }
1005 }
1006
1007 #pragma mark - Drawing
1008
1009 - (void)drawRect:(CGRect)rect {
1010     CGContextRef context = UIGraphicsGetCurrentContext();
1011     
1012     CGContextSetLineWidth(context, 2);
1013     CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
1014     CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
1015     
1016     // Draw background and Border
1017     CGFloat radius = (rect.size.height / 2) - 2;
1018     CGContextMoveToPoint(context, 2, rect.size.height/2);
1019     CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
1020     CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
1021     CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
1022     CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
1023     CGContextDrawPath(context, kCGPathFillStroke);
1024     
1025     CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
1026     radius = radius - 2;
1027     CGFloat amount = self.progress * rect.size.width;
1028     
1029     // Progress in the middle area
1030     if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
1031         CGContextMoveToPoint(context, 4, rect.size.height/2);
1032         CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
1033         CGContextAddLineToPoint(context, amount, 4);
1034         CGContextAddLineToPoint(context, amount, radius + 4);
1035         
1036         CGContextMoveToPoint(context, 4, rect.size.height/2);
1037         CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
1038         CGContextAddLineToPoint(context, amount, rect.size.height - 4);
1039         CGContextAddLineToPoint(context, amount, radius + 4);
1040         
1041         CGContextFillPath(context);
1042     }
1043     
1044     // Progress in the right arc
1045     else if (amount > radius + 4) {
1046         CGFloat x = amount - (rect.size.width - radius - 4);
1047
1048         CGContextMoveToPoint(context, 4, rect.size.height/2);
1049         CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
1050         CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
1051         CGFloat angle = -acos(x/radius);
1052         if (isnan(angle)) angle = 0;
1053         CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
1054         CGContextAddLineToPoint(context, amount, rect.size.height/2);
1055
1056         CGContextMoveToPoint(context, 4, rect.size.height/2);
1057         CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
1058         CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
1059         angle = acos(x/radius);
1060         if (isnan(angle)) angle = 0;
1061         CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
1062         CGContextAddLineToPoint(context, amount, rect.size.height/2);
1063         
1064         CGContextFillPath(context);
1065     }
1066     
1067     // Progress is in the left arc
1068     else if (amount < radius + 4 && amount > 0) {
1069         CGContextMoveToPoint(context, 4, rect.size.height/2);
1070         CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
1071         CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
1072
1073         CGContextMoveToPoint(context, 4, rect.size.height/2);
1074         CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
1075         CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
1076         
1077         CGContextFillPath(context);
1078     }
1079 }
1080
1081 @end
1082
1083
1084 @interface MBBackgroundView ()
1085
1086 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1087 @property UIVisualEffectView *effectView;
1088 #endif
1089 #if !TARGET_OS_TV
1090 @property UIToolbar *toolbar;
1091 #endif
1092
1093 @end
1094
1095
1096 @implementation MBBackgroundView
1097
1098 #pragma mark - Lifecycle
1099
1100 - (instancetype)initWithFrame:(CGRect)frame {
1101     if ((self = [super initWithFrame:frame])) {
1102         if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
1103             _style = MBProgressHUDBackgroundStyleBlur;
1104 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1105             _blurEffectStyle = UIBlurEffectStyleLight;
1106 #endif
1107             if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
1108                 _color = [UIColor colorWithWhite:0.8f alpha:0.6f];
1109             } else {
1110                 _color = [UIColor colorWithWhite:0.95f alpha:0.6f];
1111             }
1112         } else {
1113             _style = MBProgressHUDBackgroundStyleSolidColor;
1114             _color = [[UIColor blackColor] colorWithAlphaComponent:0.8];
1115         }
1116
1117         self.clipsToBounds = YES;
1118
1119         [self updateForBackgroundStyle];
1120     }
1121     return self;
1122 }
1123
1124 #pragma mark - Layout
1125
1126 - (CGSize)intrinsicContentSize {
1127     // Smallest size possible. Content pushes against this.
1128     return CGSizeZero;
1129 }
1130
1131 #pragma mark - Appearance
1132
1133 - (void)setStyle:(MBProgressHUDBackgroundStyle)style {
1134     if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) {
1135         style = MBProgressHUDBackgroundStyleSolidColor;
1136     }
1137     if (_style != style) {
1138         _style = style;
1139         [self updateForBackgroundStyle];
1140     }
1141 }
1142
1143 - (void)setColor:(UIColor *)color {
1144     NSAssert(color, @"The color should not be nil.");
1145     if (color != _color && ![color isEqual:_color]) {
1146         _color = color;
1147         [self updateViewsForColor:color];
1148     }
1149 }
1150
1151 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1152
1153 - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle {
1154     if (_blurEffectStyle == blurEffectStyle) {
1155         return;
1156     }
1157
1158     _blurEffectStyle = blurEffectStyle;
1159
1160     [self updateForBackgroundStyle];
1161 }
1162
1163 #endif
1164
1165 ///////////////////////////////////////////////////////////////////////////////////////////
1166 #pragma mark - Views
1167
1168 - (void)updateForBackgroundStyle {
1169     MBProgressHUDBackgroundStyle style = self.style;
1170     if (style == MBProgressHUDBackgroundStyleBlur) {
1171 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1172         if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
1173             UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle];
1174             UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
1175             [self addSubview:effectView];
1176             effectView.frame = self.bounds;
1177             effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
1178             self.backgroundColor = self.color;
1179             self.layer.allowsGroupOpacity = NO;
1180             self.effectView = effectView;
1181         } else {
1182 #endif
1183 #if !TARGET_OS_TV
1184             UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)];
1185             toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
1186             toolbar.barTintColor = self.color;
1187             toolbar.translucent = YES;
1188             [self addSubview:toolbar];
1189             self.toolbar = toolbar;
1190 #endif
1191 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1192         }
1193 #endif
1194     } else {
1195 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1196         if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
1197             [self.effectView removeFromSuperview];
1198             self.effectView = nil;
1199         } else {
1200 #endif
1201 #if !TARGET_OS_TV
1202             [self.toolbar removeFromSuperview];
1203             self.toolbar = nil;
1204 #endif
1205 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
1206         }
1207 #endif
1208         self.backgroundColor = self.color;
1209     }
1210 }
1211
1212 - (void)updateViewsForColor:(UIColor *)color {
1213     if (self.style == MBProgressHUDBackgroundStyleBlur) {
1214         if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
1215             self.backgroundColor = self.color;
1216         } else {
1217 #if !TARGET_OS_TV
1218             self.toolbar.barTintColor = color;
1219 #endif
1220         }
1221     } else {
1222         self.backgroundColor = self.color;
1223     }
1224 }
1225
1226 @end
1227
1228
1229 @implementation MBProgressHUD (Deprecated)
1230
1231 #pragma mark - Class
1232
1233 + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
1234     NSArray *huds = [MBProgressHUD allHUDsForView:view];
1235     for (MBProgressHUD *hud in huds) {
1236         hud.removeFromSuperViewOnHide = YES;
1237         [hud hideAnimated:animated];
1238     }
1239     return [huds count];
1240 }
1241
1242 + (NSArray *)allHUDsForView:(UIView *)view {
1243     NSMutableArray *huds = [NSMutableArray array];
1244     NSArray *subviews = view.subviews;
1245     for (UIView *aView in subviews) {
1246         if ([aView isKindOfClass:self]) {
1247             [huds addObject:aView];
1248         }
1249     }
1250     return [NSArray arrayWithArray:huds];
1251 }
1252
1253 #pragma mark - Lifecycle
1254
1255 - (id)initWithWindow:(UIWindow *)window {
1256     return [self initWithView:window];
1257 }
1258
1259 #pragma mark - Show & hide
1260
1261 - (void)show:(BOOL)animated {
1262     [self showAnimated:animated];
1263 }
1264
1265 - (void)hide:(BOOL)animated {
1266     [self hideAnimated:animated];
1267 }
1268
1269 - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay {
1270     [self hideAnimated:animated afterDelay:delay];
1271 }
1272
1273 #pragma mark - Threading
1274
1275 - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
1276     [self showAnimated:animated whileExecutingBlock:^{
1277 #pragma clang diagnostic push
1278 #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
1279         // Start executing the requested task
1280         [target performSelector:method withObject:object];
1281 #pragma clang diagnostic pop
1282     }];
1283 }
1284
1285 - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block {
1286     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1287     [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
1288 }
1289
1290 - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)(void))completion {
1291     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1292     [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion];
1293 }
1294
1295 - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue {
1296     [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
1297 }
1298
1299 - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion {
1300     self.taskInProgress = YES;
1301     self.completionBlock = completion;
1302     dispatch_async(queue, ^(void) {
1303         block();
1304         dispatch_async(dispatch_get_main_queue(), ^(void) {
1305             [self cleanUp];
1306         });
1307     });
1308     [self showAnimated:animated];
1309 }
1310
1311 - (void)cleanUp {
1312     self.taskInProgress = NO;
1313     [self hideAnimated:self.useAnimation];
1314 }
1315
1316 #pragma mark - Labels
1317
1318 - (NSString *)labelText {
1319     return self.label.text;
1320 }
1321
1322 - (void)setLabelText:(NSString *)labelText {
1323     MBMainThreadAssert();
1324     self.label.text = labelText;
1325 }
1326
1327 - (UIFont *)labelFont {
1328     return self.label.font;
1329 }
1330
1331 - (void)setLabelFont:(UIFont *)labelFont {
1332     MBMainThreadAssert();
1333     self.label.font = labelFont;
1334 }
1335
1336 - (UIColor *)labelColor {
1337     return self.label.textColor;
1338 }
1339
1340 - (void)setLabelColor:(UIColor *)labelColor {
1341     MBMainThreadAssert();
1342     self.label.textColor = labelColor;
1343 }
1344
1345 - (NSString *)detailsLabelText {
1346     return self.detailsLabel.text;
1347 }
1348
1349 - (void)setDetailsLabelText:(NSString *)detailsLabelText {
1350     MBMainThreadAssert();
1351     self.detailsLabel.text = detailsLabelText;
1352 }
1353
1354 - (UIFont *)detailsLabelFont {
1355     return self.detailsLabel.font;
1356 }
1357
1358 - (void)setDetailsLabelFont:(UIFont *)detailsLabelFont {
1359     MBMainThreadAssert();
1360     self.detailsLabel.font = detailsLabelFont;
1361 }
1362
1363 - (UIColor *)detailsLabelColor {
1364     return self.detailsLabel.textColor;
1365 }
1366
1367 - (void)setDetailsLabelColor:(UIColor *)detailsLabelColor {
1368     MBMainThreadAssert();
1369     self.detailsLabel.textColor = detailsLabelColor;
1370 }
1371
1372 - (CGFloat)opacity {
1373     return _opacity;
1374 }
1375
1376 - (void)setOpacity:(CGFloat)opacity {
1377     MBMainThreadAssert();
1378     _opacity = opacity;
1379 }
1380
1381 - (UIColor *)color {
1382     return self.bezelView.color;
1383 }
1384
1385 - (void)setColor:(UIColor *)color {
1386     MBMainThreadAssert();
1387     self.bezelView.color = color;
1388 }
1389
1390 - (CGFloat)yOffset {
1391     return self.offset.y;
1392 }
1393
1394 - (void)setYOffset:(CGFloat)yOffset {
1395     MBMainThreadAssert();
1396     self.offset = CGPointMake(self.offset.x, yOffset);
1397 }
1398
1399 - (CGFloat)xOffset {
1400     return self.offset.x;
1401 }
1402
1403 - (void)setXOffset:(CGFloat)xOffset {
1404     MBMainThreadAssert();
1405     self.offset = CGPointMake(xOffset, self.offset.y);
1406 }
1407
1408 - (CGFloat)cornerRadius {
1409     return self.bezelView.layer.cornerRadius;
1410 }
1411
1412 - (void)setCornerRadius:(CGFloat)cornerRadius {
1413     MBMainThreadAssert();
1414     self.bezelView.layer.cornerRadius = cornerRadius;
1415 }
1416
1417 - (BOOL)dimBackground {
1418     MBBackgroundView *backgroundView = self.backgroundView;
1419     UIColor *dimmedColor =  [UIColor colorWithWhite:0.f alpha:.2f];
1420     return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor];
1421 }
1422
1423 - (void)setDimBackground:(BOOL)dimBackground {
1424     MBMainThreadAssert();
1425     self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
1426     self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor];
1427 }
1428
1429 - (CGSize)size {
1430     return self.bezelView.frame.size;
1431 }
1432
1433 - (UIColor *)activityIndicatorColor {
1434     return _activityIndicatorColor;
1435 }
1436
1437 - (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor {
1438     if (activityIndicatorColor != _activityIndicatorColor) {
1439         _activityIndicatorColor = activityIndicatorColor;
1440         UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator;
1441         if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
1442             [indicator setColor:activityIndicatorColor];
1443         }
1444     }
1445 }
1446
1447 @end
1448
1449 @implementation MBProgressHUDRoundedButton
1450
1451 #pragma mark - Lifecycle
1452
1453 - (instancetype)initWithFrame:(CGRect)frame {
1454     self = [super initWithFrame:frame];
1455     if (self) {
1456         CALayer *layer = self.layer;
1457         layer.borderWidth = 1.f;
1458     }
1459     return self;
1460 }
1461
1462 #pragma mark - Layout
1463
1464 - (void)layoutSubviews {
1465     [super layoutSubviews];
1466     // Fully rounded corners
1467     CGFloat height = CGRectGetHeight(self.bounds);
1468     self.layer.cornerRadius = ceil(height / 2.f);
1469 }
1470
1471 - (CGSize)intrinsicContentSize {
1472     // Only show if we have associated control events
1473     if (self.allControlEvents == 0) return CGSizeZero;
1474     CGSize size = [super intrinsicContentSize];
1475     // Add some side padding
1476     size.width += 20.f;
1477     return size;
1478 }
1479
1480 #pragma mark - Color
1481
1482 - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
1483     [super setTitleColor:color forState:state];
1484     // Update related colors
1485     [self setHighlighted:self.highlighted];
1486     self.layer.borderColor = color.CGColor;
1487 }
1488
1489 - (void)setHighlighted:(BOOL)highlighted {
1490     [super setHighlighted:highlighted];
1491     UIColor *baseColor = [self titleColorForState:UIControlStateSelected];
1492     self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor];
1493 }
1494
1495 @end