Download GIFs with Antenna Browser for Reddit natively
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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