SVProgressHUD.m 58 KB

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