SVProgressHUD.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. //
  2. // SVProgressHUD.m
  3. //
  4. // Created by Sam Vermette on 27.03.11.
  5. // Copyright 2011 Sam Vermette. All rights reserved.
  6. //
  7. // https://github.com/samvermette/SVProgressHUD
  8. //
  9. #import "SVProgressHUD.h"
  10. #import <QuartzCore/QuartzCore.h>
  11. @interface SVProgressHUD ()
  12. @property (nonatomic, readwrite) SVProgressHUDMaskType maskType;
  13. @property (nonatomic, strong, readonly) NSTimer *fadeOutTimer;
  14. @property (nonatomic, strong, readonly) UIWindow *overlayWindow;
  15. @property (nonatomic, strong, readonly) UIView *hudView;
  16. @property (nonatomic, strong, readonly) UILabel *stringLabel;
  17. @property (nonatomic, strong, readonly) UIImageView *imageView;
  18. @property (nonatomic, strong, readonly) UIActivityIndicatorView *spinnerView;
  19. @property (nonatomic, readonly) CGFloat visibleKeyboardHeight;
  20. - (void)showWithStatus:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType networkIndicator:(BOOL)show;
  21. - (void)setStatus:(NSString*)string;
  22. - (void)registerNotifications;
  23. - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle;
  24. - (void)positionHUD:(NSNotification*)notification;
  25. - (void)dismiss;
  26. - (void)dismissWithStatus:(NSString*)string error:(BOOL)error;
  27. - (void)dismissWithStatus:(NSString*)string error:(BOOL)error afterDelay:(NSTimeInterval)seconds;
  28. @end
  29. @implementation SVProgressHUD
  30. @synthesize overlayWindow, hudView, maskType, fadeOutTimer, stringLabel, imageView, spinnerView, visibleKeyboardHeight;
  31. - (void)dealloc {
  32. self.fadeOutTimer = nil;
  33. [[NSNotificationCenter defaultCenter] removeObserver:self];
  34. }
  35. + (SVProgressHUD*)sharedView {
  36. static dispatch_once_t once;
  37. static SVProgressHUD *sharedView;
  38. dispatch_once(&once, ^ { sharedView = [[SVProgressHUD alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; });
  39. return sharedView;
  40. }
  41. + (void)setStatus:(NSString *)string {
  42. [[SVProgressHUD sharedView] setStatus:string];
  43. }
  44. #pragma mark - Show Methods
  45. + (void)show {
  46. [[SVProgressHUD sharedView] showWithStatus:nil maskType:SVProgressHUDMaskTypeNone networkIndicator:NO];
  47. }
  48. + (void)showWithStatus:(NSString *)status {
  49. [[SVProgressHUD sharedView] showWithStatus:status maskType:SVProgressHUDMaskTypeNone networkIndicator:NO];
  50. }
  51. + (void)showWithMaskType:(SVProgressHUDMaskType)maskType {
  52. [[SVProgressHUD sharedView] showWithStatus:nil maskType:maskType networkIndicator:NO];
  53. }
  54. + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  55. [[SVProgressHUD sharedView] showWithStatus:status maskType:maskType networkIndicator:NO];
  56. }
  57. + (void)showSuccessWithStatus:(NSString *)string {
  58. [SVProgressHUD showSuccessWithStatus:string duration:1];
  59. }
  60. + (void)showSuccessWithStatus:(NSString *)string duration:(NSTimeInterval)duration {
  61. [SVProgressHUD show];
  62. [SVProgressHUD dismissWithSuccess:string afterDelay:duration];
  63. }
  64. + (void)showErrorWithStatus:(NSString *)string {
  65. [SVProgressHUD showErrorWithStatus:string duration:1];
  66. }
  67. + (void)showErrorWithStatus:(NSString *)string duration:(NSTimeInterval)duration {
  68. [SVProgressHUD show];
  69. [SVProgressHUD dismissWithError:string afterDelay:duration];
  70. }
  71. #pragma mark - Dismiss Methods
  72. + (void)dismiss {
  73. [[SVProgressHUD sharedView] dismiss];
  74. }
  75. + (void)dismissWithSuccess:(NSString*)successString {
  76. [[SVProgressHUD sharedView] dismissWithStatus:successString error:NO];
  77. }
  78. + (void)dismissWithSuccess:(NSString *)successString afterDelay:(NSTimeInterval)seconds {
  79. [[SVProgressHUD sharedView] dismissWithStatus:successString error:NO afterDelay:seconds];
  80. }
  81. + (void)dismissWithError:(NSString*)errorString {
  82. [[SVProgressHUD sharedView] dismissWithStatus:errorString error:YES];
  83. }
  84. + (void)dismissWithError:(NSString *)errorString afterDelay:(NSTimeInterval)seconds {
  85. [[SVProgressHUD sharedView] dismissWithStatus:errorString error:YES afterDelay:seconds];
  86. }
  87. #pragma mark - Instance Methods
  88. - (id)initWithFrame:(CGRect)frame {
  89. if ((self = [super initWithFrame:frame])) {
  90. self.userInteractionEnabled = NO;
  91. self.backgroundColor = [UIColor clearColor];
  92. self.alpha = 0;
  93. self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  94. }
  95. return self;
  96. }
  97. - (void)drawRect:(CGRect)rect {
  98. CGContextRef context = UIGraphicsGetCurrentContext();
  99. switch (self.maskType) {
  100. case SVProgressHUDMaskTypeBlack: {
  101. [[UIColor colorWithWhite:0 alpha:0.5] set];
  102. CGContextFillRect(context, self.bounds);
  103. break;
  104. }
  105. case SVProgressHUDMaskTypeGradient: {
  106. size_t locationsCount = 2;
  107. CGFloat locations[2] = {0.0f, 1.0f};
  108. CGFloat colors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f};
  109. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  110. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount);
  111. CGColorSpaceRelease(colorSpace);
  112. CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
  113. float radius = MIN(self.bounds.size.width , self.bounds.size.height) ;
  114. CGContextDrawRadialGradient (context, gradient, center, 0, center, radius, kCGGradientDrawsAfterEndLocation);
  115. CGGradientRelease(gradient);
  116. break;
  117. }
  118. }
  119. }
  120. - (void)setStatus:(NSString *)string {
  121. CGFloat hudWidth = 100;
  122. CGFloat hudHeight = 100;
  123. CGFloat stringWidth = 0;
  124. CGFloat stringHeight = 0;
  125. CGRect labelRect = CGRectZero;
  126. if(string) {
  127. CGSize stringSize = [string sizeWithFont:self.stringLabel.font constrainedToSize:CGSizeMake(200, 300)];
  128. stringWidth = stringSize.width;
  129. stringHeight = stringSize.height;
  130. hudHeight = 80+stringHeight;
  131. if(stringWidth > hudWidth)
  132. hudWidth = ceil(stringWidth/2)*2;
  133. if(hudHeight > 100) {
  134. labelRect = CGRectMake(12, 66, hudWidth, stringHeight);
  135. hudWidth+=24;
  136. } else {
  137. hudWidth+=24;
  138. labelRect = CGRectMake(0, 66, hudWidth, stringHeight);
  139. }
  140. }
  141. self.hudView.bounds = CGRectMake(0, 0, hudWidth, hudHeight);
  142. if(string)
  143. self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, 36);
  144. else
  145. self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, CGRectGetHeight(self.hudView.bounds)/2);
  146. self.stringLabel.hidden = NO;
  147. self.stringLabel.text = string;
  148. self.stringLabel.frame = labelRect;
  149. if(string)
  150. self.spinnerView.center = CGPointMake(ceil(CGRectGetWidth(self.hudView.bounds)/2)+0.5, 40.5);
  151. else
  152. self.spinnerView.center = CGPointMake(ceil(CGRectGetWidth(self.hudView.bounds)/2)+0.5, ceil(self.hudView.bounds.size.height/2)+0.5);
  153. }
  154. - (void)setFadeOutTimer:(NSTimer *)newTimer {
  155. if(fadeOutTimer)
  156. [fadeOutTimer invalidate], fadeOutTimer = nil;
  157. if(newTimer)
  158. fadeOutTimer = newTimer;
  159. }
  160. - (void)registerNotifications {
  161. [[NSNotificationCenter defaultCenter] addObserver:self
  162. selector:@selector(positionHUD:)
  163. name:UIApplicationDidChangeStatusBarOrientationNotification
  164. object:nil];
  165. [[NSNotificationCenter defaultCenter] addObserver:self
  166. selector:@selector(positionHUD:)
  167. name:UIKeyboardWillHideNotification
  168. object:nil];
  169. [[NSNotificationCenter defaultCenter] addObserver:self
  170. selector:@selector(positionHUD:)
  171. name:UIKeyboardDidHideNotification
  172. object:nil];
  173. [[NSNotificationCenter defaultCenter] addObserver:self
  174. selector:@selector(positionHUD:)
  175. name:UIKeyboardWillShowNotification
  176. object:nil];
  177. [[NSNotificationCenter defaultCenter] addObserver:self
  178. selector:@selector(positionHUD:)
  179. name:UIKeyboardDidShowNotification
  180. object:nil];
  181. }
  182. - (void)positionHUD:(NSNotification*)notification {
  183. CGFloat keyboardHeight;
  184. double animationDuration = 0.0;
  185. UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
  186. if(notification) {
  187. NSDictionary* keyboardInfo = [notification userInfo];
  188. CGRect keyboardFrame = [[keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
  189. animationDuration = [[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  190. if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) {
  191. if(UIInterfaceOrientationIsPortrait(orientation))
  192. keyboardHeight = keyboardFrame.size.height;
  193. else
  194. keyboardHeight = keyboardFrame.size.width;
  195. } else
  196. keyboardHeight = 0;
  197. } else {
  198. keyboardHeight = self.visibleKeyboardHeight;
  199. }
  200. CGRect orientationFrame = [UIScreen mainScreen].bounds;
  201. CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
  202. if(UIInterfaceOrientationIsLandscape(orientation)) {
  203. float temp = orientationFrame.size.width;
  204. orientationFrame.size.width = orientationFrame.size.height;
  205. orientationFrame.size.height = temp;
  206. temp = statusBarFrame.size.width;
  207. statusBarFrame.size.width = statusBarFrame.size.height;
  208. statusBarFrame.size.height = temp;
  209. }
  210. CGFloat activeHeight = orientationFrame.size.height;
  211. if(keyboardHeight > 0)
  212. activeHeight += statusBarFrame.size.height*2;
  213. activeHeight -= keyboardHeight;
  214. CGFloat posY = floor(activeHeight*0.45);
  215. CGFloat posX = orientationFrame.size.width/2;
  216. CGPoint newCenter;
  217. CGFloat rotateAngle;
  218. switch (orientation) {
  219. case UIInterfaceOrientationPortraitUpsideDown:
  220. rotateAngle = M_PI;
  221. newCenter = CGPointMake(posX, orientationFrame.size.height-posY);
  222. break;
  223. case UIInterfaceOrientationLandscapeLeft:
  224. rotateAngle = -M_PI/2.0f;
  225. newCenter = CGPointMake(posY, posX);
  226. break;
  227. case UIInterfaceOrientationLandscapeRight:
  228. rotateAngle = M_PI/2.0f;
  229. newCenter = CGPointMake(orientationFrame.size.height-posY, posX);
  230. break;
  231. default: // as UIInterfaceOrientationPortrait
  232. rotateAngle = 0.0;
  233. newCenter = CGPointMake(posX, posY);
  234. break;
  235. }
  236. rotateAngle = 0.0;
  237. newCenter = CGPointMake(posX, posY);
  238. if(notification) {
  239. [UIView animateWithDuration:animationDuration
  240. delay:0
  241. options:UIViewAnimationOptionAllowUserInteraction
  242. animations:^{
  243. [self moveToPoint:newCenter rotateAngle:rotateAngle];
  244. } completion:NULL];
  245. }
  246. else {
  247. [self moveToPoint:newCenter rotateAngle:rotateAngle];
  248. }
  249. }
  250. - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle {
  251. self.hudView.transform = CGAffineTransformMakeRotation(angle);
  252. self.hudView.center = newCenter;
  253. }
  254. #pragma mark - Master show/dismiss methods
  255. - (void)showWithStatus:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType networkIndicator:(BOOL)show {
  256. dispatch_async(dispatch_get_main_queue(), ^{
  257. if(!self.superview)
  258. [self.overlayWindow addSubview:self];
  259. self.fadeOutTimer = nil;
  260. self.imageView.hidden = YES;
  261. self.maskType = hudMaskType;
  262. [self setStatus:string];
  263. [self.spinnerView startAnimating];
  264. if(self.maskType != SVProgressHUDMaskTypeNone) {
  265. self.overlayWindow.userInteractionEnabled = YES;
  266. } else {
  267. self.overlayWindow.userInteractionEnabled = NO;
  268. }
  269. [self.overlayWindow makeKeyAndVisible];
  270. [self positionHUD:nil];
  271. if(self.alpha != 1) {
  272. [self registerNotifications];
  273. self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
  274. [UIView animateWithDuration:0.15
  275. delay:0
  276. options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState
  277. animations:^{
  278. self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3, 1/1.3);
  279. self.alpha = 1;
  280. }
  281. completion:NULL];
  282. }
  283. [self setNeedsDisplay];
  284. });
  285. }
  286. - (void)dismissWithStatus:(NSString *)string error:(BOOL)error {
  287. [self dismissWithStatus:string error:error afterDelay:0.9];
  288. }
  289. - (void)dismissWithStatus:(NSString *)string error:(BOOL)error afterDelay:(NSTimeInterval)seconds {
  290. dispatch_async(dispatch_get_main_queue(), ^{
  291. if(self.alpha != 1)
  292. return;
  293. if(error)
  294. self.imageView.image = [UIImage imageNamed:@"SVProgressHUD.bundle/error.png"];
  295. else
  296. self.imageView.image = [UIImage imageNamed:@"SVProgressHUD.bundle/success.png"];
  297. self.imageView.hidden = NO;
  298. [self setStatus:string];
  299. [self.spinnerView stopAnimating];
  300. self.fadeOutTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
  301. });
  302. }
  303. - (void)dismiss {
  304. dispatch_async(dispatch_get_main_queue(), ^{
  305. [UIView animateWithDuration:0.15
  306. delay:0
  307. options:UIViewAnimationCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
  308. animations:^{
  309. self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 0.8, 0.8);
  310. self.alpha = 0;
  311. }
  312. completion:^(BOOL finished){
  313. if(self.alpha == 0) {
  314. [[NSNotificationCenter defaultCenter] removeObserver:self];
  315. [hudView removeFromSuperview];
  316. hudView = nil;
  317. // Make sure to remove the overlay window from the list of windows
  318. // before trying to find the key window in that same list
  319. NSMutableArray *windows = [[NSMutableArray alloc] initWithArray:[UIApplication sharedApplication].windows];
  320. [windows removeObject:overlayWindow];
  321. overlayWindow = nil;
  322. [windows enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(UIWindow *window, NSUInteger idx, BOOL *stop) {
  323. if([window isKindOfClass:[UIWindow class]] && window.windowLevel == UIWindowLevelNormal) {
  324. [window makeKeyWindow];
  325. *stop = YES;
  326. }
  327. }];
  328. }
  329. }];
  330. });
  331. }
  332. #pragma mark - Utilities
  333. + (BOOL)isVisible {
  334. return ([SVProgressHUD sharedView].alpha == 1);
  335. }
  336. #pragma mark - Getters
  337. - (UIWindow *)overlayWindow {
  338. if(!overlayWindow) {
  339. overlayWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  340. overlayWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  341. overlayWindow.backgroundColor = [UIColor clearColor];
  342. overlayWindow.userInteractionEnabled = NO;
  343. }
  344. return overlayWindow;
  345. }
  346. - (UIView *)hudView {
  347. if(!hudView) {
  348. hudView = [[UIView alloc] initWithFrame:CGRectZero];
  349. hudView.layer.cornerRadius = 10;
  350. hudView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.8];
  351. hudView.autoresizingMask = (UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin |
  352. UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin);
  353. [self addSubview:hudView];
  354. }
  355. return hudView;
  356. }
  357. - (UILabel *)stringLabel {
  358. if (stringLabel == nil) {
  359. stringLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  360. stringLabel.textColor = [UIColor whiteColor];
  361. stringLabel.backgroundColor = [UIColor clearColor];
  362. stringLabel.adjustsFontSizeToFitWidth = YES;
  363. stringLabel.textAlignment = NSTextAlignmentCenter;
  364. stringLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
  365. stringLabel.font = [UIFont boldSystemFontOfSize:16];
  366. stringLabel.shadowColor = [UIColor blackColor];
  367. stringLabel.shadowOffset = CGSizeMake(0, -1);
  368. stringLabel.numberOfLines = 0;
  369. }
  370. if(!stringLabel.superview)
  371. [self.hudView addSubview:stringLabel];
  372. return stringLabel;
  373. }
  374. - (UIImageView *)imageView {
  375. if (imageView == nil)
  376. imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 28, 28)];
  377. if(!imageView.superview)
  378. [self.hudView addSubview:imageView];
  379. return imageView;
  380. }
  381. - (UIActivityIndicatorView *)spinnerView {
  382. if (spinnerView == nil) {
  383. spinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  384. spinnerView.hidesWhenStopped = YES;
  385. spinnerView.bounds = CGRectMake(0, 0, 37, 37);
  386. }
  387. if(!spinnerView.superview)
  388. [self.hudView addSubview:spinnerView];
  389. return spinnerView;
  390. }
  391. - (CGFloat)visibleKeyboardHeight {
  392. UIWindow *keyboardWindow = nil;
  393. for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
  394. if(![[testWindow class] isEqual:[UIWindow class]]) {
  395. keyboardWindow = testWindow;
  396. break;
  397. }
  398. }
  399. // Locate UIKeyboard.
  400. UIView *foundKeyboard = nil;
  401. for (__strong UIView *possibleKeyboard in [keyboardWindow subviews]) {
  402. // iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView.
  403. if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"]) {
  404. possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];
  405. }
  406. if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"]) {
  407. foundKeyboard = possibleKeyboard;
  408. break;
  409. }
  410. }
  411. if(foundKeyboard && foundKeyboard.bounds.size.height > 100)
  412. return foundKeyboard.bounds.size.height;
  413. return 0;
  414. }
  415. @end