ImageLoadProgressView.m 36 KB

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