Download GIFs with Antenna Browser for Reddit natively
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1485 lines
53KB

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