MBProgressHUD.m 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  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; dispatch_async(dispatch_get_main_queue(), ^{
  614. UIView *indicator = self.indicator;
  615. if ([indicator respondsToSelector:@selector(setProgress:)]) {
  616. [(id)indicator setValue:@(self.progress) forKey:@"progress"];
  617. }
  618. });
  619. }
  620. }
  621. - (void)setContentColor:(UIColor *)contentColor {
  622. if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) {
  623. _contentColor = contentColor;
  624. [self updateViewsForColor:contentColor];
  625. }
  626. }
  627. - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled {
  628. if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) {
  629. _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled;
  630. [self updateBezelMotionEffects];
  631. }
  632. }
  633. #pragma mark - NSProgress
  634. - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
  635. // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread,
  636. // so we're refreshing the progress only every frame draw
  637. if (enabled && self.progressObject) {
  638. // Only create if not already active.
  639. if (!self.progressObjectDisplayLink) {
  640. self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
  641. }
  642. } else {
  643. self.progressObjectDisplayLink = nil;
  644. }
  645. }
  646. - (void)updateProgressFromProgressObject {
  647. self.progress = self.progressObject.fractionCompleted;
  648. }
  649. #pragma mark - Notifications
  650. - (void)registerForNotifications {
  651. #if !TARGET_OS_TV
  652. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  653. [nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
  654. name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
  655. #endif
  656. }
  657. - (void)unregisterFromNotifications {
  658. #if !TARGET_OS_TV
  659. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  660. [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
  661. #endif
  662. }
  663. #if !TARGET_OS_TV
  664. - (void)statusBarOrientationDidChange:(NSNotification *)notification {
  665. UIView *superview = self.superview;
  666. if (!superview) {
  667. return;
  668. } else {
  669. [self updateForCurrentOrientationAnimated:YES];
  670. }
  671. }
  672. #endif
  673. - (void)updateForCurrentOrientationAnimated:(BOOL)animated {
  674. // Stay in sync with the superview in any case
  675. if (self.superview) {
  676. self.frame = self.superview.bounds;
  677. }
  678. // Not needed on iOS 8+, compile out when the deployment target allows,
  679. // to avoid sharedApplication problems on extension targets
  680. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  681. // Only needed pre iOS 8 when added to a window
  682. BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0;
  683. if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return;
  684. // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check.
  685. // This just ensures we don't get a warning about extension-unsafe API.
  686. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  687. if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return;
  688. UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
  689. UIInterfaceOrientation orientation = application.statusBarOrientation;
  690. CGFloat radians = 0;
  691. if (UIInterfaceOrientationIsLandscape(orientation)) {
  692. radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2;
  693. // Window coordinates differ!
  694. self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
  695. } else {
  696. radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f;
  697. }
  698. if (animated) {
  699. [UIView animateWithDuration:0.3 animations:^{
  700. self.transform = CGAffineTransformMakeRotation(radians);
  701. }];
  702. } else {
  703. self.transform = CGAffineTransformMakeRotation(radians);
  704. }
  705. #endif
  706. }
  707. @end
  708. @implementation MBRoundProgressView
  709. #pragma mark - Lifecycle
  710. - (id)init {
  711. return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
  712. }
  713. - (id)initWithFrame:(CGRect)frame {
  714. self = [super initWithFrame:frame];
  715. if (self) {
  716. self.backgroundColor = [UIColor clearColor];
  717. self.opaque = NO;
  718. _progress = 0.f;
  719. _annular = NO;
  720. _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
  721. _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
  722. }
  723. return self;
  724. }
  725. #pragma mark - Layout
  726. - (CGSize)intrinsicContentSize {
  727. return CGSizeMake(37.f, 37.f);
  728. }
  729. #pragma mark - Properties
  730. - (void)setProgress:(float)progress {
  731. if (progress != _progress) {
  732. _progress = progress;
  733. dispatch_async(dispatch_get_main_queue(), ^{
  734. [self setNeedsDisplay];
  735. });
  736. }
  737. }
  738. - (void)setProgressTintColor:(UIColor *)progressTintColor {
  739. NSAssert(progressTintColor, @"The color should not be nil.");
  740. if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) {
  741. _progressTintColor = progressTintColor;
  742. [self setNeedsDisplay];
  743. }
  744. }
  745. - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor {
  746. NSAssert(backgroundTintColor, @"The color should not be nil.");
  747. if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) {
  748. _backgroundTintColor = backgroundTintColor;
  749. [self setNeedsDisplay];
  750. }
  751. }
  752. #pragma mark - Drawing
  753. - (void)drawRect:(CGRect)rect {
  754. CGContextRef context = UIGraphicsGetCurrentContext();
  755. BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
  756. if (_annular) {
  757. // Draw background
  758. CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f;
  759. UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
  760. processBackgroundPath.lineWidth = lineWidth;
  761. processBackgroundPath.lineCapStyle = kCGLineCapButt;
  762. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  763. CGFloat radius = (self.bounds.size.width - lineWidth)/2;
  764. CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
  765. CGFloat endAngle = (2 * (float)M_PI) + startAngle;
  766. [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  767. [_backgroundTintColor set];
  768. [processBackgroundPath stroke];
  769. // Draw progress
  770. UIBezierPath *processPath = [UIBezierPath bezierPath];
  771. processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare;
  772. processPath.lineWidth = lineWidth;
  773. endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
  774. [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  775. [_progressTintColor set];
  776. [processPath stroke];
  777. } else {
  778. // Draw background
  779. CGFloat lineWidth = 2.f;
  780. CGRect allRect = self.bounds;
  781. CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f);
  782. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  783. [_progressTintColor setStroke];
  784. [_backgroundTintColor setFill];
  785. CGContextSetLineWidth(context, lineWidth);
  786. if (isPreiOS7) {
  787. CGContextFillEllipseInRect(context, circleRect);
  788. }
  789. CGContextStrokeEllipseInRect(context, circleRect);
  790. // 90 degrees
  791. CGFloat startAngle = - ((float)M_PI / 2.f);
  792. // Draw progress
  793. if (isPreiOS7) {
  794. CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth;
  795. CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
  796. [_progressTintColor setFill];
  797. CGContextMoveToPoint(context, center.x, center.y);
  798. CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
  799. CGContextClosePath(context);
  800. CGContextFillPath(context);
  801. } else {
  802. UIBezierPath *processPath = [UIBezierPath bezierPath];
  803. processPath.lineCapStyle = kCGLineCapButt;
  804. processPath.lineWidth = lineWidth * 2.f;
  805. CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f);
  806. CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
  807. [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  808. // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f.
  809. CGContextSetBlendMode(context, kCGBlendModeCopy);
  810. [_progressTintColor set];
  811. [processPath stroke];
  812. }
  813. }
  814. }
  815. @end
  816. @implementation MBBarProgressView
  817. #pragma mark - Lifecycle
  818. - (id)init {
  819. return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
  820. }
  821. - (id)initWithFrame:(CGRect)frame {
  822. self = [super initWithFrame:frame];
  823. if (self) {
  824. _progress = 0.f;
  825. _lineColor = [UIColor whiteColor];
  826. _progressColor = [UIColor whiteColor];
  827. _progressRemainingColor = [UIColor clearColor];
  828. self.backgroundColor = [UIColor clearColor];
  829. self.opaque = NO;
  830. }
  831. return self;
  832. }
  833. #pragma mark - Layout
  834. - (CGSize)intrinsicContentSize {
  835. BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
  836. return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f);
  837. }
  838. #pragma mark - Properties
  839. - (void)setProgress:(float)progress {
  840. if (progress != _progress) {
  841. _progress = progress;
  842. [self setNeedsDisplay];
  843. }
  844. }
  845. - (void)setProgressColor:(UIColor *)progressColor {
  846. NSAssert(progressColor, @"The color should not be nil.");
  847. if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) {
  848. _progressColor = progressColor;
  849. [self setNeedsDisplay];
  850. }
  851. }
  852. - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor {
  853. NSAssert(progressRemainingColor, @"The color should not be nil.");
  854. if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) {
  855. _progressRemainingColor = progressRemainingColor;
  856. [self setNeedsDisplay];
  857. }
  858. }
  859. #pragma mark - Drawing
  860. - (void)drawRect:(CGRect)rect {
  861. CGContextRef context = UIGraphicsGetCurrentContext();
  862. CGContextSetLineWidth(context, 2);
  863. CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
  864. CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
  865. // Draw background and Border
  866. CGFloat radius = (rect.size.height / 2) - 2;
  867. CGContextMoveToPoint(context, 2, rect.size.height/2);
  868. CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
  869. CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
  870. CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
  871. CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
  872. CGContextDrawPath(context, kCGPathFillStroke);
  873. CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
  874. radius = radius - 2;
  875. CGFloat amount = self.progress * rect.size.width;
  876. // Progress in the middle area
  877. if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
  878. CGContextMoveToPoint(context, 4, rect.size.height/2);
  879. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  880. CGContextAddLineToPoint(context, amount, 4);
  881. CGContextAddLineToPoint(context, amount, radius + 4);
  882. CGContextMoveToPoint(context, 4, rect.size.height/2);
  883. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  884. CGContextAddLineToPoint(context, amount, rect.size.height - 4);
  885. CGContextAddLineToPoint(context, amount, radius + 4);
  886. CGContextFillPath(context);
  887. }
  888. // Progress in the right arc
  889. else if (amount > radius + 4) {
  890. CGFloat x = amount - (rect.size.width - radius - 4);
  891. CGContextMoveToPoint(context, 4, rect.size.height/2);
  892. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  893. CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
  894. CGFloat angle = -acos(x/radius);
  895. if (isnan(angle)) angle = 0;
  896. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
  897. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  898. CGContextMoveToPoint(context, 4, rect.size.height/2);
  899. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  900. CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
  901. angle = acos(x/radius);
  902. if (isnan(angle)) angle = 0;
  903. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
  904. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  905. CGContextFillPath(context);
  906. }
  907. // Progress is in the left arc
  908. else if (amount < radius + 4 && amount > 0) {
  909. CGContextMoveToPoint(context, 4, rect.size.height/2);
  910. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  911. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  912. CGContextMoveToPoint(context, 4, rect.size.height/2);
  913. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  914. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  915. CGContextFillPath(context);
  916. }
  917. }
  918. @end
  919. @interface MBBackgroundView ()
  920. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  921. @property UIVisualEffectView *effectView;
  922. #endif
  923. #if !TARGET_OS_TV
  924. @property UIToolbar *toolbar;
  925. #endif
  926. @end
  927. @implementation MBBackgroundView
  928. #pragma mark - Lifecycle
  929. - (instancetype)initWithFrame:(CGRect)frame {
  930. if ((self = [super initWithFrame:frame])) {
  931. if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
  932. _style = MBProgressHUDBackgroundStyleBlur;
  933. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  934. _blurEffectStyle = UIBlurEffectStyleLight;
  935. #endif
  936. if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
  937. _color = [UIColor colorWithWhite:0.8f alpha:0.6f];
  938. } else {
  939. _color = [UIColor colorWithWhite:0.95f alpha:0.6f];
  940. }
  941. } else {
  942. _style = MBProgressHUDBackgroundStyleSolidColor;
  943. _color = [[UIColor blackColor] colorWithAlphaComponent:0.8];
  944. }
  945. self.clipsToBounds = YES;
  946. [self updateForBackgroundStyle];
  947. }
  948. return self;
  949. }
  950. #pragma mark - Layout
  951. - (CGSize)intrinsicContentSize {
  952. // Smallest size possible. Content pushes against this.
  953. return CGSizeZero;
  954. }
  955. #pragma mark - Appearance
  956. - (void)setStyle:(MBProgressHUDBackgroundStyle)style {
  957. if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) {
  958. style = MBProgressHUDBackgroundStyleSolidColor;
  959. }
  960. if (_style != style) {
  961. _style = style;
  962. [self updateForBackgroundStyle];
  963. }
  964. }
  965. - (void)setColor:(UIColor *)color {
  966. NSAssert(color, @"The color should not be nil.");
  967. if (color != _color && ![color isEqual:_color]) {
  968. _color = color;
  969. [self updateViewsForColor:color];
  970. }
  971. }
  972. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  973. - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle {
  974. if (_blurEffectStyle == blurEffectStyle) {
  975. return;
  976. }
  977. _blurEffectStyle = blurEffectStyle;
  978. [self updateForBackgroundStyle];
  979. }
  980. #endif
  981. ///////////////////////////////////////////////////////////////////////////////////////////
  982. #pragma mark - Views
  983. - (void)updateForBackgroundStyle {
  984. MBProgressHUDBackgroundStyle style = self.style;
  985. if (style == MBProgressHUDBackgroundStyleBlur) {
  986. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  987. if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
  988. UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle];
  989. UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
  990. [self addSubview:effectView];
  991. effectView.frame = self.bounds;
  992. effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  993. self.backgroundColor = self.color;
  994. self.layer.allowsGroupOpacity = NO;
  995. self.effectView = effectView;
  996. } else {
  997. #endif
  998. #if !TARGET_OS_TV
  999. UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)];
  1000. toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  1001. toolbar.barTintColor = self.color;
  1002. toolbar.translucent = YES;
  1003. [self addSubview:toolbar];
  1004. self.toolbar = toolbar;
  1005. #endif
  1006. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  1007. }
  1008. #endif
  1009. } else {
  1010. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  1011. if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
  1012. [self.effectView removeFromSuperview];
  1013. self.effectView = nil;
  1014. } else {
  1015. #endif
  1016. #if !TARGET_OS_TV
  1017. [self.toolbar removeFromSuperview];
  1018. self.toolbar = nil;
  1019. #endif
  1020. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV
  1021. }
  1022. #endif
  1023. self.backgroundColor = self.color;
  1024. }
  1025. }
  1026. - (void)updateViewsForColor:(UIColor *)color {
  1027. if (self.style == MBProgressHUDBackgroundStyleBlur) {
  1028. if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
  1029. self.backgroundColor = self.color;
  1030. } else {
  1031. #if !TARGET_OS_TV
  1032. self.toolbar.barTintColor = color;
  1033. #endif
  1034. }
  1035. } else {
  1036. self.backgroundColor = self.color;
  1037. }
  1038. }
  1039. @end
  1040. @implementation MBProgressHUD (Deprecated)
  1041. #pragma mark - Class
  1042. + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
  1043. NSArray *huds = [MBProgressHUD allHUDsForView:view];
  1044. for (MBProgressHUD *hud in huds) {
  1045. hud.removeFromSuperViewOnHide = YES;
  1046. [hud hideAnimated:animated];
  1047. }
  1048. return [huds count];
  1049. }
  1050. + (NSArray *)allHUDsForView:(UIView *)view {
  1051. NSMutableArray *huds = [NSMutableArray array];
  1052. NSArray *subviews = view.subviews;
  1053. for (UIView *aView in subviews) {
  1054. if ([aView isKindOfClass:self]) {
  1055. [huds addObject:aView];
  1056. }
  1057. }
  1058. return [NSArray arrayWithArray:huds];
  1059. }
  1060. #pragma mark - Lifecycle
  1061. - (id)initWithWindow:(UIWindow *)window {
  1062. return [self initWithView:window];
  1063. }
  1064. #pragma mark - Show & hide
  1065. - (void)show:(BOOL)animated {
  1066. [self showAnimated:animated];
  1067. }
  1068. - (void)hide:(BOOL)animated {
  1069. [self hideAnimated:animated];
  1070. }
  1071. - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay {
  1072. [self hideAnimated:animated afterDelay:delay];
  1073. }
  1074. #pragma mark - Threading
  1075. - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
  1076. [self showAnimated:animated whileExecutingBlock:^{
  1077. #pragma clang diagnostic push
  1078. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  1079. // Start executing the requested task
  1080. [target performSelector:method withObject:object];
  1081. #pragma clang diagnostic pop
  1082. }];
  1083. }
  1084. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block {
  1085. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1086. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
  1087. }
  1088. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)(void))completion {
  1089. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1090. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion];
  1091. }
  1092. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue {
  1093. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
  1094. }
  1095. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion {
  1096. self.taskInProgress = YES;
  1097. self.completionBlock = completion;
  1098. dispatch_async(queue, ^(void) {
  1099. block();
  1100. dispatch_async(dispatch_get_main_queue(), ^(void) {
  1101. [self cleanUp];
  1102. });
  1103. });
  1104. [self showAnimated:animated];
  1105. }
  1106. - (void)cleanUp {
  1107. self.taskInProgress = NO;
  1108. [self hideAnimated:self.useAnimation];
  1109. }
  1110. #pragma mark - Labels
  1111. - (NSString *)labelText {
  1112. return self.label.text;
  1113. }
  1114. - (void)setLabelText:(NSString *)labelText {
  1115. MBMainThreadAssert();
  1116. self.label.text = labelText;
  1117. }
  1118. - (UIFont *)labelFont {
  1119. return self.label.font;
  1120. }
  1121. - (void)setLabelFont:(UIFont *)labelFont {
  1122. MBMainThreadAssert();
  1123. self.label.font = labelFont;
  1124. }
  1125. - (UIColor *)labelColor {
  1126. return self.label.textColor;
  1127. }
  1128. - (void)setLabelColor:(UIColor *)labelColor {
  1129. MBMainThreadAssert();
  1130. self.label.textColor = labelColor;
  1131. }
  1132. - (NSString *)detailsLabelText {
  1133. return self.detailsLabel.text;
  1134. }
  1135. - (void)setDetailsLabelText:(NSString *)detailsLabelText {
  1136. MBMainThreadAssert();
  1137. self.detailsLabel.text = detailsLabelText;
  1138. }
  1139. - (UIFont *)detailsLabelFont {
  1140. return self.detailsLabel.font;
  1141. }
  1142. - (void)setDetailsLabelFont:(UIFont *)detailsLabelFont {
  1143. MBMainThreadAssert();
  1144. self.detailsLabel.font = detailsLabelFont;
  1145. }
  1146. - (UIColor *)detailsLabelColor {
  1147. return self.detailsLabel.textColor;
  1148. }
  1149. - (void)setDetailsLabelColor:(UIColor *)detailsLabelColor {
  1150. MBMainThreadAssert();
  1151. self.detailsLabel.textColor = detailsLabelColor;
  1152. }
  1153. - (CGFloat)opacity {
  1154. return _opacity;
  1155. }
  1156. - (void)setOpacity:(CGFloat)opacity {
  1157. MBMainThreadAssert();
  1158. _opacity = opacity;
  1159. }
  1160. - (UIColor *)color {
  1161. return self.bezelView.color;
  1162. }
  1163. - (void)setColor:(UIColor *)color {
  1164. MBMainThreadAssert();
  1165. self.bezelView.color = color;
  1166. }
  1167. - (CGFloat)yOffset {
  1168. return self.offset.y;
  1169. }
  1170. - (void)setYOffset:(CGFloat)yOffset {
  1171. MBMainThreadAssert();
  1172. self.offset = CGPointMake(self.offset.x, yOffset);
  1173. }
  1174. - (CGFloat)xOffset {
  1175. return self.offset.x;
  1176. }
  1177. - (void)setXOffset:(CGFloat)xOffset {
  1178. MBMainThreadAssert();
  1179. self.offset = CGPointMake(xOffset, self.offset.y);
  1180. }
  1181. - (CGFloat)cornerRadius {
  1182. return self.bezelView.layer.cornerRadius;
  1183. }
  1184. - (void)setCornerRadius:(CGFloat)cornerRadius {
  1185. MBMainThreadAssert();
  1186. self.bezelView.layer.cornerRadius = cornerRadius;
  1187. }
  1188. - (BOOL)dimBackground {
  1189. MBBackgroundView *backgroundView = self.backgroundView;
  1190. UIColor *dimmedColor = [UIColor colorWithWhite:0.f alpha:.2f];
  1191. return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor];
  1192. }
  1193. - (void)setDimBackground:(BOOL)dimBackground {
  1194. MBMainThreadAssert();
  1195. self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
  1196. self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor];
  1197. }
  1198. - (CGSize)size {
  1199. return self.bezelView.frame.size;
  1200. }
  1201. - (UIColor *)activityIndicatorColor {
  1202. return _activityIndicatorColor;
  1203. }
  1204. - (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor {
  1205. if (activityIndicatorColor != _activityIndicatorColor) {
  1206. _activityIndicatorColor = activityIndicatorColor;
  1207. UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator;
  1208. if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
  1209. [indicator setColor:activityIndicatorColor];
  1210. }
  1211. }
  1212. }
  1213. @end
  1214. @implementation MBProgressHUDRoundedButton
  1215. #pragma mark - Lifecycle
  1216. - (instancetype)initWithFrame:(CGRect)frame {
  1217. self = [super initWithFrame:frame];
  1218. if (self) {
  1219. CALayer *layer = self.layer;
  1220. layer.borderWidth = 1.f;
  1221. }
  1222. return self;
  1223. }
  1224. #pragma mark - Layout
  1225. - (void)layoutSubviews {
  1226. [super layoutSubviews];
  1227. // Fully rounded corners
  1228. CGFloat height = CGRectGetHeight(self.bounds);
  1229. self.layer.cornerRadius = ceil(height / 2.f);
  1230. }
  1231. - (CGSize)intrinsicContentSize {
  1232. // Only show if we have associated control events
  1233. if (self.allControlEvents == 0) return CGSizeZero;
  1234. CGSize size = [super intrinsicContentSize];
  1235. // Add some side padding
  1236. size.width += 20.f;
  1237. return size;
  1238. }
  1239. #pragma mark - Color
  1240. - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
  1241. [super setTitleColor:color forState:state];
  1242. // Update related colors
  1243. [self setHighlighted:self.highlighted];
  1244. self.layer.borderColor = color.CGColor;
  1245. }
  1246. - (void)setHighlighted:(BOOL)highlighted {
  1247. [super setHighlighted:highlighted];
  1248. UIColor *baseColor = [self titleColorForState:UIControlStateSelected];
  1249. self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor];
  1250. }
  1251. @end