MBProgressHUD.m 53 KB

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