SVProgressHUD.m 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. //
  2. // SVProgressHUD.h
  3. // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
  4. //
  5. // Copyright (c) 2011-2018 Sam Vermette and contributors. All rights reserved.
  6. //
  7. #if !__has_feature(objc_arc)
  8. #error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
  9. #endif
  10. #import "SVProgressHUD.h"
  11. #import "SVIndefiniteAnimatedView.h"
  12. #import "SVProgressAnimatedView.h"
  13. #import "SVRadialGradientLayer.h"
  14. NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification";
  15. NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification";
  16. NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification";
  17. NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification";
  18. NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification";
  19. NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification";
  20. NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey";
  21. static const CGFloat SVProgressHUDParallaxDepthPoints = 10.0f;
  22. static const CGFloat SVProgressHUDUndefinedProgress = -1;
  23. static const CGFloat SVProgressHUDDefaultAnimationDuration = 0.15f;
  24. static const CGFloat SVProgressHUDVerticalSpacing = 12.0f;
  25. static const CGFloat SVProgressHUDHorizontalSpacing = 12.0f;
  26. static const CGFloat SVProgressHUDLabelSpacing = 8.0f;
  27. @interface SVProgressHUD ()
  28. @property (nonatomic, strong) NSTimer *graceTimer;
  29. @property (nonatomic, strong) NSTimer *fadeOutTimer;
  30. @property (nonatomic, strong) UIControl *controlView;
  31. @property (nonatomic, strong) UIView *backgroundView;
  32. @property (nonatomic, strong) SVRadialGradientLayer *backgroundRadialGradientLayer;
  33. @property (nonatomic, strong) UIVisualEffectView *hudView;
  34. @property (nonatomic, strong) UILabel *statusLabel;
  35. @property (nonatomic, strong) UIImageView *imageView;
  36. @property (nonatomic, strong) UIView *indefiniteAnimatedView;
  37. @property (nonatomic, strong) SVProgressAnimatedView *ringView;
  38. @property (nonatomic, strong) SVProgressAnimatedView *backgroundRingView;
  39. @property (nonatomic, readwrite) CGFloat progress;
  40. @property (nonatomic, readwrite) NSUInteger activityCount;
  41. @property (nonatomic, readonly) CGFloat visibleKeyboardHeight;
  42. @property (nonatomic, readonly) UIWindow *frontWindow;
  43. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  44. @property (nonatomic, strong) UINotificationFeedbackGenerator *hapticGenerator NS_AVAILABLE_IOS(10_0);
  45. #endif
  46. @end
  47. @implementation SVProgressHUD {
  48. BOOL _isInitializing;
  49. }
  50. + (SVProgressHUD*)sharedView {
  51. static dispatch_once_t once;
  52. static SVProgressHUD *sharedView;
  53. #if !defined(SV_APP_EXTENSIONS)
  54. dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; });
  55. #else
  56. dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; });
  57. #endif
  58. return sharedView;
  59. }
  60. #pragma mark - Setters
  61. + (void)setStatus:(NSString*)status {
  62. [[self sharedView] setStatus:status];
  63. }
  64. + (void)setDefaultStyle:(SVProgressHUDStyle)style {
  65. [self sharedView].defaultStyle = style;
  66. }
  67. + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType {
  68. [self sharedView].defaultMaskType = maskType;
  69. }
  70. + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type {
  71. [self sharedView].defaultAnimationType = type;
  72. }
  73. + (void)setContainerView:(nullable UIView*)containerView {
  74. [self sharedView].containerView = containerView;
  75. }
  76. + (void)setMinimumSize:(CGSize)minimumSize {
  77. [self sharedView].minimumSize = minimumSize;
  78. }
  79. + (void)setRingThickness:(CGFloat)ringThickness {
  80. [self sharedView].ringThickness = ringThickness;
  81. }
  82. + (void)setRingRadius:(CGFloat)radius {
  83. [self sharedView].ringRadius = radius;
  84. }
  85. + (void)setRingNoTextRadius:(CGFloat)radius {
  86. [self sharedView].ringNoTextRadius = radius;
  87. }
  88. + (void)setCornerRadius:(CGFloat)cornerRadius {
  89. [self sharedView].cornerRadius = cornerRadius;
  90. }
  91. + (void)setBorderColor:(nonnull UIColor*)color {
  92. [self sharedView].hudView.layer.borderColor = color.CGColor;
  93. }
  94. + (void)setBorderWidth:(CGFloat)width {
  95. [self sharedView].hudView.layer.borderWidth = width;
  96. }
  97. + (void)setFont:(UIFont*)font {
  98. [self sharedView].font = font;
  99. }
  100. + (void)setForegroundColor:(UIColor*)color {
  101. [self sharedView].foregroundColor = color;
  102. [self setDefaultStyle:SVProgressHUDStyleCustom];
  103. }
  104. + (void)setBackgroundColor:(UIColor*)color {
  105. [self sharedView].backgroundColor = color;
  106. [self setDefaultStyle:SVProgressHUDStyleCustom];
  107. }
  108. + (void)setBackgroundLayerColor:(UIColor*)color {
  109. [self sharedView].backgroundLayerColor = color;
  110. }
  111. + (void)setImageViewSize:(CGSize)size {
  112. [self sharedView].imageViewSize = size;
  113. }
  114. + (void)setShouldTintImages:(BOOL)shouldTintImages {
  115. [self sharedView].shouldTintImages = shouldTintImages;
  116. }
  117. + (void)setInfoImage:(UIImage*)image {
  118. [self sharedView].infoImage = image;
  119. }
  120. + (void)setSuccessImage:(UIImage*)image {
  121. [self sharedView].successImage = image;
  122. }
  123. + (void)setErrorImage:(UIImage*)image {
  124. [self sharedView].errorImage = image;
  125. }
  126. + (void)setViewForExtension:(UIView*)view {
  127. [self sharedView].viewForExtension = view;
  128. }
  129. + (void)setGraceTimeInterval:(NSTimeInterval)interval {
  130. [self sharedView].graceTimeInterval = interval;
  131. }
  132. + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval {
  133. [self sharedView].minimumDismissTimeInterval = interval;
  134. }
  135. + (void)setMaximumDismissTimeInterval:(NSTimeInterval)interval {
  136. [self sharedView].maximumDismissTimeInterval = interval;
  137. }
  138. + (void)setFadeInAnimationDuration:(NSTimeInterval)duration {
  139. [self sharedView].fadeInAnimationDuration = duration;
  140. }
  141. + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration {
  142. [self sharedView].fadeOutAnimationDuration = duration;
  143. }
  144. + (void)setMaxSupportedWindowLevel:(UIWindowLevel)windowLevel {
  145. [self sharedView].maxSupportedWindowLevel = windowLevel;
  146. }
  147. + (void)setHapticsEnabled:(BOOL)hapticsEnabled {
  148. [self sharedView].hapticsEnabled = hapticsEnabled;
  149. }
  150. #pragma mark - Show Methods
  151. + (void)show {
  152. [self showWithStatus:nil];
  153. }
  154. + (void)showWithMaskType:(SVProgressHUDMaskType)maskType {
  155. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  156. [self setDefaultMaskType:maskType];
  157. [self show];
  158. [self setDefaultMaskType:existingMaskType];
  159. }
  160. + (void)showWithStatus:(NSString*)status {
  161. [self showProgress:SVProgressHUDUndefinedProgress status:status];
  162. }
  163. + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  164. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  165. [self setDefaultMaskType:maskType];
  166. [self showWithStatus:status];
  167. [self setDefaultMaskType:existingMaskType];
  168. }
  169. + (void)showProgress:(float)progress {
  170. [self showProgress:progress status:nil];
  171. }
  172. + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType {
  173. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  174. [self setDefaultMaskType:maskType];
  175. [self showProgress:progress];
  176. [self setDefaultMaskType:existingMaskType];
  177. }
  178. + (void)showProgress:(float)progress status:(NSString*)status {
  179. [[self sharedView] showProgress:progress status:status];
  180. }
  181. + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  182. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  183. [self setDefaultMaskType:maskType];
  184. [self showProgress:progress status:status];
  185. [self setDefaultMaskType:existingMaskType];
  186. }
  187. #pragma mark - Show, then automatically dismiss methods
  188. + (void)showInfoWithStatus:(NSString*)status {
  189. [self showImage:[self sharedView].infoImage status:status];
  190. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  191. if (@available(iOS 10.0, *)) {
  192. dispatch_async(dispatch_get_main_queue(), ^{
  193. [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeWarning];
  194. });
  195. }
  196. #endif
  197. }
  198. + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  199. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  200. [self setDefaultMaskType:maskType];
  201. [self showInfoWithStatus:status];
  202. [self setDefaultMaskType:existingMaskType];
  203. }
  204. + (void)showSuccessWithStatus:(NSString*)status {
  205. [self showImage:[self sharedView].successImage status:status];
  206. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  207. if (@available(iOS 10, *)) {
  208. dispatch_async(dispatch_get_main_queue(), ^{
  209. [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess];
  210. });
  211. }
  212. #endif
  213. }
  214. + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  215. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  216. [self setDefaultMaskType:maskType];
  217. [self showSuccessWithStatus:status];
  218. [self setDefaultMaskType:existingMaskType];
  219. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  220. if (@available(iOS 10.0, *)) {
  221. dispatch_async(dispatch_get_main_queue(), ^{
  222. [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess];
  223. });
  224. }
  225. #endif
  226. }
  227. + (void)showErrorWithStatus:(NSString*)status {
  228. [self showImage:[self sharedView].errorImage status:status];
  229. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  230. if (@available(iOS 10.0, *)) {
  231. dispatch_async(dispatch_get_main_queue(), ^{
  232. [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError];
  233. });
  234. }
  235. #endif
  236. }
  237. + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  238. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  239. [self setDefaultMaskType:maskType];
  240. [self showErrorWithStatus:status];
  241. [self setDefaultMaskType:existingMaskType];
  242. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  243. if (@available(iOS 10.0, *)) {
  244. dispatch_async(dispatch_get_main_queue(), ^{
  245. [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError];
  246. });
  247. }
  248. #endif
  249. }
  250. + (void)showImage:(UIImage*)image status:(NSString*)status {
  251. NSTimeInterval displayInterval = [self displayDurationForString:status];
  252. [[self sharedView] showImage:image status:status duration:displayInterval];
  253. }
  254. + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType {
  255. SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;
  256. [self setDefaultMaskType:maskType];
  257. [self showImage:image status:status];
  258. [self setDefaultMaskType:existingMaskType];
  259. }
  260. #pragma mark - Dismiss Methods
  261. + (void)popActivity {
  262. if([self sharedView].activityCount > 0) {
  263. [self sharedView].activityCount--;
  264. }
  265. if([self sharedView].activityCount == 0) {
  266. [[self sharedView] dismiss];
  267. }
  268. }
  269. + (void)dismiss {
  270. [self dismissWithDelay:0.0 completion:nil];
  271. }
  272. + (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion {
  273. [self dismissWithDelay:0.0 completion:completion];
  274. }
  275. + (void)dismissWithDelay:(NSTimeInterval)delay {
  276. [self dismissWithDelay:delay completion:nil];
  277. }
  278. + (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
  279. [[self sharedView] dismissWithDelay:delay completion:completion];
  280. }
  281. #pragma mark - Offset
  282. + (void)setOffsetFromCenter:(UIOffset)offset {
  283. [self sharedView].offsetFromCenter = offset;
  284. }
  285. + (void)resetOffsetFromCenter {
  286. [self setOffsetFromCenter:UIOffsetZero];
  287. }
  288. #pragma mark - Instance Methods
  289. - (instancetype)initWithFrame:(CGRect)frame {
  290. if((self = [super initWithFrame:frame])) {
  291. _isInitializing = YES;
  292. self.userInteractionEnabled = NO;
  293. self.activityCount = 0;
  294. self.backgroundView.alpha = 0.0f;
  295. self.imageView.alpha = 0.0f;
  296. self.statusLabel.alpha = 0.0f;
  297. self.indefiniteAnimatedView.alpha = 0.0f;
  298. self.ringView.alpha = self.backgroundRingView.alpha = 0.0f;
  299. _backgroundColor = [UIColor whiteColor];
  300. _foregroundColor = [UIColor blackColor];
  301. _backgroundLayerColor = [UIColor colorWithWhite:0 alpha:0.4];
  302. // Set default values
  303. _defaultMaskType = SVProgressHUDMaskTypeNone;
  304. _defaultStyle = SVProgressHUDStyleLight;
  305. _defaultAnimationType = SVProgressHUDAnimationTypeFlat;
  306. _minimumSize = CGSizeZero;
  307. _font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
  308. _imageViewSize = CGSizeMake(28.0f, 28.0f);
  309. _shouldTintImages = YES;
  310. NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]];
  311. NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"];
  312. NSBundle *imageBundle = [NSBundle bundleWithURL:url];
  313. _infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]];
  314. _successImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"success" ofType:@"png"]];
  315. _errorImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"error" ofType:@"png"]];
  316. _ringThickness = 2.0f;
  317. _ringRadius = 18.0f;
  318. _ringNoTextRadius = 24.0f;
  319. _cornerRadius = 14.0f;
  320. _graceTimeInterval = 0.0f;
  321. _minimumDismissTimeInterval = 5.0;
  322. _maximumDismissTimeInterval = CGFLOAT_MAX;
  323. _fadeInAnimationDuration = SVProgressHUDDefaultAnimationDuration;
  324. _fadeOutAnimationDuration = SVProgressHUDDefaultAnimationDuration;
  325. _maxSupportedWindowLevel = UIWindowLevelNormal;
  326. _hapticsEnabled = NO;
  327. // Accessibility support
  328. self.accessibilityIdentifier = @"SVProgressHUD";
  329. self.isAccessibilityElement = YES;
  330. _isInitializing = NO;
  331. }
  332. return self;
  333. }
  334. - (void)updateHUDFrame {
  335. // Check if an image or progress ring is displayed
  336. BOOL imageUsed = (self.imageView.image) && !(self.imageView.hidden);
  337. BOOL progressUsed = self.imageView.hidden;
  338. // Calculate size of string
  339. CGRect labelRect = CGRectZero;
  340. CGFloat labelHeight = 0.0f;
  341. CGFloat labelWidth = 0.0f;
  342. if(self.statusLabel.text) {
  343. CGSize constraintSize = CGSizeMake(200.0f, 300.0f);
  344. labelRect = [self.statusLabel.text boundingRectWithSize:constraintSize
  345. options:(NSStringDrawingOptions)(NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin)
  346. attributes:@{NSFontAttributeName: self.statusLabel.font}
  347. context:NULL];
  348. labelHeight = ceilf(CGRectGetHeight(labelRect));
  349. labelWidth = ceilf(CGRectGetWidth(labelRect));
  350. }
  351. // Calculate hud size based on content
  352. // For the beginning use default values, these
  353. // might get update if string is too large etc.
  354. CGFloat hudWidth;
  355. CGFloat hudHeight;
  356. CGFloat contentWidth = 0.0f;
  357. CGFloat contentHeight = 0.0f;
  358. if(imageUsed || progressUsed) {
  359. contentWidth = CGRectGetWidth(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame);
  360. contentHeight = CGRectGetHeight(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame);
  361. }
  362. // |-spacing-content-spacing-|
  363. hudWidth = SVProgressHUDHorizontalSpacing + MAX(labelWidth, contentWidth) + SVProgressHUDHorizontalSpacing;
  364. // |-spacing-content-(labelSpacing-label-)spacing-|
  365. hudHeight = SVProgressHUDVerticalSpacing + labelHeight + contentHeight + SVProgressHUDVerticalSpacing;
  366. if(self.statusLabel.text && (imageUsed || progressUsed)){
  367. // Add spacing if both content and label are used
  368. hudHeight += SVProgressHUDLabelSpacing;
  369. }
  370. // Update values on subviews
  371. self.hudView.bounds = CGRectMake(0.0f, 0.0f, MAX(self.minimumSize.width, hudWidth), MAX(self.minimumSize.height, hudHeight));
  372. // Animate value update
  373. [CATransaction begin];
  374. [CATransaction setDisableActions:YES];
  375. // Spinner and image view
  376. CGFloat centerY;
  377. if(self.statusLabel.text) {
  378. CGFloat yOffset = MAX(SVProgressHUDVerticalSpacing, (self.minimumSize.height - contentHeight - SVProgressHUDLabelSpacing - labelHeight) / 2.0f);
  379. centerY = yOffset + contentHeight / 2.0f;
  380. } else {
  381. centerY = CGRectGetMidY(self.hudView.bounds);
  382. }
  383. self.indefiniteAnimatedView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY);
  384. if(self.progress != SVProgressHUDUndefinedProgress) {
  385. self.backgroundRingView.center = self.ringView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY);
  386. }
  387. self.imageView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY);
  388. // Label
  389. if(imageUsed || progressUsed) {
  390. centerY = CGRectGetMaxY(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame) + SVProgressHUDLabelSpacing + labelHeight / 2.0f;
  391. } else {
  392. centerY = CGRectGetMidY(self.hudView.bounds);
  393. }
  394. self.statusLabel.frame = labelRect;
  395. self.statusLabel.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY);
  396. [CATransaction commit];
  397. }
  398. #if TARGET_OS_IOS
  399. - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation {
  400. UIInterpolatingMotionEffectType xMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis;
  401. UIInterpolatingMotionEffectType yMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis;
  402. [self updateMotionEffectForXMotionEffectType:xMotionEffectType yMotionEffectType:yMotionEffectType];
  403. }
  404. #endif
  405. - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType {
  406. UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:xMotionEffectType];
  407. effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints);
  408. effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints);
  409. UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:yMotionEffectType];
  410. effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints);
  411. effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints);
  412. UIMotionEffectGroup *effectGroup = [UIMotionEffectGroup new];
  413. effectGroup.motionEffects = @[effectX, effectY];
  414. // Clear old motion effect, then add new motion effects
  415. self.hudView.motionEffects = @[];
  416. [self.hudView addMotionEffect:effectGroup];
  417. }
  418. - (void)updateViewHierarchy {
  419. // Add the overlay to the application window if necessary
  420. if(!self.controlView.superview) {
  421. if(self.containerView){
  422. [self.containerView addSubview:self.controlView];
  423. } else {
  424. #if !defined(SV_APP_EXTENSIONS)
  425. [self.frontWindow addSubview:self.controlView];
  426. #else
  427. // If SVProgressHUD is used inside an app extension add it to the given view
  428. if(self.viewForExtension) {
  429. [self.viewForExtension addSubview:self.controlView];
  430. }
  431. #endif
  432. }
  433. } else {
  434. // The HUD is already on screen, but maybe not in front. Therefore
  435. // ensure that overlay will be on top of rootViewController (which may
  436. // be changed during runtime).
  437. [self.controlView.superview bringSubviewToFront:self.controlView];
  438. }
  439. // Add self to the overlay view
  440. if(!self.superview) {
  441. [self.controlView addSubview:self];
  442. }
  443. }
  444. - (void)setStatus:(NSString*)status {
  445. self.statusLabel.text = status;
  446. self.statusLabel.hidden = status.length == 0;
  447. [self updateHUDFrame];
  448. }
  449. - (void)setGraceTimer:(NSTimer*)timer {
  450. if(_graceTimer) {
  451. [_graceTimer invalidate];
  452. _graceTimer = nil;
  453. }
  454. if(timer) {
  455. _graceTimer = timer;
  456. }
  457. }
  458. - (void)setFadeOutTimer:(NSTimer*)timer {
  459. if(_fadeOutTimer) {
  460. [_fadeOutTimer invalidate];
  461. _fadeOutTimer = nil;
  462. }
  463. if(timer) {
  464. _fadeOutTimer = timer;
  465. }
  466. }
  467. #pragma mark - Notifications and their handling
  468. - (void)registerNotifications {
  469. #if TARGET_OS_IOS
  470. [[NSNotificationCenter defaultCenter] addObserver:self
  471. selector:@selector(positionHUD:)
  472. name:UIApplicationDidChangeStatusBarOrientationNotification
  473. object:nil];
  474. [[NSNotificationCenter defaultCenter] addObserver:self
  475. selector:@selector(positionHUD:)
  476. name:UIKeyboardWillHideNotification
  477. object:nil];
  478. [[NSNotificationCenter defaultCenter] addObserver:self
  479. selector:@selector(positionHUD:)
  480. name:UIKeyboardDidHideNotification
  481. object:nil];
  482. [[NSNotificationCenter defaultCenter] addObserver:self
  483. selector:@selector(positionHUD:)
  484. name:UIKeyboardWillShowNotification
  485. object:nil];
  486. [[NSNotificationCenter defaultCenter] addObserver:self
  487. selector:@selector(positionHUD:)
  488. name:UIKeyboardDidShowNotification
  489. object:nil];
  490. #endif
  491. [[NSNotificationCenter defaultCenter] addObserver:self
  492. selector:@selector(positionHUD:)
  493. name:UIApplicationDidBecomeActiveNotification
  494. object:nil];
  495. }
  496. - (NSDictionary*)notificationUserInfo {
  497. return (self.statusLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.statusLabel.text} : nil);
  498. }
  499. - (void)positionHUD:(NSNotification*)notification {
  500. CGFloat keyboardHeight = 0.0f;
  501. double animationDuration = 0.0;
  502. #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
  503. self.frame = [[[UIApplication sharedApplication] delegate] window].bounds;
  504. UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation;
  505. #elif !defined(SV_APP_EXTENSIONS) && !TARGET_OS_IOS
  506. self.frame= [UIApplication sharedApplication].keyWindow.bounds;
  507. #else
  508. if (self.viewForExtension) {
  509. self.frame = self.viewForExtension.frame;
  510. } else {
  511. self.frame = UIScreen.mainScreen.bounds;
  512. }
  513. #if TARGET_OS_IOS
  514. UIInterfaceOrientation orientation = CGRectGetWidth(self.frame) > CGRectGetHeight(self.frame) ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait;
  515. #endif
  516. #endif
  517. #if TARGET_OS_IOS
  518. // Get keyboardHeight in regard to current state
  519. if(notification) {
  520. NSDictionary* keyboardInfo = [notification userInfo];
  521. CGRect keyboardFrame = [keyboardInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
  522. animationDuration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  523. if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) {
  524. keyboardHeight = CGRectGetWidth(keyboardFrame);
  525. if(UIInterfaceOrientationIsPortrait(orientation)) {
  526. keyboardHeight = CGRectGetHeight(keyboardFrame);
  527. }
  528. }
  529. } else {
  530. keyboardHeight = self.visibleKeyboardHeight;
  531. }
  532. #endif
  533. // Get the currently active frame of the display (depends on orientation)
  534. CGRect orientationFrame = self.bounds;
  535. #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
  536. CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame;
  537. #else
  538. CGRect statusBarFrame = CGRectZero;
  539. #endif
  540. #if TARGET_OS_IOS
  541. // Update the motion effects in regard to orientation
  542. [self updateMotionEffectForOrientation:orientation];
  543. #else
  544. [self updateMotionEffectForXMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis yMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  545. #endif
  546. // Calculate available height for display
  547. CGFloat activeHeight = CGRectGetHeight(orientationFrame);
  548. if(keyboardHeight > 0) {
  549. activeHeight += CGRectGetHeight(statusBarFrame) * 2;
  550. }
  551. activeHeight -= keyboardHeight;
  552. CGFloat posX = CGRectGetMidX(orientationFrame);
  553. CGFloat posY = floorf(activeHeight*0.45f);
  554. CGFloat rotateAngle = 0.0;
  555. CGPoint newCenter = CGPointMake(posX, posY);
  556. if(notification) {
  557. // Animate update if notification was present
  558. [UIView animateWithDuration:animationDuration
  559. delay:0
  560. options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState)
  561. animations:^{
  562. [self moveToPoint:newCenter rotateAngle:rotateAngle];
  563. [self.hudView setNeedsDisplay];
  564. } completion:nil];
  565. } else {
  566. [self moveToPoint:newCenter rotateAngle:rotateAngle];
  567. }
  568. }
  569. - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle {
  570. self.hudView.transform = CGAffineTransformMakeRotation(angle);
  571. if (self.containerView) {
  572. self.hudView.center = CGPointMake(self.containerView.center.x + self.offsetFromCenter.horizontal, self.containerView.center.y + self.offsetFromCenter.vertical);
  573. } else {
  574. self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical);
  575. }
  576. }
  577. #pragma mark - Event handling
  578. - (void)controlViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event {
  579. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification
  580. object:self
  581. userInfo:[self notificationUserInfo]];
  582. UITouch *touch = event.allTouches.anyObject;
  583. CGPoint touchLocation = [touch locationInView:self];
  584. if(CGRectContainsPoint(self.hudView.frame, touchLocation)) {
  585. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidTouchDownInsideNotification
  586. object:self
  587. userInfo:[self notificationUserInfo]];
  588. }
  589. }
  590. #pragma mark - Master show/dismiss methods
  591. - (void)showProgress:(float)progress status:(NSString*)status {
  592. __weak SVProgressHUD *weakSelf = self;
  593. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  594. __strong SVProgressHUD *strongSelf = weakSelf;
  595. if(strongSelf){
  596. if(strongSelf.fadeOutTimer) {
  597. strongSelf.activityCount = 0;
  598. }
  599. // Stop timer
  600. strongSelf.fadeOutTimer = nil;
  601. strongSelf.graceTimer = nil;
  602. // Update / Check view hierarchy to ensure the HUD is visible
  603. [strongSelf updateViewHierarchy];
  604. // Reset imageView and fadeout timer if an image is currently displayed
  605. strongSelf.imageView.hidden = YES;
  606. strongSelf.imageView.image = nil;
  607. // Update text and set progress to the given value
  608. strongSelf.statusLabel.hidden = status.length == 0;
  609. strongSelf.statusLabel.text = status;
  610. strongSelf.progress = progress;
  611. // Choose the "right" indicator depending on the progress
  612. if(progress >= 0) {
  613. // Cancel the indefiniteAnimatedView, then show the ringLayer
  614. [strongSelf cancelIndefiniteAnimatedViewAnimation];
  615. // Add ring to HUD
  616. if(!strongSelf.ringView.superview){
  617. [strongSelf.hudView.contentView addSubview:strongSelf.ringView];
  618. }
  619. if(!strongSelf.backgroundRingView.superview){
  620. [strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
  621. }
  622. // Set progress animated
  623. [CATransaction begin];
  624. [CATransaction setDisableActions:YES];
  625. strongSelf.ringView.strokeEnd = progress;
  626. [CATransaction commit];
  627. // Update the activity count
  628. if(progress == 0) {
  629. strongSelf.activityCount++;
  630. }
  631. } else {
  632. // Cancel the ringLayer animation, then show the indefiniteAnimatedView
  633. [strongSelf cancelRingLayerAnimation];
  634. // Add indefiniteAnimatedView to HUD
  635. [strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
  636. if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
  637. [(id)strongSelf.indefiniteAnimatedView startAnimating];
  638. }
  639. // Update the activity count
  640. strongSelf.activityCount++;
  641. }
  642. // Fade in delayed if a grace time is set
  643. if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
  644. strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO];
  645. [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
  646. } else {
  647. [strongSelf fadeIn:nil];
  648. }
  649. // Tell the Haptics Generator to prepare for feedback, which may come soon
  650. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  651. if (@available(iOS 10.0, *)) {
  652. [strongSelf.hapticGenerator prepare];
  653. }
  654. #endif
  655. }
  656. }];
  657. }
  658. - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
  659. __weak SVProgressHUD *weakSelf = self;
  660. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  661. __strong SVProgressHUD *strongSelf = weakSelf;
  662. if(strongSelf){
  663. // Stop timer
  664. strongSelf.fadeOutTimer = nil;
  665. strongSelf.graceTimer = nil;
  666. // Update / Check view hierarchy to ensure the HUD is visible
  667. [strongSelf updateViewHierarchy];
  668. // Reset progress and cancel any running animation
  669. strongSelf.progress = SVProgressHUDUndefinedProgress;
  670. [strongSelf cancelRingLayerAnimation];
  671. [strongSelf cancelIndefiniteAnimatedViewAnimation];
  672. // Update imageView
  673. if (self.shouldTintImages) {
  674. if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
  675. strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  676. }
  677. strongSelf.imageView.tintColor = strongSelf.foregroundColorForStyle;;
  678. } else {
  679. strongSelf.imageView.image = image;
  680. }
  681. strongSelf.imageView.hidden = NO;
  682. // Update text
  683. strongSelf.statusLabel.hidden = status.length == 0;
  684. strongSelf.statusLabel.text = status;
  685. // Fade in delayed if a grace time is set
  686. // An image will be dismissed automatically. Thus pass the duration as userInfo.
  687. if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) {
  688. strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:@(duration) repeats:NO];
  689. [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes];
  690. } else {
  691. [strongSelf fadeIn:@(duration)];
  692. }
  693. }
  694. }];
  695. }
  696. - (void)fadeIn:(id)data {
  697. // Update the HUDs frame to the new content and position HUD
  698. [self updateHUDFrame];
  699. [self positionHUD:nil];
  700. // Update accessibility as well as user interaction
  701. if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
  702. self.controlView.userInteractionEnabled = YES;
  703. self.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
  704. self.isAccessibilityElement = YES;
  705. } else {
  706. self.controlView.userInteractionEnabled = NO;
  707. self.hudView.accessibilityLabel = self.statusLabel.text ?: NSLocalizedString(@"Loading", nil);
  708. self.hudView.isAccessibilityElement = YES;
  709. }
  710. // Get duration
  711. id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data;
  712. // Show if not already visible
  713. if(self.backgroundView.alpha != 1.0f) {
  714. // Post notification to inform user
  715. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
  716. object:self
  717. userInfo:[self notificationUserInfo]];
  718. // Shrink HUD to to make a nice appear / pop up animation
  719. self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f);
  720. __block void (^animationsBlock)(void) = ^{
  721. // Zoom HUD a little to make a nice appear / pop up animation
  722. self.hudView.transform = CGAffineTransformIdentity;
  723. // Fade in all effects (colors, blur, etc.)
  724. [self fadeInEffects];
  725. };
  726. __block void (^completionBlock)(void) = ^{
  727. // Check if we really achieved to show the HUD (<=> alpha)
  728. // and the change of these values has not been cancelled in between e.g. due to a dismissal
  729. if(self.backgroundView.alpha == 1.0f){
  730. // Register observer <=> we now have to handle orientation changes etc.
  731. [self registerNotifications];
  732. // Post notification to inform user
  733. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
  734. object:self
  735. userInfo:[self notificationUserInfo]];
  736. // Update accessibility
  737. UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
  738. UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
  739. // Dismiss automatically if a duration was passed as userInfo. We start a timer
  740. // which then will call dismiss after the predefined duration
  741. if(duration){
  742. self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
  743. [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
  744. }
  745. }
  746. };
  747. // Animate appearance
  748. if (self.fadeInAnimationDuration > 0) {
  749. // Animate appearance
  750. [UIView animateWithDuration:self.fadeInAnimationDuration
  751. delay:0
  752. options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
  753. animations:^{
  754. animationsBlock();
  755. } completion:^(BOOL finished) {
  756. completionBlock();
  757. }];
  758. } else {
  759. animationsBlock();
  760. completionBlock();
  761. }
  762. // Inform iOS to redraw the view hierarchy
  763. [self setNeedsDisplay];
  764. } else {
  765. // Update accessibility
  766. UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
  767. UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text);
  768. // Dismiss automatically if a duration was passed as userInfo. We start a timer
  769. // which then will call dismiss after the predefined duration
  770. if(duration){
  771. self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO];
  772. [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes];
  773. }
  774. }
  775. }
  776. - (void)dismiss {
  777. [self dismissWithDelay:0.0 completion:nil];
  778. }
  779. - (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
  780. __weak SVProgressHUD *weakSelf = self;
  781. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  782. __strong SVProgressHUD *strongSelf = weakSelf;
  783. if(strongSelf){
  784. // Stop timer
  785. strongSelf.graceTimer = nil;
  786. // Post notification to inform user
  787. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification
  788. object:nil
  789. userInfo:[strongSelf notificationUserInfo]];
  790. // Reset activity count
  791. strongSelf.activityCount = 0;
  792. __block void (^animationsBlock)(void) = ^{
  793. // Shrink HUD a little to make a nice disappear animation
  794. strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
  795. // Fade out all effects (colors, blur, etc.)
  796. [strongSelf fadeOutEffects];
  797. };
  798. __block void (^completionBlock)(void) = ^{
  799. // Check if we really achieved to dismiss the HUD (<=> alpha values are applied)
  800. // and the change of these values has not been cancelled in between e.g. due to a new show
  801. if(self.backgroundView.alpha == 0.0f){
  802. // Clean up view hierarchy (overlays)
  803. [strongSelf.controlView removeFromSuperview];
  804. [strongSelf.backgroundView removeFromSuperview];
  805. [strongSelf.hudView removeFromSuperview];
  806. [strongSelf removeFromSuperview];
  807. // Reset progress and cancel any running animation
  808. strongSelf.progress = SVProgressHUDUndefinedProgress;
  809. [strongSelf cancelRingLayerAnimation];
  810. [strongSelf cancelIndefiniteAnimatedViewAnimation];
  811. // Remove observer <=> we do not have to handle orientation changes etc.
  812. [[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
  813. // Post notification to inform user
  814. [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
  815. object:strongSelf
  816. userInfo:[strongSelf notificationUserInfo]];
  817. // Tell the rootViewController to update the StatusBar appearance
  818. #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
  819. UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
  820. [rootController setNeedsStatusBarAppearanceUpdate];
  821. #endif
  822. // Run an (optional) completionHandler
  823. if (completion) {
  824. completion();
  825. }
  826. }
  827. };
  828. // UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected
  829. // When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current
  830. // values to check if an animation is necessary. The evaluation happens at function call time and not
  831. // after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after.
  832. dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
  833. dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
  834. if (strongSelf.fadeOutAnimationDuration > 0) {
  835. // Animate appearance
  836. [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration
  837. delay:0
  838. options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState)
  839. animations:^{
  840. animationsBlock();
  841. } completion:^(BOOL finished) {
  842. completionBlock();
  843. }];
  844. } else {
  845. animationsBlock();
  846. completionBlock();
  847. }
  848. });
  849. // Inform iOS to redraw the view hierarchy
  850. [strongSelf setNeedsDisplay];
  851. }
  852. }];
  853. }
  854. #pragma mark - Ring progress animation
  855. - (UIView*)indefiniteAnimatedView {
  856. // Get the correct spinner for defaultAnimationType
  857. if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat){
  858. // Check if spinner exists and is an object of different class
  859. if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[SVIndefiniteAnimatedView class]]){
  860. [_indefiniteAnimatedView removeFromSuperview];
  861. _indefiniteAnimatedView = nil;
  862. }
  863. if(!_indefiniteAnimatedView){
  864. _indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero];
  865. }
  866. // Update styling
  867. SVIndefiniteAnimatedView *indefiniteAnimatedView = (SVIndefiniteAnimatedView*)_indefiniteAnimatedView;
  868. indefiniteAnimatedView.strokeColor = self.foregroundColorForStyle;
  869. indefiniteAnimatedView.strokeThickness = self.ringThickness;
  870. indefiniteAnimatedView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius;
  871. } else {
  872. // Check if spinner exists and is an object of different class
  873. if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[UIActivityIndicatorView class]]){
  874. [_indefiniteAnimatedView removeFromSuperview];
  875. _indefiniteAnimatedView = nil;
  876. }
  877. if(!_indefiniteAnimatedView){
  878. _indefiniteAnimatedView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  879. }
  880. // Update styling
  881. UIActivityIndicatorView *activityIndicatorView = (UIActivityIndicatorView*)_indefiniteAnimatedView;
  882. activityIndicatorView.color = self.foregroundColorForStyle;
  883. }
  884. [_indefiniteAnimatedView sizeToFit];
  885. return _indefiniteAnimatedView;
  886. }
  887. - (SVProgressAnimatedView*)ringView {
  888. if(!_ringView) {
  889. _ringView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero];
  890. }
  891. // Update styling
  892. _ringView.strokeColor = self.foregroundColorForStyle;
  893. _ringView.strokeThickness = self.ringThickness;
  894. _ringView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius;
  895. return _ringView;
  896. }
  897. - (SVProgressAnimatedView*)backgroundRingView {
  898. if(!_backgroundRingView) {
  899. _backgroundRingView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero];
  900. _backgroundRingView.strokeEnd = 1.0f;
  901. }
  902. // Update styling
  903. _backgroundRingView.strokeColor = [self.foregroundColorForStyle colorWithAlphaComponent:0.1f];
  904. _backgroundRingView.strokeThickness = self.ringThickness;
  905. _backgroundRingView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius;
  906. return _backgroundRingView;
  907. }
  908. - (void)cancelRingLayerAnimation {
  909. // Animate value update, stop animation
  910. [CATransaction begin];
  911. [CATransaction setDisableActions:YES];
  912. [self.hudView.layer removeAllAnimations];
  913. self.ringView.strokeEnd = 0.0f;
  914. [CATransaction commit];
  915. // Remove from view
  916. [self.ringView removeFromSuperview];
  917. [self.backgroundRingView removeFromSuperview];
  918. }
  919. - (void)cancelIndefiniteAnimatedViewAnimation {
  920. // Stop animation
  921. if([self.indefiniteAnimatedView respondsToSelector:@selector(stopAnimating)]) {
  922. [(id)self.indefiniteAnimatedView stopAnimating];
  923. }
  924. // Remove from view
  925. [self.indefiniteAnimatedView removeFromSuperview];
  926. }
  927. #pragma mark - Utilities
  928. + (BOOL)isVisible {
  929. // Checking one alpha value is sufficient as they are all the same
  930. return [self sharedView].backgroundView.alpha > 0.0f;
  931. }
  932. #pragma mark - Getters
  933. + (NSTimeInterval)displayDurationForString:(NSString*)string {
  934. CGFloat minimum = MAX((CGFloat)string.length * 0.06 + 0.5, [self sharedView].minimumDismissTimeInterval);
  935. return MIN(minimum, [self sharedView].maximumDismissTimeInterval);
  936. }
  937. - (UIColor*)foregroundColorForStyle {
  938. if(self.defaultStyle == SVProgressHUDStyleLight) {
  939. return [UIColor blackColor];
  940. } else if(self.defaultStyle == SVProgressHUDStyleDark) {
  941. return [UIColor whiteColor];
  942. } else {
  943. return self.foregroundColor;
  944. }
  945. }
  946. - (UIColor*)backgroundColorForStyle {
  947. if(self.defaultStyle == SVProgressHUDStyleLight) {
  948. return [UIColor whiteColor];
  949. } else if(self.defaultStyle == SVProgressHUDStyleDark) {
  950. return [UIColor blackColor];
  951. } else {
  952. return self.backgroundColor;
  953. }
  954. }
  955. - (UIControl*)controlView {
  956. if(!_controlView) {
  957. _controlView = [UIControl new];
  958. _controlView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  959. _controlView.backgroundColor = [UIColor clearColor];
  960. _controlView.userInteractionEnabled = YES;
  961. [_controlView addTarget:self action:@selector(controlViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown];
  962. }
  963. // Update frames
  964. #if !defined(SV_APP_EXTENSIONS)
  965. CGRect windowBounds = [[[UIApplication sharedApplication] delegate] window].bounds;
  966. _controlView.frame = windowBounds;
  967. #else
  968. _controlView.frame = [UIScreen mainScreen].bounds;
  969. #endif
  970. return _controlView;
  971. }
  972. -(UIView *)backgroundView {
  973. if(!_backgroundView){
  974. _backgroundView = [UIView new];
  975. _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  976. }
  977. if(!_backgroundView.superview){
  978. [self insertSubview:_backgroundView belowSubview:self.hudView];
  979. }
  980. // Update styling
  981. if(self.defaultMaskType == SVProgressHUDMaskTypeGradient){
  982. if(!_backgroundRadialGradientLayer){
  983. _backgroundRadialGradientLayer = [SVRadialGradientLayer layer];
  984. }
  985. if(!_backgroundRadialGradientLayer.superlayer){
  986. [_backgroundView.layer insertSublayer:_backgroundRadialGradientLayer atIndex:0];
  987. }
  988. _backgroundView.backgroundColor = [UIColor clearColor];
  989. } else {
  990. if(_backgroundRadialGradientLayer && _backgroundRadialGradientLayer.superlayer){
  991. [_backgroundRadialGradientLayer removeFromSuperlayer];
  992. }
  993. if(self.defaultMaskType == SVProgressHUDMaskTypeBlack){
  994. _backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
  995. } else if(self.defaultMaskType == SVProgressHUDMaskTypeCustom){
  996. _backgroundView.backgroundColor = self.backgroundLayerColor;
  997. } else {
  998. _backgroundView.backgroundColor = [UIColor clearColor];
  999. }
  1000. }
  1001. // Update frame
  1002. if(_backgroundView){
  1003. _backgroundView.frame = self.bounds;
  1004. }
  1005. if(_backgroundRadialGradientLayer){
  1006. _backgroundRadialGradientLayer.frame = self.bounds;
  1007. // Calculate the new center of the gradient, it may change if keyboard is visible
  1008. CGPoint gradientCenter = self.center;
  1009. gradientCenter.y = (self.bounds.size.height - self.visibleKeyboardHeight)/2;
  1010. _backgroundRadialGradientLayer.gradientCenter = gradientCenter;
  1011. [_backgroundRadialGradientLayer setNeedsDisplay];
  1012. }
  1013. return _backgroundView;
  1014. }
  1015. - (UIVisualEffectView*)hudView {
  1016. if(!_hudView) {
  1017. _hudView = [UIVisualEffectView new];
  1018. _hudView.layer.masksToBounds = YES;
  1019. _hudView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
  1020. }
  1021. if(!_hudView.superview) {
  1022. [self addSubview:_hudView];
  1023. }
  1024. // Update styling
  1025. _hudView.layer.cornerRadius = self.cornerRadius;
  1026. return _hudView;
  1027. }
  1028. - (UILabel*)statusLabel {
  1029. if(!_statusLabel) {
  1030. _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  1031. _statusLabel.backgroundColor = [UIColor clearColor];
  1032. _statusLabel.adjustsFontSizeToFitWidth = YES;
  1033. _statusLabel.textAlignment = NSTextAlignmentCenter;
  1034. _statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
  1035. _statusLabel.numberOfLines = 0;
  1036. }
  1037. if(!_statusLabel.superview) {
  1038. [self.hudView.contentView addSubview:_statusLabel];
  1039. }
  1040. // Update styling
  1041. _statusLabel.textColor = self.foregroundColorForStyle;
  1042. _statusLabel.font = self.font;
  1043. return _statusLabel;
  1044. }
  1045. - (UIImageView*)imageView {
  1046. if(_imageView && !CGSizeEqualToSize(_imageView.bounds.size, _imageViewSize)) {
  1047. [_imageView removeFromSuperview];
  1048. _imageView = nil;
  1049. }
  1050. if(!_imageView) {
  1051. _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _imageViewSize.width, _imageViewSize.height)];
  1052. }
  1053. if(!_imageView.superview) {
  1054. [self.hudView.contentView addSubview:_imageView];
  1055. }
  1056. return _imageView;
  1057. }
  1058. #pragma mark - Helper
  1059. - (CGFloat)visibleKeyboardHeight {
  1060. #if !defined(SV_APP_EXTENSIONS)
  1061. UIWindow *keyboardWindow = nil;
  1062. for (UIWindow *testWindow in UIApplication.sharedApplication.windows) {
  1063. if(![testWindow.class isEqual:UIWindow.class]) {
  1064. keyboardWindow = testWindow;
  1065. break;
  1066. }
  1067. }
  1068. for (__strong UIView *possibleKeyboard in keyboardWindow.subviews) {
  1069. NSString *viewName = NSStringFromClass(possibleKeyboard.class);
  1070. if([viewName hasPrefix:@"UI"]){
  1071. if([viewName hasSuffix:@"PeripheralHostView"] || [viewName hasSuffix:@"Keyboard"]){
  1072. return CGRectGetHeight(possibleKeyboard.bounds);
  1073. } else if ([viewName hasSuffix:@"InputSetContainerView"]){
  1074. for (__strong UIView *possibleKeyboardSubview in possibleKeyboard.subviews) {
  1075. viewName = NSStringFromClass(possibleKeyboardSubview.class);
  1076. if([viewName hasPrefix:@"UI"] && [viewName hasSuffix:@"InputSetHostView"]) {
  1077. CGRect convertedRect = [possibleKeyboard convertRect:possibleKeyboardSubview.frame toView:self];
  1078. CGRect intersectedRect = CGRectIntersection(convertedRect, self.bounds);
  1079. if (!CGRectIsNull(intersectedRect)) {
  1080. return CGRectGetHeight(intersectedRect);
  1081. }
  1082. }
  1083. }
  1084. }
  1085. }
  1086. }
  1087. #endif
  1088. return 0;
  1089. }
  1090. - (UIWindow *)frontWindow {
  1091. #if !defined(SV_APP_EXTENSIONS)
  1092. NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
  1093. for (UIWindow *window in frontToBackWindows) {
  1094. BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
  1095. BOOL windowIsVisible = !window.hidden && window.alpha > 0;
  1096. BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal && window.windowLevel <= self.maxSupportedWindowLevel);
  1097. BOOL windowKeyWindow = window.isKeyWindow;
  1098. if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
  1099. return window;
  1100. }
  1101. }
  1102. #endif
  1103. return nil;
  1104. }
  1105. - (void)fadeInEffects {
  1106. if(self.defaultStyle != SVProgressHUDStyleCustom) {
  1107. // Add blur effect
  1108. UIBlurEffectStyle blurEffectStyle = self.defaultStyle == SVProgressHUDStyleDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight;
  1109. UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurEffectStyle];
  1110. self.hudView.effect = blurEffect;
  1111. // We omit UIVibrancy effect and use a suitable background color as an alternative.
  1112. // This will make everything more readable. See the following for details:
  1113. // https://www.omnigroup.com/developer/how-to-make-text-in-a-uivisualeffectview-readable-on-any-background
  1114. self.hudView.backgroundColor = [self.backgroundColorForStyle colorWithAlphaComponent:0.6f];
  1115. } else {
  1116. self.hudView.backgroundColor = self.backgroundColorForStyle;
  1117. }
  1118. // Fade in views
  1119. self.backgroundView.alpha = 1.0f;
  1120. self.imageView.alpha = 1.0f;
  1121. self.statusLabel.alpha = 1.0f;
  1122. self.indefiniteAnimatedView.alpha = 1.0f;
  1123. self.ringView.alpha = self.backgroundRingView.alpha = 1.0f;
  1124. }
  1125. - (void)fadeOutEffects
  1126. {
  1127. if(self.defaultStyle != SVProgressHUDStyleCustom) {
  1128. // Remove blur effect
  1129. self.hudView.effect = nil;
  1130. }
  1131. // Remove background color
  1132. self.hudView.backgroundColor = [UIColor clearColor];
  1133. // Fade out views
  1134. self.backgroundView.alpha = 0.0f;
  1135. self.imageView.alpha = 0.0f;
  1136. self.statusLabel.alpha = 0.0f;
  1137. self.indefiniteAnimatedView.alpha = 0.0f;
  1138. self.ringView.alpha = self.backgroundRingView.alpha = 0.0f;
  1139. }
  1140. #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
  1141. - (UINotificationFeedbackGenerator *)hapticGenerator NS_AVAILABLE_IOS(10_0) {
  1142. // Only return if haptics are enabled
  1143. if(!self.hapticsEnabled) {
  1144. return nil;
  1145. }
  1146. if(!_hapticGenerator) {
  1147. _hapticGenerator = [[UINotificationFeedbackGenerator alloc] init];
  1148. }
  1149. return _hapticGenerator;
  1150. }
  1151. #endif
  1152. #pragma mark - UIAppearance Setters
  1153. - (void)setDefaultStyle:(SVProgressHUDStyle)style {
  1154. if (!_isInitializing) _defaultStyle = style;
  1155. }
  1156. - (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType {
  1157. if (!_isInitializing) _defaultMaskType = maskType;
  1158. }
  1159. - (void)setDefaultAnimationType:(SVProgressHUDAnimationType)animationType {
  1160. if (!_isInitializing) _defaultAnimationType = animationType;
  1161. }
  1162. - (void)setContainerView:(UIView *)containerView {
  1163. if (!_isInitializing) _containerView = containerView;
  1164. }
  1165. - (void)setMinimumSize:(CGSize)minimumSize {
  1166. if (!_isInitializing) _minimumSize = minimumSize;
  1167. }
  1168. - (void)setRingThickness:(CGFloat)ringThickness {
  1169. if (!_isInitializing) _ringThickness = ringThickness;
  1170. }
  1171. - (void)setRingRadius:(CGFloat)ringRadius {
  1172. if (!_isInitializing) _ringRadius = ringRadius;
  1173. }
  1174. - (void)setRingNoTextRadius:(CGFloat)ringNoTextRadius {
  1175. if (!_isInitializing) _ringNoTextRadius = ringNoTextRadius;
  1176. }
  1177. - (void)setCornerRadius:(CGFloat)cornerRadius {
  1178. if (!_isInitializing) _cornerRadius = cornerRadius;
  1179. }
  1180. - (void)setFont:(UIFont*)font {
  1181. if (!_isInitializing) _font = font;
  1182. }
  1183. - (void)setForegroundColor:(UIColor*)color {
  1184. if (!_isInitializing) _foregroundColor = color;
  1185. }
  1186. - (void)setBackgroundColor:(UIColor*)color {
  1187. if (!_isInitializing) _backgroundColor = color;
  1188. }
  1189. - (void)setBackgroundLayerColor:(UIColor*)color {
  1190. if (!_isInitializing) _backgroundLayerColor = color;
  1191. }
  1192. - (void)setShouldTintImages:(BOOL)shouldTintImages {
  1193. if (!_isInitializing) _shouldTintImages = shouldTintImages;
  1194. }
  1195. - (void)setInfoImage:(UIImage*)image {
  1196. if (!_isInitializing) _infoImage = image;
  1197. }
  1198. - (void)setSuccessImage:(UIImage*)image {
  1199. if (!_isInitializing) _successImage = image;
  1200. }
  1201. - (void)setErrorImage:(UIImage*)image {
  1202. if (!_isInitializing) _errorImage = image;
  1203. }
  1204. - (void)setViewForExtension:(UIView*)view {
  1205. if (!_isInitializing) _viewForExtension = view;
  1206. }
  1207. - (void)setOffsetFromCenter:(UIOffset)offset {
  1208. if (!_isInitializing) _offsetFromCenter = offset;
  1209. }
  1210. - (void)setMinimumDismissTimeInterval:(NSTimeInterval)minimumDismissTimeInterval {
  1211. if (!_isInitializing) _minimumDismissTimeInterval = minimumDismissTimeInterval;
  1212. }
  1213. - (void)setFadeInAnimationDuration:(NSTimeInterval)duration {
  1214. if (!_isInitializing) _fadeInAnimationDuration = duration;
  1215. }
  1216. - (void)setFadeOutAnimationDuration:(NSTimeInterval)duration {
  1217. if (!_isInitializing) _fadeOutAnimationDuration = duration;
  1218. }
  1219. - (void)setMaxSupportedWindowLevel:(UIWindowLevel)maxSupportedWindowLevel {
  1220. if (!_isInitializing) _maxSupportedWindowLevel = maxSupportedWindowLevel;
  1221. }
  1222. @end