MBProgressHUD.m 53 KB

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