SVProgressHUD.m 59 KB

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