MBProgressHUD.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. //
  2. // MBProgressHUD.m
  3. // Version 0.8
  4. // Created by Matej Bukovinski on 2.4.09.
  5. //
  6. #import "MBProgressHUD.h"
  7. #import <tgmath.h>
  8. #if __has_feature(objc_arc)
  9. #define MB_AUTORELEASE(exp) exp
  10. #define MB_RELEASE(exp) exp
  11. #define MB_RETAIN(exp) exp
  12. #else
  13. #define MB_AUTORELEASE(exp) [exp autorelease]
  14. #define MB_RELEASE(exp) [exp release]
  15. #define MB_RETAIN(exp) [exp retain]
  16. #endif
  17. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
  18. #define MBLabelAlignmentCenter NSTextAlignmentCenter
  19. #else
  20. #define MBLabelAlignmentCenter UITextAlignmentCenter
  21. #endif
  22. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  23. #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text \
  24. sizeWithAttributes:@{NSFontAttributeName:font}] : CGSizeZero;
  25. #else
  26. #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text sizeWithFont:font] : CGSizeZero;
  27. #endif
  28. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  29. #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
  30. boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \
  31. attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero;
  32. #else
  33. #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
  34. sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero;
  35. #endif
  36. static const CGFloat kPadding = 4.f;
  37. static const CGFloat kLabelFontSize = 16.f;
  38. static const CGFloat kDetailsLabelFontSize = 12.f;
  39. @interface MBProgressHUD ()
  40. - (void)setupLabels;
  41. - (void)registerForKVO;
  42. - (void)unregisterFromKVO;
  43. - (NSArray *)observableKeypaths;
  44. - (void)registerForNotifications;
  45. - (void)unregisterFromNotifications;
  46. - (void)updateUIForKeypath:(NSString *)keyPath;
  47. - (void)hideUsingAnimation:(BOOL)animated;
  48. - (void)showUsingAnimation:(BOOL)animated;
  49. - (void)done;
  50. - (void)updateIndicators;
  51. - (void)handleGraceTimer:(NSTimer *)theTimer;
  52. - (void)handleMinShowTimer:(NSTimer *)theTimer;
  53. - (void)setTransformForCurrentOrientation:(BOOL)animated;
  54. - (void)cleanUp;
  55. - (void)launchExecution;
  56. - (void)deviceOrientationDidChange:(NSNotification *)notification;
  57. - (void)hideDelayed:(NSNumber *)animated;
  58. @property (atomic, MB_STRONG) UIView *indicator;
  59. @property (atomic, MB_STRONG) NSTimer *graceTimer;
  60. @property (atomic, MB_STRONG) NSTimer *minShowTimer;
  61. @property (atomic, MB_STRONG) NSDate *showStarted;
  62. @property (atomic, assign) CGSize size;
  63. @end
  64. @implementation MBProgressHUD {
  65. BOOL useAnimation;
  66. SEL methodForExecution;
  67. id targetForExecution;
  68. id objectForExecution;
  69. UILabel *label;
  70. UILabel *detailsLabel;
  71. BOOL isFinished;
  72. CGAffineTransform rotationTransform;
  73. }
  74. #pragma mark - Properties
  75. @synthesize animationType;
  76. @synthesize delegate;
  77. @synthesize opacity;
  78. @synthesize color;
  79. @synthesize labelFont;
  80. @synthesize labelColor;
  81. @synthesize detailsLabelFont;
  82. @synthesize detailsLabelColor;
  83. @synthesize indicator;
  84. @synthesize xOffset;
  85. @synthesize yOffset;
  86. @synthesize minSize;
  87. @synthesize square;
  88. @synthesize margin;
  89. @synthesize dimBackground;
  90. @synthesize graceTime;
  91. @synthesize minShowTime;
  92. @synthesize graceTimer;
  93. @synthesize minShowTimer;
  94. @synthesize taskInProgress;
  95. @synthesize removeFromSuperViewOnHide;
  96. @synthesize customView;
  97. @synthesize showStarted;
  98. @synthesize mode;
  99. @synthesize labelText;
  100. @synthesize detailsLabelText;
  101. @synthesize progress;
  102. @synthesize size;
  103. #if NS_BLOCKS_AVAILABLE
  104. @synthesize completionBlock;
  105. #endif
  106. #pragma mark - Class methods
  107. + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
  108. MBProgressHUD *hud = [[self alloc] initWithFrame:CGRectMake(0, 0,view.width,view.height)];
  109. [view addSubview:hud];
  110. [hud show:animated];
  111. return MB_AUTORELEASE(hud);
  112. }
  113. + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
  114. MBProgressHUD *hud = [self HUDForView:view];
  115. if (hud != nil) {
  116. hud.removeFromSuperViewOnHide = YES;
  117. [hud hide:animated];
  118. return YES;
  119. }
  120. return NO;
  121. }
  122. + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
  123. NSArray *huds = [MBProgressHUD allHUDsForView:view];
  124. for (MBProgressHUD *hud in huds) {
  125. hud.removeFromSuperViewOnHide = YES;
  126. [hud hide:animated];
  127. }
  128. return [huds count];
  129. }
  130. + (MB_INSTANCETYPE)HUDForView:(UIView *)view {
  131. NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
  132. for (UIView *subview in subviewsEnum) {
  133. if ([subview isKindOfClass:self]) {
  134. return (MBProgressHUD *)subview;
  135. }
  136. }
  137. return nil;
  138. }
  139. + (NSArray *)allHUDsForView:(UIView *)view {
  140. NSMutableArray *huds = [NSMutableArray array];
  141. NSArray *subviews = view.subviews;
  142. for (UIView *aView in subviews) {
  143. if ([aView isKindOfClass:self]) {
  144. [huds addObject:aView];
  145. }
  146. }
  147. return [NSArray arrayWithArray:huds];
  148. }
  149. #pragma mark - Lifecycle
  150. - (id)initWithFrame:(CGRect)frame {
  151. self = [super initWithFrame:frame];
  152. if (self) {
  153. // Set default values for properties
  154. self.animationType = MBProgressHUDAnimationFade;
  155. self.mode = MBProgressHUDModeIndeterminate;
  156. self.labelText = nil;
  157. self.detailsLabelText = nil;
  158. self.opacity = 0.8f;
  159. self.color = nil;
  160. self.labelFont = YBLBFont(kLabelFontSize);
  161. self.labelColor = [UIColor whiteColor];
  162. self.detailsLabelFont = YBLBFont(kDetailsLabelFontSize);
  163. self.detailsLabelColor = [UIColor whiteColor];
  164. self.xOffset = 0.0f;
  165. self.yOffset = 0.0f;
  166. self.dimBackground = NO;
  167. self.margin = 20.0f;
  168. self.cornerRadius = 10.0f;
  169. self.graceTime = 0.0f;
  170. self.minShowTime = 0.0f;
  171. self.removeFromSuperViewOnHide = NO;
  172. self.minSize = CGSizeZero;
  173. self.square = NO;
  174. self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
  175. | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
  176. // Transparent background
  177. self.opaque = NO;
  178. self.backgroundColor = [UIColor clearColor];
  179. // Make it invisible for now
  180. self.alpha = 0.0f;
  181. taskInProgress = NO;
  182. rotationTransform = CGAffineTransformIdentity;
  183. [self setupLabels];
  184. [self updateIndicators];
  185. [self registerForKVO];
  186. [self registerForNotifications];
  187. }
  188. return self;
  189. }
  190. - (id)initWithView:(UIView *)view {
  191. NSAssert(view, @"View must not be nil.");
  192. return [self initWithFrame:view.bounds];
  193. }
  194. - (id)initWithWindow:(UIWindow *)window {
  195. return [self initWithView:window];
  196. }
  197. - (void)dealloc {
  198. [self unregisterFromNotifications];
  199. [self unregisterFromKVO];
  200. #if !__has_feature(objc_arc)
  201. [color release];
  202. [indicator release];
  203. [label release];
  204. [detailsLabel release];
  205. [labelText release];
  206. [detailsLabelText release];
  207. [graceTimer release];
  208. [minShowTimer release];
  209. [showStarted release];
  210. [customView release];
  211. [labelFont release];
  212. [labelColor release];
  213. [detailsLabelFont release];
  214. [detailsLabelColor release];
  215. #if NS_BLOCKS_AVAILABLE
  216. [completionBlock release];
  217. #endif
  218. [super dealloc];
  219. #endif
  220. }
  221. #pragma mark - Show & hide
  222. - (void)show:(BOOL)animated {
  223. useAnimation = animated;
  224. // If the grace time is set postpone the HUD display
  225. if (self.graceTime > 0.0) {
  226. self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self
  227. selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
  228. }
  229. // ... otherwise show the HUD imediately
  230. else {
  231. [self setNeedsDisplay];
  232. [self showUsingAnimation:useAnimation];
  233. }
  234. }
  235. - (void)hide:(BOOL)animated {
  236. useAnimation = animated;
  237. // If the minShow time is set, calculate how long the hud was shown,
  238. // and pospone the hiding operation if necessary
  239. if (self.minShowTime > 0.0 && showStarted) {
  240. NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted];
  241. if (interv < self.minShowTime) {
  242. self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self
  243. selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
  244. return;
  245. }
  246. }
  247. // ... otherwise hide the HUD immediately
  248. [self hideUsingAnimation:useAnimation];
  249. }
  250. - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay {
  251. [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay];
  252. }
  253. - (void)hideDelayed:(NSNumber *)animated {
  254. [self hide:[animated boolValue]];
  255. }
  256. #pragma mark - Timer callbacks
  257. - (void)handleGraceTimer:(NSTimer *)theTimer {
  258. // Show the HUD only if the task is still running
  259. if (taskInProgress) {
  260. [self setNeedsDisplay];
  261. [self showUsingAnimation:useAnimation];
  262. }
  263. }
  264. - (void)handleMinShowTimer:(NSTimer *)theTimer {
  265. [self hideUsingAnimation:useAnimation];
  266. }
  267. #pragma mark - View Hierrarchy
  268. - (void)didMoveToSuperview {
  269. // We need to take care of rotation ourselfs if we're adding the HUD to a window
  270. if ([self.superview isKindOfClass:[UIWindow class]]) {
  271. [self setTransformForCurrentOrientation:NO];
  272. }
  273. }
  274. #pragma mark - Internal show & hide operations
  275. - (void)showUsingAnimation:(BOOL)animated {
  276. if (animated && animationType == MBProgressHUDAnimationZoomIn) {
  277. self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
  278. } else if (animated && animationType == MBProgressHUDAnimationZoomOut) {
  279. self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
  280. }
  281. self.showStarted = [NSDate date];
  282. // Fade in
  283. if (animated) {
  284. [UIView beginAnimations:nil context:NULL];
  285. [UIView setAnimationDuration:0.30];
  286. self.alpha = 1.0f;
  287. if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) {
  288. self.transform = rotationTransform;
  289. }
  290. [UIView commitAnimations];
  291. }
  292. else {
  293. self.alpha = 1.0f;
  294. }
  295. }
  296. - (void)hideUsingAnimation:(BOOL)animated {
  297. // Fade out
  298. if (animated && showStarted) {
  299. [UIView beginAnimations:nil context:NULL];
  300. [UIView setAnimationDuration:0.30];
  301. [UIView setAnimationDelegate:self];
  302. [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)];
  303. // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden
  304. // in the done method
  305. if (animationType == MBProgressHUDAnimationZoomIn) {
  306. self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
  307. } else if (animationType == MBProgressHUDAnimationZoomOut) {
  308. self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
  309. }
  310. self.alpha = 0.02f;
  311. [UIView commitAnimations];
  312. }
  313. else {
  314. self.alpha = 0.0f;
  315. [self done];
  316. }
  317. self.showStarted = nil;
  318. }
  319. - (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context {
  320. [self done];
  321. }
  322. - (void)done {
  323. [NSObject cancelPreviousPerformRequestsWithTarget:self];
  324. isFinished = YES;
  325. self.alpha = 0.0f;
  326. if (removeFromSuperViewOnHide) {
  327. [self removeFromSuperview];
  328. }
  329. #if NS_BLOCKS_AVAILABLE
  330. if (self.completionBlock) {
  331. self.completionBlock();
  332. self.completionBlock = NULL;
  333. }
  334. #endif
  335. if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
  336. [delegate performSelector:@selector(hudWasHidden:) withObject:self];
  337. }
  338. }
  339. #pragma mark - Threading
  340. - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated {
  341. methodForExecution = method;
  342. targetForExecution = MB_RETAIN(target);
  343. objectForExecution = MB_RETAIN(object);
  344. // Launch execution in new thread
  345. self.taskInProgress = YES;
  346. [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil];
  347. // Show HUD view
  348. [self show:animated];
  349. }
  350. #if NS_BLOCKS_AVAILABLE
  351. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block {
  352. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  353. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
  354. }
  355. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion {
  356. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  357. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion];
  358. }
  359. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue {
  360. [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL];
  361. }
  362. - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue
  363. completionBlock:(MBProgressHUDCompletionBlock)completion {
  364. self.taskInProgress = YES;
  365. self.completionBlock = completion;
  366. dispatch_async(queue, ^(void) {
  367. block();
  368. dispatch_async(dispatch_get_main_queue(), ^(void) {
  369. [self cleanUp];
  370. });
  371. });
  372. [self show:animated];
  373. }
  374. #endif
  375. - (void)launchExecution {
  376. @autoreleasepool {
  377. #pragma clang diagnostic push
  378. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  379. // Start executing the requested task
  380. [targetForExecution performSelector:methodForExecution withObject:objectForExecution];
  381. #pragma clang diagnostic pop
  382. // Task completed, update view in main thread (note: view operations should
  383. // be done only in the main thread)
  384. [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];
  385. }
  386. }
  387. - (void)cleanUp {
  388. taskInProgress = NO;
  389. #if !__has_feature(objc_arc)
  390. [targetForExecution release];
  391. [objectForExecution release];
  392. #else
  393. targetForExecution = nil;
  394. objectForExecution = nil;
  395. #endif
  396. [self hide:useAnimation];
  397. }
  398. #pragma mark - UI
  399. - (void)setupLabels {
  400. label = [[UILabel alloc] initWithFrame:self.bounds];
  401. label.adjustsFontSizeToFitWidth = NO;
  402. label.textAlignment = MBLabelAlignmentCenter;
  403. label.opaque = NO;
  404. label.backgroundColor = [UIColor clearColor];
  405. label.textColor = self.labelColor;
  406. label.font = self.labelFont;
  407. label.text = self.labelText;
  408. [self addSubview:label];
  409. detailsLabel = [[UILabel alloc] initWithFrame:self.bounds];
  410. detailsLabel.font = self.detailsLabelFont;
  411. detailsLabel.adjustsFontSizeToFitWidth = NO;
  412. detailsLabel.textAlignment = MBLabelAlignmentCenter;
  413. detailsLabel.opaque = NO;
  414. detailsLabel.backgroundColor = [UIColor clearColor];
  415. detailsLabel.textColor = self.detailsLabelColor;
  416. detailsLabel.numberOfLines = 0;
  417. detailsLabel.font = self.detailsLabelFont;
  418. detailsLabel.text = self.detailsLabelText;
  419. [self addSubview:detailsLabel];
  420. }
  421. - (void)updateIndicators {
  422. BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
  423. BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
  424. if (mode == MBProgressHUDModeIndeterminate && !isActivityIndicator) {
  425. // Update to indeterminate indicator
  426. [indicator removeFromSuperview];
  427. self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
  428. initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
  429. [(UIActivityIndicatorView *)indicator startAnimating];
  430. [self addSubview:indicator];
  431. }
  432. else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
  433. // Update to bar determinate indicator
  434. [indicator removeFromSuperview];
  435. self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
  436. [self addSubview:indicator];
  437. }
  438. else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
  439. if (!isRoundIndicator) {
  440. // Update to determinante indicator
  441. [indicator removeFromSuperview];
  442. self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);
  443. [self addSubview:indicator];
  444. }
  445. if (mode == MBProgressHUDModeAnnularDeterminate) {
  446. [(MBRoundProgressView *)indicator setAnnular:YES];
  447. }
  448. }
  449. else if (mode == MBProgressHUDModeCustomView && customView != indicator) {
  450. // Update custom view indicator
  451. [indicator removeFromSuperview];
  452. self.indicator = customView;
  453. [self addSubview:indicator];
  454. } else if (mode == MBProgressHUDModeText) {
  455. [indicator removeFromSuperview];
  456. self.indicator = nil;
  457. }
  458. }
  459. #pragma mark - Layout
  460. - (void)layoutSubviews {
  461. // Entirely cover the parent view
  462. UIView *parent = self.superview;
  463. if (parent && [parent isKindOfClass:[UIWindow class]]) {
  464. self.frame = parent.bounds;
  465. // CGRectMake(0, iPhoneX?88:64, parent.bounds.size.width, parent.bounds.size.height-(iPhoneX?88:64));
  466. }
  467. CGRect bounds = self.bounds;
  468. // Determine the total widt and height needed
  469. CGFloat maxWidth = bounds.size.width - 4 * margin;
  470. CGSize totalSize = CGSizeZero;
  471. CGRect indicatorF = indicator.bounds;
  472. indicatorF.size.width = MIN(indicatorF.size.width, maxWidth);
  473. totalSize.width = MAX(totalSize.width, indicatorF.size.width);
  474. totalSize.height += indicatorF.size.height;
  475. CGSize labelSize = MB_TEXTSIZE(label.text, label.font);
  476. labelSize.width = MIN(labelSize.width, maxWidth);
  477. totalSize.width = MAX(totalSize.width, labelSize.width);
  478. totalSize.height += labelSize.height;
  479. if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
  480. totalSize.height += kPadding;
  481. }
  482. CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin;
  483. CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);
  484. CGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode);
  485. totalSize.width = MAX(totalSize.width, detailsLabelSize.width);
  486. totalSize.height += detailsLabelSize.height;
  487. if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) {
  488. totalSize.height += kPadding;
  489. }
  490. totalSize.width += 2 * margin;
  491. totalSize.height += 2 * margin;
  492. // Position elements
  493. CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset;
  494. CGFloat xPos = xOffset;
  495. indicatorF.origin.y = yPos;
  496. indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos;
  497. indicator.frame = indicatorF;
  498. yPos += indicatorF.size.height;
  499. if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
  500. yPos += kPadding;
  501. }
  502. CGRect labelF;
  503. labelF.origin.y = yPos;
  504. labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos;
  505. labelF.size = labelSize;
  506. label.frame = labelF;
  507. yPos += labelF.size.height;
  508. if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) {
  509. yPos += kPadding;
  510. }
  511. CGRect detailsLabelF;
  512. detailsLabelF.origin.y = yPos;
  513. detailsLabelF.origin.x = round((bounds.size.width - detailsLabelSize.width) / 2) + xPos;
  514. detailsLabelF.size = detailsLabelSize;
  515. detailsLabel.frame = detailsLabelF;
  516. // Enforce minsize and quare rules
  517. if (square) {
  518. CGFloat max = MAX(totalSize.width, totalSize.height);
  519. if (max <= bounds.size.width - 2 * margin) {
  520. totalSize.width = max;
  521. }
  522. if (max <= bounds.size.height - 2 * margin) {
  523. totalSize.height = max;
  524. }
  525. }
  526. if (totalSize.width < minSize.width) {
  527. totalSize.width = minSize.width;
  528. }
  529. if (totalSize.height < minSize.height) {
  530. totalSize.height = minSize.height;
  531. }
  532. self.size = totalSize;
  533. }
  534. #pragma mark BG Drawing
  535. - (void)drawRect:(CGRect)rect {
  536. CGContextRef context = UIGraphicsGetCurrentContext();
  537. UIGraphicsPushContext(context);
  538. if (self.dimBackground) {
  539. //Gradient colours
  540. size_t gradLocationsNum = 2;
  541. CGFloat gradLocations[2] = {0.0f, 1.0f};
  542. CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f};
  543. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  544. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
  545. CGColorSpaceRelease(colorSpace);
  546. //Gradient center
  547. CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
  548. //Gradient radius
  549. float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
  550. //Gradient draw
  551. CGContextDrawRadialGradient (context, gradient, gradCenter,
  552. 0, gradCenter, gradRadius,
  553. kCGGradientDrawsAfterEndLocation);
  554. CGGradientRelease(gradient);
  555. }
  556. // Set background rect color
  557. if (self.color) {
  558. CGContextSetFillColorWithColor(context, self.color.CGColor);
  559. } else {
  560. CGContextSetGrayFillColor(context, 0.0f, self.opacity);
  561. }
  562. // Center HUD
  563. CGRect allRect = self.bounds;
  564. // Draw rounded HUD backgroud rect
  565. CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset,
  566. round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height);
  567. float radius = self.cornerRadius;
  568. CGContextBeginPath(context);
  569. CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect));
  570. CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0);
  571. CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0);
  572. CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0);
  573. CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0);
  574. CGContextClosePath(context);
  575. CGContextFillPath(context);
  576. UIGraphicsPopContext();
  577. }
  578. #pragma mark - KVO
  579. - (void)registerForKVO {
  580. for (NSString *keyPath in [self observableKeypaths]) {
  581. [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
  582. }
  583. }
  584. - (void)unregisterFromKVO {
  585. for (NSString *keyPath in [self observableKeypaths]) {
  586. [self removeObserver:self forKeyPath:keyPath];
  587. }
  588. }
  589. - (NSArray *)observableKeypaths {
  590. return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor",
  591. @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", nil];
  592. }
  593. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  594. if (![NSThread isMainThread]) {
  595. [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO];
  596. } else {
  597. [self updateUIForKeypath:keyPath];
  598. }
  599. }
  600. - (void)updateUIForKeypath:(NSString *)keyPath {
  601. if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"]) {
  602. [self updateIndicators];
  603. } else if ([keyPath isEqualToString:@"labelText"]) {
  604. label.text = self.labelText;
  605. } else if ([keyPath isEqualToString:@"labelFont"]) {
  606. label.font = self.labelFont;
  607. } else if ([keyPath isEqualToString:@"labelColor"]) {
  608. label.textColor = self.labelColor;
  609. } else if ([keyPath isEqualToString:@"detailsLabelText"]) {
  610. detailsLabel.text = self.detailsLabelText;
  611. } else if ([keyPath isEqualToString:@"detailsLabelFont"]) {
  612. detailsLabel.font = self.detailsLabelFont;
  613. } else if ([keyPath isEqualToString:@"detailsLabelColor"]) {
  614. detailsLabel.textColor = self.detailsLabelColor;
  615. } else if ([keyPath isEqualToString:@"progress"]) {
  616. if ([indicator respondsToSelector:@selector(setProgress:)]) {
  617. [(id)indicator setValue:@(progress) forKey:@"progress"];
  618. }
  619. return;
  620. }
  621. [self setNeedsLayout];
  622. [self setNeedsDisplay];
  623. }
  624. #pragma mark - Notifications
  625. - (void)registerForNotifications {
  626. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  627. [nc addObserver:self selector:@selector(deviceOrientationDidChange:)
  628. name:UIDeviceOrientationDidChangeNotification object:nil];
  629. }
  630. - (void)unregisterFromNotifications {
  631. [[NSNotificationCenter defaultCenter] removeObserver:self];
  632. }
  633. - (void)deviceOrientationDidChange:(NSNotification *)notification {
  634. UIView *superview = self.superview;
  635. if (!superview) {
  636. return;
  637. } else if ([superview isKindOfClass:[UIWindow class]]) {
  638. [self setTransformForCurrentOrientation:YES];
  639. } else {
  640. self.frame = self.superview.bounds;
  641. [self setNeedsDisplay];
  642. }
  643. }
  644. - (void)setTransformForCurrentOrientation:(BOOL)animated {
  645. // Stay in sync with the superview
  646. if (self.superview) {
  647. self.bounds = self.superview.bounds;
  648. [self setNeedsDisplay];
  649. }
  650. UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
  651. CGFloat radians = 0;
  652. if (UIInterfaceOrientationIsLandscape(orientation)) {
  653. if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; }
  654. else { radians = (CGFloat)M_PI_2; }
  655. // Window coordinates differ!
  656. self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
  657. } else {
  658. if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; }
  659. else { radians = 0; }
  660. }
  661. rotationTransform = CGAffineTransformMakeRotation(radians);
  662. if (animated) {
  663. [UIView beginAnimations:nil context:nil];
  664. }
  665. [self setTransform:rotationTransform];
  666. if (animated) {
  667. [UIView commitAnimations];
  668. }
  669. }
  670. @end
  671. @implementation MBRoundProgressView
  672. #pragma mark - Lifecycle
  673. - (id)init {
  674. return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
  675. }
  676. - (id)initWithFrame:(CGRect)frame {
  677. self = [super initWithFrame:frame];
  678. if (self) {
  679. self.backgroundColor = [UIColor clearColor];
  680. self.opaque = NO;
  681. _progress = 0.f;
  682. _annular = NO;
  683. _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
  684. _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
  685. [self registerForKVO];
  686. }
  687. return self;
  688. }
  689. - (void)dealloc {
  690. [self unregisterFromKVO];
  691. #if !__has_feature(objc_arc)
  692. [_progressTintColor release];
  693. [_backgroundTintColor release];
  694. [super dealloc];
  695. #endif
  696. }
  697. #pragma mark - Drawing
  698. - (void)drawRect:(CGRect)rect {
  699. CGRect allRect = self.bounds;
  700. CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f);
  701. CGContextRef context = UIGraphicsGetCurrentContext();
  702. if (_annular) {
  703. // Draw background
  704. CGFloat lineWidth = 5.f;
  705. UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
  706. processBackgroundPath.lineWidth = lineWidth;
  707. processBackgroundPath.lineCapStyle = kCGLineCapRound;
  708. CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
  709. CGFloat radius = (self.bounds.size.width - lineWidth)/2;
  710. CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
  711. CGFloat endAngle = (2 * (float)M_PI) + startAngle;
  712. [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  713. [_backgroundTintColor set];
  714. [processBackgroundPath stroke];
  715. // Draw progress
  716. UIBezierPath *processPath = [UIBezierPath bezierPath];
  717. processPath.lineCapStyle = kCGLineCapRound;
  718. processPath.lineWidth = lineWidth;
  719. endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
  720. [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  721. [_progressTintColor set];
  722. [processPath stroke];
  723. } else {
  724. // Draw background
  725. [_progressTintColor setStroke];
  726. [_backgroundTintColor setFill];
  727. CGContextSetLineWidth(context, 2.0f);
  728. CGContextFillEllipseInRect(context, circleRect);
  729. CGContextStrokeEllipseInRect(context, circleRect);
  730. // Draw progress
  731. CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2);
  732. CGFloat radius = (allRect.size.width - 4) / 2;
  733. CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
  734. CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
  735. CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f); // white
  736. CGContextMoveToPoint(context, center.x, center.y);
  737. CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
  738. CGContextClosePath(context);
  739. CGContextFillPath(context);
  740. }
  741. }
  742. #pragma mark - KVO
  743. - (void)registerForKVO {
  744. for (NSString *keyPath in [self observableKeypaths]) {
  745. [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
  746. }
  747. }
  748. - (void)unregisterFromKVO {
  749. for (NSString *keyPath in [self observableKeypaths]) {
  750. [self removeObserver:self forKeyPath:keyPath];
  751. }
  752. }
  753. - (NSArray *)observableKeypaths {
  754. return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", @"progress", @"annular", nil];
  755. }
  756. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  757. [self setNeedsDisplay];
  758. }
  759. @end
  760. @implementation MBBarProgressView
  761. #pragma mark - Lifecycle
  762. - (id)init {
  763. return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
  764. }
  765. - (id)initWithFrame:(CGRect)frame {
  766. self = [super initWithFrame:frame];
  767. if (self) {
  768. _progress = 0.f;
  769. _lineColor = [UIColor whiteColor];
  770. _progressColor = [UIColor whiteColor];
  771. _progressRemainingColor = [UIColor clearColor];
  772. self.backgroundColor = [UIColor clearColor];
  773. self.opaque = NO;
  774. [self registerForKVO];
  775. }
  776. return self;
  777. }
  778. - (void)dealloc {
  779. [self unregisterFromKVO];
  780. #if !__has_feature(objc_arc)
  781. [_lineColor release];
  782. [_progressColor release];
  783. [_progressRemainingColor release];
  784. [super dealloc];
  785. #endif
  786. }
  787. #pragma mark - Drawing
  788. - (void)drawRect:(CGRect)rect {
  789. CGContextRef context = UIGraphicsGetCurrentContext();
  790. CGContextSetLineWidth(context, 2);
  791. CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
  792. CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
  793. // Draw background
  794. float radius = (rect.size.height / 2) - 2;
  795. CGContextMoveToPoint(context, 2, rect.size.height/2);
  796. CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
  797. CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
  798. CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
  799. CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
  800. CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
  801. CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
  802. CGContextFillPath(context);
  803. // Draw border
  804. CGContextMoveToPoint(context, 2, rect.size.height/2);
  805. CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
  806. CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2);
  807. CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
  808. CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
  809. CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2);
  810. CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
  811. CGContextStrokePath(context);
  812. CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
  813. radius = radius - 2;
  814. float amount = self.progress * rect.size.width;
  815. // Progress in the middle area
  816. if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
  817. CGContextMoveToPoint(context, 4, rect.size.height/2);
  818. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  819. CGContextAddLineToPoint(context, amount, 4);
  820. CGContextAddLineToPoint(context, amount, radius + 4);
  821. CGContextMoveToPoint(context, 4, rect.size.height/2);
  822. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  823. CGContextAddLineToPoint(context, amount, rect.size.height - 4);
  824. CGContextAddLineToPoint(context, amount, radius + 4);
  825. CGContextFillPath(context);
  826. }
  827. // Progress in the right arc
  828. else if (amount > radius + 4) {
  829. float x = amount - (rect.size.width - radius - 4);
  830. CGContextMoveToPoint(context, 4, rect.size.height/2);
  831. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  832. CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
  833. float angle = -acos(x/radius);
  834. if (isnan(angle)) angle = 0;
  835. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
  836. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  837. CGContextMoveToPoint(context, 4, rect.size.height/2);
  838. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  839. CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
  840. angle = acos(x/radius);
  841. if (isnan(angle)) angle = 0;
  842. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
  843. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  844. CGContextFillPath(context);
  845. }
  846. // Progress is in the left arc
  847. else if (amount < radius + 4 && amount > 0) {
  848. CGContextMoveToPoint(context, 4, rect.size.height/2);
  849. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  850. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  851. CGContextMoveToPoint(context, 4, rect.size.height/2);
  852. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  853. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  854. CGContextFillPath(context);
  855. }
  856. }
  857. #pragma mark - KVO
  858. - (void)registerForKVO {
  859. for (NSString *keyPath in [self observableKeypaths]) {
  860. [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
  861. }
  862. }
  863. - (void)unregisterFromKVO {
  864. for (NSString *keyPath in [self observableKeypaths]) {
  865. [self removeObserver:self forKeyPath:keyPath];
  866. }
  867. }
  868. - (NSArray *)observableKeypaths {
  869. return [NSArray arrayWithObjects:@"lineColor", @"progressRemainingColor", @"progressColor", @"progress", nil];
  870. }
  871. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  872. [self setNeedsDisplay];
  873. }
  874. @end