You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

831 lines
32 KiB

5 years ago
  1. //
  2. // M13ProgressViewHUD.m
  3. // M13ProgressView
  4. //
  5. /*Copyright (c) 2013 Brandon McQuilkin
  6. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  7. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  8. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  9. */
  10. #import "M13ProgressHUD.h"
  11. #import "UIImage+ImageEffects.h"
  12. @interface M13ProgressHUD ()
  13. @property (nonatomic, readwrite) CGFloat progress;
  14. @end
  15. @implementation M13ProgressHUD
  16. {
  17. UIView *backgroundView;
  18. UIView *maskView;
  19. UILabel *statusLabel;
  20. NSString *optimalStatusString;
  21. BOOL onScreen;
  22. }
  23. #pragma mark Initalization and Setup
  24. - (id)init
  25. {
  26. self = [super init];
  27. if (self) {
  28. [self setup];
  29. }
  30. return self;
  31. }
  32. - (id)initWithFrame:(CGRect)frame
  33. {
  34. self = [super initWithFrame:frame];
  35. if (self) {
  36. [self setup];
  37. }
  38. return self;
  39. }
  40. - (id)initWithCoder:(NSCoder *)aDecoder
  41. {
  42. self = [super initWithCoder:aDecoder];
  43. if (self) {
  44. [self setup];
  45. }
  46. return self;
  47. }
  48. - (id)initWithProgressView:(M13ProgressView *)progressView
  49. {
  50. self = [self init];
  51. if (self) {
  52. _progressView = progressView;
  53. [self setup];
  54. }
  55. return self;
  56. }
  57. - (id)initAndShowWithProgressView:(M13ProgressView *)progressView progress:(CGFloat)progress indeterminate:(BOOL)indeterminate status:(NSString *)status mask:(M13ProgressHUDMaskType)maskType inView:(UIView *)view
  58. {
  59. self = [super init];
  60. if (self) {
  61. _progressView = progressView;
  62. [self setup];
  63. self.progress = progress;
  64. self.indeterminate = indeterminate;
  65. self.status = status;
  66. self.maskType = maskType;
  67. [view addSubview:self];
  68. [self show:YES];
  69. }
  70. return self;
  71. }
  72. - (void)setup
  73. {
  74. //Set the defaults for the progress view
  75. self.backgroundColor = [UIColor clearColor];
  76. self.layer.opacity = 0;
  77. _primaryColor = [UIColor colorWithRed:0 green:122/255.0 blue:1.0 alpha:1.0];
  78. _secondaryColor = [UIColor colorWithRed:181/255.0 green:182/255.0 blue:183/255.0 alpha:1.0];
  79. _progress = 0;
  80. _indeterminate = NO;
  81. _shouldAutorotate = YES;
  82. if (self.frame.size.height != 0 && self.frame.size.width != 0) {
  83. _progressViewSize = CGSizeMake(150 / 4, 150 / 4);
  84. }
  85. _animationDuration = .3;
  86. //Set the other defaults
  87. _applyBlurToBackground = NO;
  88. _statusPosition = M13ProgressHUDStatusPositionBelowProgress;
  89. _contentMargin = 20.0;
  90. _cornerRadius = 20.0;
  91. _maskType = M13ProgressHUDMaskTypeNone;
  92. _maskColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.5];
  93. _statusColor = [UIColor whiteColor];
  94. _statusFont = [UIFont systemFontOfSize:20.0];
  95. _minimumSize = CGSizeMake(150, 150);
  96. _dismissAfterAction = NO;
  97. _hudBackgroundColor = [UIColor colorWithWhite:0 alpha:.8];
  98. //Add the proper views
  99. maskView = [[UIView alloc] init];
  100. [self addSubview:maskView];
  101. backgroundView = [[UIView alloc] init];
  102. backgroundView.backgroundColor = _hudBackgroundColor;
  103. backgroundView.layer.cornerRadius = _cornerRadius;
  104. backgroundView.clipsToBounds = YES;
  105. [self addSubview:backgroundView];
  106. statusLabel = [[UILabel alloc] init];
  107. statusLabel.font = _statusFont;
  108. statusLabel.textColor = _statusColor;
  109. statusLabel.textAlignment = NSTextAlignmentCenter;
  110. statusLabel.contentMode = UIViewContentModeTop;
  111. statusLabel.lineBreakMode = NSLineBreakByWordWrapping;
  112. statusLabel.numberOfLines = 0;
  113. [backgroundView addSubview:statusLabel];
  114. if (_progressView != nil) {
  115. [backgroundView addSubview:_progressView];
  116. }
  117. }
  118. #pragma marks Properties
  119. - (void)setProgressView:(M13ProgressView *)progressView
  120. {
  121. if (_progressView) {
  122. [_progressView removeFromSuperview];
  123. }
  124. [backgroundView addSubview:progressView];
  125. [self setNeedsLayout];
  126. }
  127. - (void)setPrimaryColor:(UIColor *)primaryColor
  128. {
  129. _primaryColor = primaryColor;
  130. _progressView.primaryColor = _primaryColor;
  131. }
  132. - (void)setSecondaryColor:(UIColor *)secondaryColor
  133. {
  134. _secondaryColor = secondaryColor;
  135. _progressView.secondaryColor = _secondaryColor;
  136. }
  137. - (void)setApplyBlurToBackground:(BOOL)applyBlurToBackground
  138. {
  139. _applyBlurToBackground = applyBlurToBackground;
  140. //Only needs to be redrawn if visible
  141. if ([self isVisible]) {
  142. [self drawBackground];
  143. }
  144. }
  145. - (void)setStatusPosition:(M13ProgressHUDStatusPosition)statusPosition
  146. {
  147. _statusPosition = statusPosition;
  148. [self setNeedsLayout];
  149. }
  150. - (void)setOffsetFromCenter:(UIOffset)offsetFromCenter
  151. {
  152. _offsetFromCenter = offsetFromCenter;
  153. [self setNeedsLayout];
  154. }
  155. - (void)setContentMargin:(CGFloat)contentMargin
  156. {
  157. _contentMargin = contentMargin;
  158. [self setNeedsLayout];
  159. }
  160. - (void)setCornerRadius:(CGFloat)cornerRadius
  161. {
  162. _cornerRadius = cornerRadius;
  163. backgroundView.layer.cornerRadius = cornerRadius;
  164. }
  165. - (void)setMaskType:(M13ProgressHUDMaskType)maskType
  166. {
  167. _maskType = maskType;
  168. [self drawMask];
  169. }
  170. - (void)setMaskColor:(UIColor *)maskColor
  171. {
  172. _maskColor = maskColor;
  173. [self drawMask];
  174. }
  175. - (void)setStatusColor:(UIColor *)statusColor
  176. {
  177. _statusColor = statusColor;
  178. statusLabel.textColor = _statusColor;
  179. }
  180. - (void)setStatusFont:(UIFont *)statusFont
  181. {
  182. _statusFont = statusFont;
  183. statusLabel.font = _statusFont;
  184. [self layoutSubviews];
  185. }
  186. - (void)setAnimationDuration:(CGFloat)animationDuration
  187. {
  188. _animationDuration = animationDuration;
  189. _progressView.animationDuration = _animationDuration;
  190. }
  191. - (void)setStatus:(NSString *)status
  192. {
  193. _status = status;
  194. if (_status.length == 0 || _status == nil) {
  195. //Clear the optimal string
  196. optimalStatusString = nil;
  197. } else {
  198. [self recalculateOptimalStatusStringStructure];
  199. }
  200. [self layoutHUD];
  201. }
  202. - (void)setMinimumSize:(CGSize)minimumSize
  203. {
  204. _minimumSize = minimumSize;
  205. [self recalculateOptimalStatusStringStructure];
  206. [self setNeedsLayout];
  207. }
  208. - (void)setDismissAfterAction:(BOOL)dismissAfterAction
  209. {
  210. _dismissAfterAction = dismissAfterAction;
  211. }
  212. - (void)setHudBackgroundColor:(UIColor *)hudBackgroundColor
  213. {
  214. _hudBackgroundColor = hudBackgroundColor;
  215. if ([self isVisible]) {
  216. [self drawBackground];
  217. }
  218. }
  219. - (BOOL)isVisible
  220. {
  221. if (self.alpha == 1) {
  222. return YES;
  223. } else {
  224. return NO;
  225. }
  226. }
  227. - (void)didMoveToSuperview
  228. {
  229. if (_maskType == M13ProgressHUDMaskTypeIOS7Blur || _applyBlurToBackground) {\
  230. [self setNeedsLayout];
  231. [self redrawBlurs];
  232. }
  233. }
  234. - (void)didMoveToWindow
  235. {
  236. if (_maskType == M13ProgressHUDMaskTypeIOS7Blur || _applyBlurToBackground) {
  237. [self setNeedsLayout];
  238. [self redrawBlurs];
  239. }
  240. }
  241. #pragma mark Actions
  242. - (void)setIndeterminate:(BOOL)indeterminate
  243. {
  244. _indeterminate = indeterminate;
  245. _progressView.indeterminate = _indeterminate;
  246. }
  247. - (void)setProgress:(CGFloat)progress animated:(BOOL)animated
  248. {
  249. [_progressView setProgress:progress animated:animated];
  250. self.progress = progress;
  251. }
  252. - (void)performAction:(M13ProgressViewAction)action animated:(BOOL)animated
  253. {
  254. [_progressView performAction:action animated:animated];
  255. }
  256. - (void)show:(BOOL)animated
  257. {
  258. //reset the blurs to the curent screen if need be
  259. [self registerForNotificationCenter];
  260. [self setNeedsLayout];
  261. [self setNeedsDisplay];
  262. onScreen = YES;
  263. //Animate the HUD on screen
  264. CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
  265. fadeAnimation.duration = _animationDuration;
  266. fadeAnimation.fromValue = [NSNumber numberWithFloat:0.0];
  267. fadeAnimation.toValue = [NSNumber numberWithFloat:1.0];
  268. fadeAnimation.removedOnCompletion = YES;
  269. [self.layer addAnimation:fadeAnimation forKey:@"fadeAnimation"];
  270. self.layer.opacity = 1.0;
  271. CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  272. scaleAnimation.duration = _animationDuration;
  273. scaleAnimation.fromValue = [NSNumber numberWithFloat:0.0];
  274. scaleAnimation.toValue = [NSNumber numberWithFloat:1.0];
  275. scaleAnimation.removedOnCompletion = YES;
  276. CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
  277. positionAnimation.duration = _animationDuration;
  278. if (_animationCentered)
  279. {
  280. positionAnimation.fromValue = [NSValue valueWithCGPoint:backgroundView.layer.position];
  281. }
  282. else
  283. {
  284. positionAnimation.fromValue = [NSValue valueWithCGPoint:_animationPoint];
  285. }
  286. positionAnimation.toValue = [NSValue valueWithCGPoint:backgroundView.layer.position];
  287. positionAnimation.removedOnCompletion = YES;
  288. CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
  289. animationGroup.animations = @[scaleAnimation, positionAnimation];
  290. animationGroup.duration = _animationDuration;
  291. animationGroup.removedOnCompletion = YES;
  292. [backgroundView.layer addAnimation:animationGroup forKey:nil];
  293. }
  294. - (void)hide:(BOOL)animated
  295. {
  296. [self unregisterFromNotificationCenter];
  297. onScreen = NO;
  298. CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
  299. fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
  300. fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
  301. fadeAnimation.removedOnCompletion = YES;
  302. [self.layer addAnimation:fadeAnimation forKey:@"fadeAnimation"];
  303. self.layer.opacity = 0.0;
  304. CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  305. scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
  306. scaleAnimation.toValue = [NSNumber numberWithFloat:0.0];
  307. scaleAnimation.removedOnCompletion = YES;
  308. CABasicAnimation *frameAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
  309. if (_animationCentered)
  310. {
  311. frameAnimation.toValue = [NSValue valueWithCGPoint:backgroundView.layer.position];
  312. }
  313. else
  314. {
  315. frameAnimation.toValue = [NSValue valueWithCGPoint:_animationPoint];
  316. }
  317. frameAnimation.removedOnCompletion = YES;
  318. if (!_animationCentered)
  319. {
  320. backgroundView.layer.position = _animationPoint;
  321. }
  322. backgroundView.layer.position = _animationPoint;
  323. CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
  324. animationGroup.animations = @[scaleAnimation, frameAnimation];
  325. animationGroup.duration = _animationDuration;
  326. animationGroup.removedOnCompletion = YES;
  327. [backgroundView.layer addAnimation:animationGroup forKey:nil];
  328. }
  329. - (void)dismiss:(BOOL)animated
  330. {
  331. [self hide:animated];
  332. //Removes the HUD from the superview, dismissing it.
  333. [self performSelector:@selector(removeFromSuperview) withObject:Nil afterDelay:_animationDuration];
  334. }
  335. #pragma mark - Notifications
  336. - (void)registerForNotificationCenter {
  337. NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
  338. [center addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
  339. }
  340. - (void)unregisterFromNotificationCenter {
  341. NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
  342. [center removeObserver:self];
  343. }
  344. - (void)deviceOrientationDidChange:(NSNotification *)notification {
  345. UIDeviceOrientation deviceOrientation = [notification.object orientation];
  346. if (_shouldAutorotate && UIDeviceOrientationIsValidInterfaceOrientation(deviceOrientation)) {
  347. if (UIDeviceOrientationIsPortrait(deviceOrientation)) {
  348. if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
  349. _orientation = UIInterfaceOrientationPortraitUpsideDown;
  350. } else {
  351. _orientation = UIInterfaceOrientationPortrait;
  352. }
  353. } else {
  354. if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
  355. _orientation = UIInterfaceOrientationLandscapeLeft;
  356. } else {
  357. _orientation = UIInterfaceOrientationLandscapeRight;
  358. }
  359. }
  360. [self layoutHUD];
  361. }
  362. }
  363. #pragma mark Layout
  364. - (void)willMoveToSuperview:(UIView *)newSuperview
  365. {
  366. self.frame = CGRectMake(0, 0, newSuperview.frame.size.width, newSuperview.frame.size.height);
  367. [self layoutSubviews];
  368. }
  369. - (void)layoutSubviews
  370. {
  371. [super layoutSubviews];
  372. [self layoutHUD];
  373. }
  374. - (void)layoutHUD
  375. {
  376. //Setup the background rect
  377. CGRect backgroundRect = CGRectZero;
  378. //Setup the label rect
  379. CGRect statusRect = [optimalStatusString boundingRectWithSize:[UIScreen mainScreen].bounds.size options:(NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName : statusLabel.font} context:nil];
  380. //Setup the progress rect
  381. CGRect progressRect = CGRectMake(0, 0, _progressViewSize.width, _progressViewSize.height);
  382. //Calculate the rects as per positioning
  383. if (optimalStatusString.length != 0 && optimalStatusString != nil) {
  384. if (_statusPosition == M13ProgressHUDStatusPositionBelowProgress) {
  385. //Calculate background height
  386. CGFloat backgroundRectBaseHeight = _progressViewSize.height + _contentMargin * 3;
  387. backgroundRect.size.height = backgroundRectBaseHeight + statusRect.size.height;
  388. if (backgroundRect.size.height < _minimumSize.height) {
  389. backgroundRect.size.height = _minimumSize.height;
  390. }
  391. //Calculate background width
  392. backgroundRect.size.width = (statusRect.size.width > _progressViewSize.width) ? statusRect.size.width : _progressViewSize.width;
  393. backgroundRect.size.width += 2 * _contentMargin;
  394. if (backgroundRect.size.width < _minimumSize.width) {
  395. backgroundRect.size.width = _minimumSize.width;
  396. }
  397. //Calculate background origin (Calculated to keep the progress bar in the same position on the screen during frame changes.)
  398. backgroundRect.origin.x = (self.bounds.size.width / 2.0) - (backgroundRect.size.width / 2.0);
  399. backgroundRect.origin.y = (self.bounds.size.height / 2.0) - (backgroundRectBaseHeight / 2.0);
  400. //Calculate the progress view rect
  401. progressRect.origin.x = (backgroundRect.size.width / 2.0) - (progressRect.size.width / 2.0);
  402. progressRect.origin.y = _contentMargin;
  403. //Calculate the label rect
  404. statusRect.origin.x = (backgroundRect.size.width / 2.0) - (statusRect.size.width / 2.0);
  405. statusRect.origin.y = progressRect.origin.y + progressRect.size.height + _contentMargin;
  406. } else if (_statusPosition == M13ProgressHUDStatusPositionAboveProgress) {
  407. //Calculate background height
  408. backgroundRect.size.height = _contentMargin + _progressViewSize.height + _contentMargin + statusRect.size.height + _contentMargin;
  409. if (backgroundRect.size.height < _minimumSize.height) {
  410. backgroundRect.size.height = _minimumSize.height;
  411. }
  412. //Calculate background width
  413. backgroundRect.size.width = (statusRect.size.width > _progressViewSize.width) ? statusRect.size.width : _progressViewSize.width;
  414. backgroundRect.size.width += 2 * _contentMargin;
  415. if (backgroundRect.size.width < _minimumSize.width) {
  416. backgroundRect.size.width = _minimumSize.width;
  417. }
  418. //Calculate background origin (Calculated to keep the progress bar in the same position on the screen during frame changes.)
  419. backgroundRect.origin.x = (self.bounds.size.width / 2.0) - (backgroundRect.size.width / 2.0);
  420. backgroundRect.origin.y = (self.bounds.size.height / 2.0) - (_minimumSize.height / 2.0);
  421. //Calculate the label rect
  422. statusRect.origin.x = (backgroundRect.size.width / 2.0) - (statusRect.size.width / 2.0);
  423. statusRect.origin.y = _contentMargin;
  424. //Calculate the progress view rect
  425. progressRect.origin.x = (backgroundRect.size.width / 2.0) - (progressRect.size.width / 2.0);
  426. progressRect.origin.y = statusRect.origin.y + statusRect.size.height + _contentMargin;
  427. } else if (_statusPosition == M13ProgressHUDStatusPositionLeftOfProgress) {
  428. //Calculate background height
  429. backgroundRect.size.height = (statusRect.size.height > progressRect.size.height) ? statusRect.size.height : progressRect.size.height;
  430. backgroundRect.size.height += 2 * _contentMargin;
  431. if (backgroundRect.size.height < _minimumSize.height) {
  432. backgroundRect.size.height = _minimumSize.height;
  433. }
  434. //Calculate background width
  435. backgroundRect.size.width = _contentMargin + statusRect.size.width + _contentMargin + progressRect.size.width + _contentMargin;
  436. if (backgroundRect.size.width < _minimumSize.width) {
  437. backgroundRect.size.width = _minimumSize.width;
  438. }
  439. //Calculate background origin (Calculated to keep the progress bar in the same position on the screen during frame changes.)
  440. backgroundRect.origin.x = (self.bounds.size.width / 2.0) - (backgroundRect.size.width / 2.0);
  441. backgroundRect.origin.y = (self.bounds.size.height / 2.0) - (_minimumSize.height / 2.0);
  442. //Calculate the label rect
  443. statusRect.origin.x = _contentMargin;
  444. statusRect.origin.y = (backgroundRect.size.height / 2.0) - (statusRect.size.height / 2.0);
  445. //Calculate the progress view rect
  446. progressRect.origin.x = statusRect.origin.x + statusRect.size.width + _contentMargin;
  447. progressRect.origin.y = (backgroundRect.size.height / 2.0) - (progressRect.size.height / 2.0);
  448. } else if (_statusPosition == M13ProgressHUDStatusPositionRightOfProgress) {
  449. //Calculate background height
  450. backgroundRect.size.height = (statusRect.size.height > progressRect.size.height) ? statusRect.size.height : progressRect.size.height;
  451. backgroundRect.size.height += 2 * _contentMargin;
  452. if (backgroundRect.size.height < _minimumSize.height) {
  453. backgroundRect.size.height = _minimumSize.height;
  454. }
  455. //Calculate background width
  456. backgroundRect.size.width = _contentMargin + statusRect.size.width + _contentMargin + progressRect.size.width + _contentMargin;
  457. if (backgroundRect.size.width < _minimumSize.width) {
  458. backgroundRect.size.width = _minimumSize.width;
  459. }
  460. //Calculate background origin (Calculated to keep the progress bar in the same position on the screen during frame changes.)
  461. backgroundRect.origin.x = (self.bounds.size.width / 2.0) - (backgroundRect.size.width / 2.0);
  462. backgroundRect.origin.y = (self.bounds.size.height / 2.0) - (_minimumSize.height / 2.0);
  463. //Calculate the progress view rect
  464. progressRect.origin.x = _contentMargin;
  465. progressRect.origin.y = (backgroundRect.size.height / 2.0) - (progressRect.size.height / 2.0);
  466. //Calculate the label rect
  467. statusRect.origin.x = progressRect.origin.x + progressRect.size.width + _contentMargin;
  468. statusRect.origin.y = (backgroundRect.size.height / 2.0) - (statusRect.size.height / 2.0);
  469. }
  470. } else {
  471. //Calculate background height
  472. backgroundRect.size.height = (statusRect.size.height > progressRect.size.height) ? statusRect.size.height : progressRect.size.height;
  473. backgroundRect.size.height += 2 * _contentMargin;
  474. if (backgroundRect.size.height < _minimumSize.height) {
  475. backgroundRect.size.height = _minimumSize.height;
  476. }
  477. //Calculate background width
  478. backgroundRect.size.width = (statusRect.size.width > _progressViewSize.width) ? statusRect.size.width : _progressViewSize.width;
  479. backgroundRect.size.width += 2 * _contentMargin;
  480. if (backgroundRect.size.width < _minimumSize.width) {
  481. backgroundRect.size.width = _minimumSize.width;
  482. }
  483. backgroundRect.origin.x = (self.bounds.size.width / 2.0) - (backgroundRect.size.width / 2.0);
  484. backgroundRect.origin.y = (self.bounds.size.height / 2.0) - (_minimumSize.height / 2.0);
  485. //There is no status label text, center the progress view
  486. progressRect.origin.x = (backgroundRect.size.width / 2.0) - (progressRect.size.width / 2.0);
  487. progressRect.origin.y = (backgroundRect.size.height / 2.0) - (progressRect.size.height / 2.0);
  488. statusRect.size.width = 0.0;
  489. statusRect.size.height = 0.0;
  490. }
  491. //Swap height and with on rotation
  492. if (_orientation == UIInterfaceOrientationLandscapeLeft || _orientation == UIInterfaceOrientationLandscapeRight) {
  493. //Flip the width and height.
  494. CGFloat temp = backgroundRect.size.width;
  495. backgroundRect.size.width = backgroundRect.size.height;
  496. backgroundRect.size.height = temp;
  497. }
  498. if (onScreen) {
  499. //Set the frame of the background and its subviews
  500. [UIView animateWithDuration:_animationDuration animations:^{
  501. self->backgroundView.frame = CGRectIntegral(backgroundRect);
  502. self.progressView.frame = CGRectIntegral(progressRect);
  503. self->backgroundView.transform = CGAffineTransformMakeRotation([self angleForDeviceOrientation]);
  504. //Fade the label
  505. self->statusLabel.alpha = 0.0;
  506. } completion:^(BOOL finished) {
  507. if (finished) {
  508. //Set the label frame
  509. self->statusLabel.frame = CGRectIntegral(statusRect);
  510. self->statusLabel.text = self->optimalStatusString;
  511. [UIView animateWithDuration:self.animationDuration animations:^{
  512. //Show the label
  513. self->statusLabel.alpha = 1.0;
  514. }];
  515. }
  516. }];
  517. } else {
  518. backgroundView.frame = CGRectIntegral(backgroundRect);
  519. _progressView.frame = CGRectIntegral(progressRect);
  520. backgroundView.transform = CGAffineTransformMakeRotation([self angleForDeviceOrientation]);
  521. //Fade the label
  522. statusLabel.alpha = 0.0;
  523. }
  524. }
  525. - (void)recalculateOptimalStatusStringStructure
  526. {
  527. if ([_status rangeOfString:@" "].location == NSNotFound || [_status rangeOfString:@"\n"].location != NSNotFound) {
  528. //One word, just pass the string as is.
  529. //Or has line breaks, so follow them.
  530. optimalStatusString = [_status copy];
  531. } else if ([_status rangeOfString:@" "].location != NSNotFound && [_status rangeOfString:@"\n"].location == NSNotFound) {
  532. //There are spaces, but no line breaks. Insert line breaks as needed.
  533. //Break the status into indivual words
  534. NSArray *wordsArray = [_status componentsSeparatedByString:@" "];
  535. //Calculate the mean width and standard deviation to use as parameters for where line breaks should go.
  536. float meanWidth = 0.0;
  537. float standardDeviation = 0.0;
  538. NSMutableArray *sizesArray = [NSMutableArray array];
  539. //Calculate size of each word
  540. for (NSString *word in wordsArray) {
  541. CGRect wordRect = [word boundingRectWithSize:[UIScreen mainScreen].bounds.size options:(NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName : statusLabel.font} context:nil];
  542. [sizesArray addObject:NSStringFromCGRect(wordRect)];
  543. //Sum the widths to calculate the mean width
  544. meanWidth += wordRect.size.width;
  545. }
  546. //Finish the mean size and standard deviation calculations
  547. meanWidth = roundf(meanWidth / wordsArray.count);
  548. //Calculate the standard deviation
  549. for (NSString *rect in sizesArray) {
  550. CGRect theRect = CGRectFromString(rect);
  551. //Sum the widths to calculate the mean width
  552. standardDeviation += exp2f((float)theRect.size.width - meanWidth);
  553. }
  554. standardDeviation = sqrtf(standardDeviation / wordsArray.count);
  555. //Correct the mean width if it is below the minimum size
  556. if (meanWidth < self.minimumSize.width) {
  557. meanWidth = (float)self.minimumSize.width;
  558. }
  559. //Now calculate where to put line breaks. Lines can exceed the minimum width, but cannot exceed the minimum width plus the standard deviation. Single words can excced these limits.
  560. NSMutableString *correctedString = [[NSMutableString alloc] initWithString:wordsArray[0]];
  561. float lineSize = (float)CGRectFromString(sizesArray[0]).size.width;
  562. for (int i = 1; i < wordsArray.count; i++) {
  563. NSString *word = wordsArray[i];
  564. CGRect wordRect = CGRectFromString(sizesArray[i]);
  565. if (lineSize + wordRect.size.width > meanWidth + standardDeviation) {
  566. //If the max width is exceeded, add a new line
  567. [correctedString appendFormat:@"\n"];
  568. } else {
  569. //append a space before the new word
  570. [correctedString appendFormat:@" "];
  571. }
  572. //Append the string
  573. [correctedString appendString:word];
  574. }
  575. //Set the optimal string
  576. optimalStatusString = correctedString;
  577. }
  578. }
  579. #pragma mark Drawing
  580. - (void)drawRect:(CGRect)rect
  581. {
  582. [super drawRect:rect];
  583. [self drawMask];
  584. [self drawBackground];
  585. }
  586. - (void)drawBackground
  587. {
  588. //Set the proper color
  589. if (_applyBlurToBackground == NO) {
  590. backgroundView.backgroundColor = _hudBackgroundColor;
  591. } else {
  592. //Redraw the hud blur
  593. [self redrawBlurs];
  594. }
  595. }
  596. - (void)drawMask
  597. {
  598. maskView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
  599. if (_maskType == M13ProgressHUDMaskTypeNone) {
  600. maskView.backgroundColor = [UIColor clearColor];
  601. } else if (_maskType == M13ProgressHUDMaskTypeSolidColor) {
  602. maskView.backgroundColor = _maskColor;
  603. } else if (_maskType == M13ProgressHUDMaskTypeGradient) {
  604. //Get the components of color of the maskColor
  605. CGFloat red;
  606. CGFloat green;
  607. CGFloat blue;
  608. CGFloat alpha;
  609. [_maskColor getRed:&red green:&green blue:&blue alpha:&alpha];
  610. //Create the gradient as an image, and then set it as the color of the mask view.
  611. UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
  612. CGContextRef context = UIGraphicsGetCurrentContext();
  613. if (!context) {
  614. return;
  615. }
  616. //Create the gradient
  617. size_t locationsCount = 2;
  618. CGFloat locations[2] = {0.0f, 1.0f};
  619. CGFloat colors[8] = {0.0f, 0.0f, 0.0f, 0.0f, red, green, blue, alpha};
  620. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  621. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount);
  622. CGColorSpaceRelease(colorSpace);
  623. //Draw the gradient
  624. CGPoint center = CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);
  625. float radius = (float)MIN(self.bounds.size.width , self.bounds.size.height) ;
  626. CGContextDrawRadialGradient (context, gradient, center, 0, center, radius, kCGGradientDrawsAfterEndLocation);
  627. CGGradientRelease(gradient);
  628. //Get the gradient image
  629. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  630. UIGraphicsEndImageContext();
  631. //Set the background
  632. maskView.backgroundColor = [UIColor colorWithPatternImage:image];
  633. } else if (_maskType == M13ProgressHUDMaskTypeIOS7Blur) {
  634. // do nothing; we don't want to take a snapshot of the background for blurring now, no idea what the background is
  635. }
  636. }
  637. - (void)redrawBlurs
  638. {
  639. if (_maskType == M13ProgressHUDMaskTypeIOS7Blur) {
  640. //Get the snapshot of the mask
  641. __block UIImage *image = [self snapshotForBlurredBackgroundInView:maskView];
  642. if (image != nil) {
  643. //Apply the filters to blur the image
  644. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  645. image = [image applyLightEffect];
  646. dispatch_async(dispatch_get_main_queue(), ^{
  647. // Fade on content's change, if there was already an image.
  648. CATransition *transition = [CATransition new];
  649. transition.duration = 0.3;
  650. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  651. transition.type = kCATransitionFade;
  652. [self->maskView.layer addAnimation:transition forKey:nil];
  653. self->maskView.backgroundColor = [UIColor colorWithPatternImage:image];
  654. });
  655. });
  656. }
  657. }
  658. if (_applyBlurToBackground) {
  659. //Get the snapshot of the mask
  660. __block UIImage *image = [self snapshotForBlurredBackgroundInView:backgroundView];
  661. if (image != nil) {
  662. //Apply the filters to blur the image
  663. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  664. //image = [image applyLightEffect];
  665. image = [image applyLightEffect];
  666. dispatch_async(dispatch_get_main_queue(), ^{
  667. // Fade on content's change, if there was already an image.
  668. CATransition *transition = [CATransition new];
  669. transition.duration = 0.3;
  670. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  671. transition.type = kCATransitionFade;
  672. [self->backgroundView.layer addAnimation:transition forKey:nil];
  673. self->backgroundView.backgroundColor = [UIColor colorWithPatternImage:image];
  674. });
  675. });
  676. }
  677. }
  678. }
  679. - (UIImage *)snapshotForBlurredBackgroundInView:(UIView *)view
  680. {
  681. //Translate the view's rect to the superview's rect
  682. CGRect viewRect = view.bounds;
  683. viewRect = [view convertRect:viewRect toView:self.superview];
  684. //Hide self if visible
  685. BOOL previousViewState = self.hidden;
  686. self.hidden = YES;
  687. //Create a snapshot of the superview
  688. UIView *snapshotView = [self.superview resizableSnapshotViewFromRect:viewRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
  689. //Draw the snapshot view into a UIImage
  690. UIGraphicsBeginImageContextWithOptions(snapshotView.bounds.size, YES, [UIScreen mainScreen].scale);
  691. CGContextRef context = UIGraphicsGetCurrentContext();
  692. if (!context) {
  693. return nil;
  694. }
  695. CGContextTranslateCTM(context, viewRect.origin.x, viewRect.origin.y);
  696. BOOL result = [self.superview drawViewHierarchyInRect:viewRect afterScreenUpdates:YES];
  697. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  698. UIGraphicsEndImageContext();
  699. //Return self to the previous state
  700. self.hidden = previousViewState;
  701. if (result) {
  702. return image;
  703. } else {
  704. return nil;
  705. }
  706. }
  707. - (CGFloat)angleForDeviceOrientation
  708. {
  709. if (_orientation == UIInterfaceOrientationLandscapeLeft) {
  710. return M_PI_2;
  711. } else if (_orientation == UIInterfaceOrientationLandscapeRight) {
  712. return -M_PI_2;
  713. } else if (_orientation == UIInterfaceOrientationPortraitUpsideDown) {
  714. return M_PI;
  715. }
  716. return 0;
  717. }
  718. @end
  719. @implementation UIView (M13ProgressHUD)
  720. - (M13ProgressHUD *)progressHUD
  721. {
  722. for (id view in self.subviews) {
  723. //If the subview is a progress HUD return it.
  724. if ([[view class] isSubclassOfClass:[M13ProgressHUD class]]) {
  725. return view;
  726. }
  727. }
  728. return nil;
  729. }
  730. @end