Geen omschrijving

MBProgressHUD.m 32KB

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