MBProgressHUD.m 32 KB

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