MBProgressHUD.m 53 KB

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