MBProgressHUD.m 53 KB

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