MBProgressHUD.m 33 KB

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