PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ios/MBProgressHUD.m

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