You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1581 lines
60 KiB
1581 lines
60 KiB
// |
|
// SVProgressHUD.h |
|
// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD |
|
// |
|
// Copyright (c) 2011-2019 Sam Vermette and contributors. All rights reserved. |
|
// |
|
|
|
#if !__has_feature(objc_arc) |
|
#error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag |
|
#endif |
|
|
|
#import "SVProgressHUD.h" |
|
#import "SVIndefiniteAnimatedView.h" |
|
#import "SVProgressAnimatedView.h" |
|
#import "SVRadialGradientLayer.h" |
|
|
|
NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification"; |
|
NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification"; |
|
NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification"; |
|
NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"; |
|
NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification"; |
|
NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification"; |
|
|
|
NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey"; |
|
|
|
static const CGFloat SVProgressHUDParallaxDepthPoints = 10.0f; |
|
static const CGFloat SVProgressHUDUndefinedProgress = -1; |
|
static const CGFloat SVProgressHUDDefaultAnimationDuration = 0.15f; |
|
static const CGFloat SVProgressHUDVerticalSpacing = 12.0f; |
|
static const CGFloat SVProgressHUDHorizontalSpacing = 12.0f; |
|
static const CGFloat SVProgressHUDLabelSpacing = 8.0f; |
|
|
|
@interface SVProgressHUD () |
|
|
|
@property (nonatomic, strong) NSTimer *graceTimer; |
|
@property (nonatomic, strong) NSTimer *fadeOutTimer; |
|
|
|
@property (nonatomic, strong) UIControl *controlView; |
|
@property (nonatomic, strong) UIView *backgroundView; |
|
@property (nonatomic, strong) SVRadialGradientLayer *backgroundRadialGradientLayer; |
|
@property (nonatomic, strong) UIVisualEffectView *hudView; |
|
@property (nonatomic, strong) UIBlurEffect *hudViewCustomBlurEffect; |
|
@property (nonatomic, strong) UILabel *statusLabel; |
|
@property (nonatomic, strong) UIImageView *imageView; |
|
|
|
@property (nonatomic, strong) UIView *indefiniteAnimatedView; |
|
@property (nonatomic, strong) SVProgressAnimatedView *ringView; |
|
@property (nonatomic, strong) SVProgressAnimatedView *backgroundRingView; |
|
|
|
@property (nonatomic, readwrite) CGFloat progress; |
|
@property (nonatomic, readwrite) NSUInteger activityCount; |
|
|
|
@property (nonatomic, readonly) CGFloat visibleKeyboardHeight; |
|
@property (nonatomic, readonly) UIWindow *frontWindow; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
@property (nonatomic, strong) UINotificationFeedbackGenerator *hapticGenerator NS_AVAILABLE_IOS(10_0); |
|
#endif |
|
|
|
@end |
|
|
|
@implementation SVProgressHUD { |
|
BOOL _isInitializing; |
|
} |
|
|
|
+ (SVProgressHUD*)sharedView { |
|
static dispatch_once_t once; |
|
|
|
static SVProgressHUD *sharedView; |
|
#if !defined(SV_APP_EXTENSIONS) |
|
if (@available(iOS 13.0, *)) { |
|
dispatch_once(&once, ^{ |
|
|
|
NSPredicate *foregroundActivePredict = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { |
|
|
|
return ([evaluatedObject activationState] == UISceneActivationStateForegroundActive); |
|
}]; |
|
|
|
NSSet<UIScene *> *setOfScene = [[[UIApplication sharedApplication] connectedScenes] filteredSetUsingPredicate:foregroundActivePredict]; |
|
UIWindowScene *windowScene = (UIWindowScene *)[[setOfScene allObjects] firstObject]; |
|
UIWindow *current = [[UIWindow alloc] initWithWindowScene:windowScene]; |
|
|
|
sharedView = [[self alloc] initWithFrame:current.bounds]; }); |
|
}else{ |
|
// 创建单例对象 |
|
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; }); |
|
} |
|
|
|
#else |
|
dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); |
|
#endif |
|
return sharedView; |
|
} |
|
|
|
|
|
#pragma mark - Setters |
|
|
|
+ (void)setStatus:(NSString*)status { |
|
[[self sharedView] setStatus:status]; |
|
} |
|
|
|
+ (void)setDefaultStyle:(SVProgressHUDStyle)style { |
|
[self sharedView].defaultStyle = style; |
|
} |
|
|
|
+ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { |
|
[self sharedView].defaultMaskType = maskType; |
|
} |
|
|
|
+ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type { |
|
[self sharedView].defaultAnimationType = type; |
|
} |
|
|
|
+ (void)setContainerView:(nullable UIView*)containerView { |
|
[self sharedView].containerView = containerView; |
|
} |
|
|
|
+ (void)setMinimumSize:(CGSize)minimumSize { |
|
[self sharedView].minimumSize = minimumSize; |
|
} |
|
|
|
+ (void)setRingThickness:(CGFloat)ringThickness { |
|
[self sharedView].ringThickness = ringThickness; |
|
} |
|
|
|
+ (void)setRingRadius:(CGFloat)radius { |
|
[self sharedView].ringRadius = radius; |
|
} |
|
|
|
+ (void)setRingNoTextRadius:(CGFloat)radius { |
|
[self sharedView].ringNoTextRadius = radius; |
|
} |
|
|
|
+ (void)setCornerRadius:(CGFloat)cornerRadius { |
|
[self sharedView].cornerRadius = cornerRadius; |
|
} |
|
|
|
+ (void)setBorderColor:(nonnull UIColor*)color { |
|
[self sharedView].hudView.layer.borderColor = color.CGColor; |
|
} |
|
|
|
+ (void)setBorderWidth:(CGFloat)width { |
|
[self sharedView].hudView.layer.borderWidth = width; |
|
} |
|
|
|
+ (void)setFont:(UIFont*)font { |
|
[self sharedView].font = font; |
|
} |
|
|
|
+ (void)setForegroundColor:(UIColor*)color { |
|
[self sharedView].foregroundColor = color; |
|
[self setDefaultStyle:SVProgressHUDStyleCustom]; |
|
} |
|
|
|
+ (void)setForegroundImageColor:(UIColor *)color { |
|
[self sharedView].foregroundImageColor = color; |
|
[self setDefaultStyle:SVProgressHUDStyleCustom]; |
|
} |
|
|
|
+ (void)setBackgroundColor:(UIColor*)color { |
|
[self sharedView].backgroundColor = color; |
|
[self setDefaultStyle:SVProgressHUDStyleCustom]; |
|
} |
|
|
|
+ (void)setHudViewCustomBlurEffect:(UIBlurEffect*)blurEffect { |
|
[self sharedView].hudViewCustomBlurEffect = blurEffect; |
|
[self setDefaultStyle:SVProgressHUDStyleCustom]; |
|
} |
|
|
|
+ (void)setBackgroundLayerColor:(UIColor*)color { |
|
[self sharedView].backgroundLayerColor = color; |
|
} |
|
|
|
+ (void)setImageViewSize:(CGSize)size { |
|
[self sharedView].imageViewSize = size; |
|
} |
|
|
|
+ (void)setShouldTintImages:(BOOL)shouldTintImages { |
|
[self sharedView].shouldTintImages = shouldTintImages; |
|
} |
|
|
|
+ (void)setInfoImage:(UIImage*)image { |
|
[self sharedView].infoImage = image; |
|
} |
|
|
|
+ (void)setSuccessImage:(UIImage*)image { |
|
[self sharedView].successImage = image; |
|
} |
|
|
|
+ (void)setErrorImage:(UIImage*)image { |
|
[self sharedView].errorImage = image; |
|
} |
|
|
|
+ (void)setViewForExtension:(UIView*)view { |
|
[self sharedView].viewForExtension = view; |
|
} |
|
|
|
+ (void)setGraceTimeInterval:(NSTimeInterval)interval { |
|
[self sharedView].graceTimeInterval = interval; |
|
} |
|
|
|
+ (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval { |
|
[self sharedView].minimumDismissTimeInterval = interval; |
|
} |
|
|
|
+ (void)setMaximumDismissTimeInterval:(NSTimeInterval)interval { |
|
[self sharedView].maximumDismissTimeInterval = interval; |
|
} |
|
|
|
+ (void)setFadeInAnimationDuration:(NSTimeInterval)duration { |
|
[self sharedView].fadeInAnimationDuration = duration; |
|
} |
|
|
|
+ (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { |
|
[self sharedView].fadeOutAnimationDuration = duration; |
|
} |
|
|
|
+ (void)setMaxSupportedWindowLevel:(UIWindowLevel)windowLevel { |
|
[self sharedView].maxSupportedWindowLevel = windowLevel; |
|
} |
|
|
|
+ (void)setHapticsEnabled:(BOOL)hapticsEnabled { |
|
[self sharedView].hapticsEnabled = hapticsEnabled; |
|
} |
|
|
|
+ (void)setMotionEffectEnabled:(BOOL)motionEffectEnabled { |
|
[self sharedView].motionEffectEnabled = motionEffectEnabled; |
|
} |
|
|
|
#pragma mark - Show Methods |
|
|
|
+ (void)show { |
|
[self showWithStatus:nil]; |
|
} |
|
|
|
+ (void)showWithMaskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self show]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
+ (void)showWithStatus:(NSString*)status { |
|
[self showProgress:SVProgressHUDUndefinedProgress status:status]; |
|
} |
|
|
|
+ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showWithStatus:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
+ (void)showProgress:(float)progress { |
|
[self showProgress:progress status:nil]; |
|
} |
|
|
|
+ (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showProgress:progress]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
+ (void)showProgress:(float)progress status:(NSString*)status { |
|
[[self sharedView] showProgress:progress status:status]; |
|
} |
|
|
|
+ (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showProgress:progress status:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
|
|
#pragma mark - Show, then automatically dismiss methods |
|
|
|
+ (void)showInfoWithStatus:(NSString*)status { |
|
[self showImage:[self sharedView].infoImage status:status]; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10.0, *)) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeWarning]; |
|
}); |
|
} |
|
#endif |
|
} |
|
|
|
+ (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showInfoWithStatus:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
+ (void)showSuccessWithStatus:(NSString*)status { |
|
[self showImage:[self sharedView].successImage status:status]; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10, *)) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; |
|
}); |
|
} |
|
#endif |
|
} |
|
|
|
+ (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showSuccessWithStatus:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10.0, *)) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; |
|
}); |
|
} |
|
#endif |
|
} |
|
|
|
+ (void)showErrorWithStatus:(NSString*)status { |
|
[self showImage:[self sharedView].errorImage status:status]; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10.0, *)) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError]; |
|
}); |
|
} |
|
#endif |
|
} |
|
|
|
+ (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showErrorWithStatus:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10.0, *)) { |
|
dispatch_async(dispatch_get_main_queue(), ^{ |
|
[[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError]; |
|
}); |
|
} |
|
#endif |
|
} |
|
|
|
+ (void)showImage:(UIImage*)image status:(NSString*)status { |
|
NSTimeInterval displayInterval = [self displayDurationForString:status]; |
|
[[self sharedView] showImage:image status:status duration:displayInterval]; |
|
} |
|
|
|
+ (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { |
|
SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; |
|
[self setDefaultMaskType:maskType]; |
|
[self showImage:image status:status]; |
|
[self setDefaultMaskType:existingMaskType]; |
|
} |
|
|
|
|
|
#pragma mark - Dismiss Methods |
|
|
|
+ (void)popActivity { |
|
if([self sharedView].activityCount > 0) { |
|
[self sharedView].activityCount--; |
|
} |
|
if([self sharedView].activityCount == 0) { |
|
[[self sharedView] dismiss]; |
|
} |
|
} |
|
|
|
+ (void)dismiss { |
|
[self dismissWithDelay:0.0 completion:nil]; |
|
} |
|
|
|
+ (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion { |
|
[self dismissWithDelay:0.0 completion:completion]; |
|
} |
|
|
|
+ (void)dismissWithDelay:(NSTimeInterval)delay { |
|
[self dismissWithDelay:delay completion:nil]; |
|
} |
|
|
|
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { |
|
[[self sharedView] dismissWithDelay:delay completion:completion]; |
|
} |
|
|
|
|
|
#pragma mark - Offset |
|
|
|
+ (void)setOffsetFromCenter:(UIOffset)offset { |
|
[self sharedView].offsetFromCenter = offset; |
|
} |
|
|
|
+ (void)resetOffsetFromCenter { |
|
[self setOffsetFromCenter:UIOffsetZero]; |
|
} |
|
|
|
|
|
#pragma mark - Instance Methods |
|
|
|
- (instancetype)initWithFrame:(CGRect)frame { |
|
if((self = [super initWithFrame:frame])) { |
|
_isInitializing = YES; |
|
|
|
self.userInteractionEnabled = NO; |
|
self.activityCount = 0; |
|
|
|
self.backgroundView.alpha = 0.0f; |
|
self.imageView.alpha = 0.0f; |
|
self.statusLabel.alpha = 0.0f; |
|
self.indefiniteAnimatedView.alpha = 0.0f; |
|
self.ringView.alpha = self.backgroundRingView.alpha = 0.0f; |
|
|
|
|
|
_backgroundColor = [UIColor whiteColor]; |
|
_foregroundColor = [UIColor blackColor]; |
|
_backgroundLayerColor = [UIColor colorWithWhite:0 alpha:0.4]; |
|
|
|
// Set default values |
|
_defaultMaskType = SVProgressHUDMaskTypeNone; |
|
_defaultStyle = SVProgressHUDStyleLight; |
|
_defaultAnimationType = SVProgressHUDAnimationTypeFlat; |
|
_minimumSize = CGSizeZero; |
|
_font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; |
|
|
|
_imageViewSize = CGSizeMake(28.0f, 28.0f); |
|
_shouldTintImages = YES; |
|
|
|
NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; |
|
NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; |
|
NSBundle *imageBundle = [NSBundle bundleWithURL:url]; |
|
|
|
_infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]]; |
|
_successImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"success" ofType:@"png"]]; |
|
_errorImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"error" ofType:@"png"]]; |
|
|
|
_ringThickness = 2.0f; |
|
_ringRadius = 18.0f; |
|
_ringNoTextRadius = 24.0f; |
|
|
|
_cornerRadius = 14.0f; |
|
|
|
_graceTimeInterval = 0.0f; |
|
_minimumDismissTimeInterval = 5.0; |
|
_maximumDismissTimeInterval = CGFLOAT_MAX; |
|
|
|
_fadeInAnimationDuration = SVProgressHUDDefaultAnimationDuration; |
|
_fadeOutAnimationDuration = SVProgressHUDDefaultAnimationDuration; |
|
|
|
_maxSupportedWindowLevel = UIWindowLevelNormal; |
|
|
|
_hapticsEnabled = NO; |
|
_motionEffectEnabled = YES; |
|
|
|
// Accessibility support |
|
self.accessibilityIdentifier = @"SVProgressHUD"; |
|
self.isAccessibilityElement = YES; |
|
|
|
_isInitializing = NO; |
|
} |
|
return self; |
|
} |
|
|
|
- (void)updateHUDFrame { |
|
// Check if an image or progress ring is displayed |
|
BOOL imageUsed = (self.imageView.image) && !(self.imageView.hidden); |
|
BOOL progressUsed = self.imageView.hidden; |
|
|
|
// Calculate size of string |
|
CGRect labelRect = CGRectZero; |
|
CGFloat labelHeight = 0.0f; |
|
CGFloat labelWidth = 0.0f; |
|
|
|
if(self.statusLabel.text) { |
|
CGSize constraintSize = CGSizeMake(200.0f, 300.0f); |
|
labelRect = [self.statusLabel.text boundingRectWithSize:constraintSize |
|
options:(NSStringDrawingOptions)(NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin) |
|
attributes:@{NSFontAttributeName: self.statusLabel.font} |
|
context:NULL]; |
|
labelHeight = ceilf(CGRectGetHeight(labelRect)); |
|
labelWidth = ceilf(CGRectGetWidth(labelRect)); |
|
} |
|
|
|
// Calculate hud size based on content |
|
// For the beginning use default values, these |
|
// might get update if string is too large etc. |
|
CGFloat hudWidth; |
|
CGFloat hudHeight; |
|
|
|
CGFloat contentWidth = 0.0f; |
|
CGFloat contentHeight = 0.0f; |
|
|
|
if(imageUsed || progressUsed) { |
|
contentWidth = CGRectGetWidth(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame); |
|
contentHeight = CGRectGetHeight(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame); |
|
} |
|
|
|
// |-spacing-content-spacing-| |
|
hudWidth = SVProgressHUDHorizontalSpacing + MAX(labelWidth, contentWidth) + SVProgressHUDHorizontalSpacing; |
|
|
|
// |-spacing-content-(labelSpacing-label-)spacing-| |
|
hudHeight = SVProgressHUDVerticalSpacing + labelHeight + contentHeight + SVProgressHUDVerticalSpacing; |
|
if(self.statusLabel.text && (imageUsed || progressUsed)){ |
|
// Add spacing if both content and label are used |
|
hudHeight += SVProgressHUDLabelSpacing; |
|
} |
|
|
|
// Update values on subviews |
|
self.hudView.bounds = CGRectMake(0.0f, 0.0f, MAX(self.minimumSize.width, hudWidth), MAX(self.minimumSize.height, hudHeight)); |
|
|
|
// Animate value update |
|
[CATransaction begin]; |
|
[CATransaction setDisableActions:YES]; |
|
|
|
// Spinner and image view |
|
CGFloat centerY; |
|
if(self.statusLabel.text) { |
|
CGFloat yOffset = MAX(SVProgressHUDVerticalSpacing, (self.minimumSize.height - contentHeight - SVProgressHUDLabelSpacing - labelHeight) / 2.0f); |
|
centerY = yOffset + contentHeight / 2.0f; |
|
} else { |
|
centerY = CGRectGetMidY(self.hudView.bounds); |
|
} |
|
self.indefiniteAnimatedView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); |
|
if(self.progress != SVProgressHUDUndefinedProgress) { |
|
self.backgroundRingView.center = self.ringView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); |
|
} |
|
self.imageView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); |
|
|
|
// Label |
|
if(imageUsed || progressUsed) { |
|
centerY = CGRectGetMaxY(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame) + SVProgressHUDLabelSpacing + labelHeight / 2.0f; |
|
} else { |
|
centerY = CGRectGetMidY(self.hudView.bounds); |
|
} |
|
self.statusLabel.frame = labelRect; |
|
self.statusLabel.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); |
|
|
|
[CATransaction commit]; |
|
} |
|
|
|
#if TARGET_OS_IOS |
|
- (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation { |
|
UIInterpolatingMotionEffectType xMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis; |
|
UIInterpolatingMotionEffectType yMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis; |
|
[self updateMotionEffectForXMotionEffectType:xMotionEffectType yMotionEffectType:yMotionEffectType]; |
|
} |
|
#endif |
|
|
|
- (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType { |
|
UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:xMotionEffectType]; |
|
effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); |
|
effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); |
|
|
|
UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:yMotionEffectType]; |
|
effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); |
|
effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); |
|
|
|
UIMotionEffectGroup *effectGroup = [UIMotionEffectGroup new]; |
|
effectGroup.motionEffects = @[effectX, effectY]; |
|
|
|
// Clear old motion effect, then add new motion effects |
|
self.hudView.motionEffects = @[]; |
|
[self.hudView addMotionEffect:effectGroup]; |
|
} |
|
|
|
- (void)updateViewHierarchy { |
|
// Add the overlay to the application window if necessary |
|
if(!self.controlView.superview) { |
|
if(self.containerView){ |
|
[self.containerView addSubview:self.controlView]; |
|
} else { |
|
#if !defined(SV_APP_EXTENSIONS) |
|
[self.frontWindow addSubview:self.controlView]; |
|
#else |
|
// If SVProgressHUD is used inside an app extension add it to the given view |
|
if(self.viewForExtension) { |
|
[self.viewForExtension addSubview:self.controlView]; |
|
} |
|
#endif |
|
} |
|
} else { |
|
// The HUD is already on screen, but maybe not in front. Therefore |
|
// ensure that overlay will be on top of rootViewController (which may |
|
// be changed during runtime). |
|
[self.controlView.superview bringSubviewToFront:self.controlView]; |
|
} |
|
|
|
// Add self to the overlay view |
|
if(!self.superview) { |
|
[self.controlView addSubview:self]; |
|
} |
|
} |
|
|
|
- (void)setStatus:(NSString*)status { |
|
self.statusLabel.text = status; |
|
self.statusLabel.hidden = status.length == 0; |
|
[self updateHUDFrame]; |
|
} |
|
|
|
- (void)setGraceTimer:(NSTimer*)timer { |
|
if(_graceTimer) { |
|
[_graceTimer invalidate]; |
|
_graceTimer = nil; |
|
} |
|
if(timer) { |
|
_graceTimer = timer; |
|
} |
|
} |
|
|
|
- (void)setFadeOutTimer:(NSTimer*)timer { |
|
if(_fadeOutTimer) { |
|
[_fadeOutTimer invalidate]; |
|
_fadeOutTimer = nil; |
|
} |
|
if(timer) { |
|
_fadeOutTimer = timer; |
|
} |
|
} |
|
|
|
|
|
#pragma mark - Notifications and their handling |
|
|
|
- (void)registerNotifications { |
|
#if TARGET_OS_IOS |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIApplicationDidChangeStatusBarOrientationNotification |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIKeyboardWillHideNotification |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIKeyboardDidHideNotification |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIKeyboardWillShowNotification |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIKeyboardDidShowNotification |
|
object:nil]; |
|
#endif |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(positionHUD:) |
|
name:UIApplicationDidBecomeActiveNotification |
|
object:nil]; |
|
} |
|
|
|
- (NSDictionary*)notificationUserInfo { |
|
return (self.statusLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.statusLabel.text} : nil); |
|
} |
|
|
|
- (void)positionHUD:(NSNotification*)notification { |
|
CGFloat keyboardHeight = 0.0f; |
|
double animationDuration = 0.0; |
|
|
|
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS |
|
self.frame = [self getCurrentWindow].bounds; |
|
UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation; |
|
#elif !defined(SV_APP_EXTENSIONS) && !TARGET_OS_IOS |
|
self.frame= [UIApplication sharedApplication].keyWindow.bounds; |
|
#else |
|
if (self.viewForExtension) { |
|
self.frame = self.viewForExtension.frame; |
|
} else { |
|
self.frame = UIScreen.mainScreen.bounds; |
|
} |
|
#if TARGET_OS_IOS |
|
UIInterfaceOrientation orientation = CGRectGetWidth(self.frame) > CGRectGetHeight(self.frame) ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait; |
|
#endif |
|
#endif |
|
|
|
#if TARGET_OS_IOS |
|
// Get keyboardHeight in regard to current state |
|
if(notification) { |
|
NSDictionary* keyboardInfo = [notification userInfo]; |
|
CGRect keyboardFrame = [keyboardInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; |
|
animationDuration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; |
|
|
|
if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { |
|
keyboardHeight = CGRectGetWidth(keyboardFrame); |
|
|
|
if(UIInterfaceOrientationIsPortrait(orientation)) { |
|
keyboardHeight = CGRectGetHeight(keyboardFrame); |
|
} |
|
} |
|
} else { |
|
keyboardHeight = self.visibleKeyboardHeight; |
|
} |
|
#endif |
|
|
|
// Get the currently active frame of the display (depends on orientation) |
|
CGRect orientationFrame = self.bounds; |
|
|
|
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS |
|
CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; |
|
#else |
|
CGRect statusBarFrame = CGRectZero; |
|
#endif |
|
|
|
if (_motionEffectEnabled) { |
|
#if TARGET_OS_IOS |
|
// Update the motion effects in regard to orientation |
|
[self updateMotionEffectForOrientation:orientation]; |
|
#else |
|
[self updateMotionEffectForXMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis yMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; |
|
#endif |
|
} |
|
|
|
// Calculate available height for display |
|
CGFloat activeHeight = CGRectGetHeight(orientationFrame); |
|
if(keyboardHeight > 0) { |
|
activeHeight += CGRectGetHeight(statusBarFrame) * 2; |
|
} |
|
activeHeight -= keyboardHeight; |
|
|
|
CGFloat posX = CGRectGetMidX(orientationFrame); |
|
CGFloat posY = floorf(activeHeight*0.45f); |
|
|
|
CGFloat rotateAngle = 0.0; |
|
CGPoint newCenter = CGPointMake(posX, posY); |
|
|
|
if(notification) { |
|
// Animate update if notification was present |
|
[UIView animateWithDuration:animationDuration |
|
delay:0 |
|
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) |
|
animations:^{ |
|
[self moveToPoint:newCenter rotateAngle:rotateAngle]; |
|
[self.hudView setNeedsDisplay]; |
|
} completion:nil]; |
|
} else { |
|
[self moveToPoint:newCenter rotateAngle:rotateAngle]; |
|
} |
|
} |
|
|
|
- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { |
|
self.hudView.transform = CGAffineTransformMakeRotation(angle); |
|
if (self.containerView) { |
|
self.hudView.center = CGPointMake(self.containerView.center.x + self.offsetFromCenter.horizontal, self.containerView.center.y + self.offsetFromCenter.vertical); |
|
} else { |
|
self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical); |
|
} |
|
} |
|
|
|
|
|
#pragma mark - Event handling |
|
|
|
- (void)controlViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event { |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification |
|
object:self |
|
userInfo:[self notificationUserInfo]]; |
|
|
|
UITouch *touch = event.allTouches.anyObject; |
|
CGPoint touchLocation = [touch locationInView:self]; |
|
|
|
if(CGRectContainsPoint(self.hudView.frame, touchLocation)) { |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidTouchDownInsideNotification |
|
object:self |
|
userInfo:[self notificationUserInfo]]; |
|
} |
|
} |
|
|
|
|
|
#pragma mark - Master show/dismiss methods |
|
|
|
- (void)showProgress:(float)progress status:(NSString*)status { |
|
__weak SVProgressHUD *weakSelf = self; |
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ |
|
__strong SVProgressHUD *strongSelf = weakSelf; |
|
if(strongSelf){ |
|
if(strongSelf.fadeOutTimer) { |
|
strongSelf.activityCount = 0; |
|
} |
|
|
|
// Stop timer |
|
strongSelf.fadeOutTimer = nil; |
|
strongSelf.graceTimer = nil; |
|
|
|
// Update / Check view hierarchy to ensure the HUD is visible |
|
[strongSelf updateViewHierarchy]; |
|
|
|
// Reset imageView and fadeout timer if an image is currently displayed |
|
strongSelf.imageView.hidden = YES; |
|
strongSelf.imageView.image = nil; |
|
|
|
// Update text and set progress to the given value |
|
strongSelf.statusLabel.hidden = status.length == 0; |
|
strongSelf.statusLabel.text = status; |
|
strongSelf.progress = progress; |
|
|
|
// Choose the "right" indicator depending on the progress |
|
if(progress >= 0) { |
|
// Cancel the indefiniteAnimatedView, then show the ringLayer |
|
[strongSelf cancelIndefiniteAnimatedViewAnimation]; |
|
|
|
// Add ring to HUD |
|
if(!strongSelf.ringView.superview){ |
|
[strongSelf.hudView.contentView addSubview:strongSelf.ringView]; |
|
} |
|
if(!strongSelf.backgroundRingView.superview){ |
|
[strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView]; |
|
} |
|
|
|
// Set progress animated |
|
[CATransaction begin]; |
|
[CATransaction setDisableActions:YES]; |
|
strongSelf.ringView.strokeEnd = progress; |
|
[CATransaction commit]; |
|
|
|
// Update the activity count |
|
if(progress == 0) { |
|
strongSelf.activityCount++; |
|
} |
|
} else { |
|
// Cancel the ringLayer animation, then show the indefiniteAnimatedView |
|
[strongSelf cancelRingLayerAnimation]; |
|
|
|
// Add indefiniteAnimatedView to HUD |
|
[strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView]; |
|
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) { |
|
[(id)strongSelf.indefiniteAnimatedView startAnimating]; |
|
} |
|
|
|
// Update the activity count |
|
strongSelf.activityCount++; |
|
} |
|
|
|
// Fade in delayed if a grace time is set |
|
if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) { |
|
strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO]; |
|
[[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes]; |
|
} else { |
|
[strongSelf fadeIn:nil]; |
|
} |
|
|
|
// Tell the Haptics Generator to prepare for feedback, which may come soon |
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
if (@available(iOS 10.0, *)) { |
|
[strongSelf.hapticGenerator prepare]; |
|
} |
|
#endif |
|
} |
|
}]; |
|
} |
|
|
|
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration { |
|
__weak SVProgressHUD *weakSelf = self; |
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ |
|
__strong SVProgressHUD *strongSelf = weakSelf; |
|
if(strongSelf){ |
|
// Stop timer |
|
strongSelf.fadeOutTimer = nil; |
|
strongSelf.graceTimer = nil; |
|
|
|
// Update / Check view hierarchy to ensure the HUD is visible |
|
[strongSelf updateViewHierarchy]; |
|
|
|
// Reset progress and cancel any running animation |
|
strongSelf.progress = SVProgressHUDUndefinedProgress; |
|
[strongSelf cancelRingLayerAnimation]; |
|
[strongSelf cancelIndefiniteAnimatedViewAnimation]; |
|
|
|
// Update imageView |
|
if (self.shouldTintImages) { |
|
if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) { |
|
strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
|
} |
|
strongSelf.imageView.tintColor = strongSelf.foregroundImageColorForStyle; |
|
} else { |
|
strongSelf.imageView.image = image; |
|
} |
|
strongSelf.imageView.hidden = NO; |
|
|
|
// Update text |
|
strongSelf.statusLabel.hidden = status.length == 0; |
|
strongSelf.statusLabel.text = status; |
|
|
|
// Fade in delayed if a grace time is set |
|
// An image will be dismissed automatically. Thus pass the duration as userInfo. |
|
if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) { |
|
strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:@(duration) repeats:NO]; |
|
[[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes]; |
|
} else { |
|
[strongSelf fadeIn:@(duration)]; |
|
} |
|
} |
|
}]; |
|
} |
|
|
|
- (void)fadeIn:(id)data { |
|
// Update the HUDs frame to the new content and position HUD |
|
[self updateHUDFrame]; |
|
[self positionHUD:nil]; |
|
|
|
// Update accessibility as well as user interaction |
|
// \n cause to read text twice so remove "\n" new line character before setting up accessiblity label |
|
NSString *accessibilityString = [[self.statusLabel.text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@" "]; |
|
if(self.defaultMaskType != SVProgressHUDMaskTypeNone) { |
|
self.controlView.userInteractionEnabled = YES; |
|
self.accessibilityLabel = accessibilityString ?: NSLocalizedString(@"Loading", nil); |
|
self.isAccessibilityElement = YES; |
|
self.controlView.accessibilityViewIsModal = YES; |
|
} else { |
|
self.controlView.userInteractionEnabled = NO; |
|
self.hudView.accessibilityLabel = accessibilityString ?: NSLocalizedString(@"Loading", nil); |
|
self.hudView.isAccessibilityElement = YES; |
|
self.controlView.accessibilityViewIsModal = NO; |
|
} |
|
|
|
// Get duration |
|
id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data; |
|
|
|
// Show if not already visible |
|
if(self.backgroundView.alpha != 1.0f) { |
|
// Post notification to inform user |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification |
|
object:self |
|
userInfo:[self notificationUserInfo]]; |
|
|
|
// Shrink HUD to to make a nice appear / pop up animation |
|
self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f); |
|
|
|
__block void (^animationsBlock)(void) = ^{ |
|
// Zoom HUD a little to make a nice appear / pop up animation |
|
self.hudView.transform = CGAffineTransformIdentity; |
|
|
|
// Fade in all effects (colors, blur, etc.) |
|
[self fadeInEffects]; |
|
}; |
|
|
|
__block void (^completionBlock)(void) = ^{ |
|
// Check if we really achieved to show the HUD (<=> alpha) |
|
// and the change of these values has not been cancelled in between e.g. due to a dismissal |
|
if(self.backgroundView.alpha == 1.0f){ |
|
// Register observer <=> we now have to handle orientation changes etc. |
|
[self registerNotifications]; |
|
|
|
// Post notification to inform user |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification |
|
object:self |
|
userInfo:[self notificationUserInfo]]; |
|
|
|
// Update accessibility |
|
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); |
|
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text); |
|
|
|
// Dismiss automatically if a duration was passed as userInfo. We start a timer |
|
// which then will call dismiss after the predefined duration |
|
if(duration){ |
|
self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; |
|
[[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes]; |
|
} |
|
} |
|
}; |
|
|
|
// Animate appearance |
|
if (self.fadeInAnimationDuration > 0) { |
|
// Animate appearance |
|
[UIView animateWithDuration:self.fadeInAnimationDuration |
|
delay:0 |
|
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) |
|
animations:^{ |
|
animationsBlock(); |
|
} completion:^(BOOL finished) { |
|
completionBlock(); |
|
}]; |
|
} else { |
|
animationsBlock(); |
|
completionBlock(); |
|
} |
|
|
|
// Inform iOS to redraw the view hierarchy |
|
[self setNeedsDisplay]; |
|
} else { |
|
// Update accessibility |
|
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); |
|
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text); |
|
|
|
// Dismiss automatically if a duration was passed as userInfo. We start a timer |
|
// which then will call dismiss after the predefined duration |
|
if(duration){ |
|
self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; |
|
[[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes]; |
|
} |
|
} |
|
} |
|
|
|
- (void)dismiss { |
|
[self dismissWithDelay:0.0 completion:nil]; |
|
} |
|
|
|
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { |
|
__weak SVProgressHUD *weakSelf = self; |
|
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ |
|
__strong SVProgressHUD *strongSelf = weakSelf; |
|
if(strongSelf){ |
|
|
|
// Post notification to inform user |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification |
|
object:nil |
|
userInfo:[strongSelf notificationUserInfo]]; |
|
|
|
// Reset activity count |
|
strongSelf.activityCount = 0; |
|
|
|
__block void (^animationsBlock)(void) = ^{ |
|
// Shrink HUD a little to make a nice disappear animation |
|
strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f); |
|
|
|
// Fade out all effects (colors, blur, etc.) |
|
[strongSelf fadeOutEffects]; |
|
}; |
|
|
|
__block void (^completionBlock)(void) = ^{ |
|
// Check if we really achieved to dismiss the HUD (<=> alpha values are applied) |
|
// and the change of these values has not been cancelled in between e.g. due to a new show |
|
if(self.backgroundView.alpha == 0.0f){ |
|
// Clean up view hierarchy (overlays) |
|
[strongSelf.controlView removeFromSuperview]; |
|
[strongSelf.backgroundView removeFromSuperview]; |
|
[strongSelf.hudView removeFromSuperview]; |
|
[strongSelf removeFromSuperview]; |
|
|
|
// Reset progress and cancel any running animation |
|
strongSelf.progress = SVProgressHUDUndefinedProgress; |
|
[strongSelf cancelRingLayerAnimation]; |
|
[strongSelf cancelIndefiniteAnimatedViewAnimation]; |
|
|
|
// Remove observer <=> we do not have to handle orientation changes etc. |
|
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf]; |
|
|
|
// Post notification to inform user |
|
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification |
|
object:strongSelf |
|
userInfo:[strongSelf notificationUserInfo]]; |
|
|
|
// Tell the rootViewController to update the StatusBar appearance |
|
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS |
|
UIViewController *rootController = [self getCurrentWindow].rootViewController; |
|
[rootController setNeedsStatusBarAppearanceUpdate]; |
|
#endif |
|
|
|
// Run an (optional) completionHandler |
|
if (completion) { |
|
completion(); |
|
} |
|
} |
|
}; |
|
|
|
// UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected |
|
// When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current |
|
// values to check if an animation is necessary. The evaluation happens at function call time and not |
|
// after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after. |
|
|
|
dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); |
|
dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{ |
|
|
|
// Stop timer |
|
strongSelf.graceTimer = nil; |
|
|
|
if (strongSelf.fadeOutAnimationDuration > 0) { |
|
// Animate appearance |
|
[UIView animateWithDuration:strongSelf.fadeOutAnimationDuration |
|
delay:0 |
|
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) |
|
animations:^{ |
|
animationsBlock(); |
|
} completion:^(BOOL finished) { |
|
completionBlock(); |
|
}]; |
|
} else { |
|
animationsBlock(); |
|
completionBlock(); |
|
} |
|
}); |
|
|
|
// Inform iOS to redraw the view hierarchy |
|
[strongSelf setNeedsDisplay]; |
|
} |
|
}]; |
|
} |
|
|
|
|
|
#pragma mark - Ring progress animation |
|
|
|
- (UIView*)indefiniteAnimatedView { |
|
// Get the correct spinner for defaultAnimationType |
|
if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat){ |
|
// Check if spinner exists and is an object of different class |
|
if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[SVIndefiniteAnimatedView class]]){ |
|
[_indefiniteAnimatedView removeFromSuperview]; |
|
_indefiniteAnimatedView = nil; |
|
} |
|
|
|
if(!_indefiniteAnimatedView){ |
|
_indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero]; |
|
} |
|
|
|
// Update styling |
|
SVIndefiniteAnimatedView *indefiniteAnimatedView = (SVIndefiniteAnimatedView*)_indefiniteAnimatedView; |
|
indefiniteAnimatedView.strokeColor = self.foregroundImageColorForStyle; |
|
indefiniteAnimatedView.strokeThickness = self.ringThickness; |
|
indefiniteAnimatedView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; |
|
} else { |
|
// Check if spinner exists and is an object of different class |
|
if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[UIActivityIndicatorView class]]){ |
|
[_indefiniteAnimatedView removeFromSuperview]; |
|
_indefiniteAnimatedView = nil; |
|
} |
|
|
|
if(!_indefiniteAnimatedView){ |
|
_indefiniteAnimatedView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; |
|
} |
|
|
|
// Update styling |
|
UIActivityIndicatorView *activityIndicatorView = (UIActivityIndicatorView*)_indefiniteAnimatedView; |
|
activityIndicatorView.color = self.foregroundImageColorForStyle; |
|
} |
|
[_indefiniteAnimatedView sizeToFit]; |
|
|
|
return _indefiniteAnimatedView; |
|
} |
|
|
|
- (SVProgressAnimatedView*)ringView { |
|
if(!_ringView) { |
|
_ringView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; |
|
} |
|
|
|
// Update styling |
|
_ringView.strokeColor = self.foregroundImageColorForStyle; |
|
_ringView.strokeThickness = self.ringThickness; |
|
_ringView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; |
|
|
|
return _ringView; |
|
} |
|
|
|
- (SVProgressAnimatedView*)backgroundRingView { |
|
if(!_backgroundRingView) { |
|
_backgroundRingView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; |
|
_backgroundRingView.strokeEnd = 1.0f; |
|
} |
|
|
|
// Update styling |
|
_backgroundRingView.strokeColor = [self.foregroundImageColorForStyle colorWithAlphaComponent:0.1f]; |
|
_backgroundRingView.strokeThickness = self.ringThickness; |
|
_backgroundRingView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; |
|
|
|
return _backgroundRingView; |
|
} |
|
|
|
- (void)cancelRingLayerAnimation { |
|
// Animate value update, stop animation |
|
[CATransaction begin]; |
|
[CATransaction setDisableActions:YES]; |
|
|
|
[self.hudView.layer removeAllAnimations]; |
|
self.ringView.strokeEnd = 0.0f; |
|
|
|
[CATransaction commit]; |
|
|
|
// Remove from view |
|
[self.ringView removeFromSuperview]; |
|
[self.backgroundRingView removeFromSuperview]; |
|
} |
|
|
|
- (void)cancelIndefiniteAnimatedViewAnimation { |
|
// Stop animation |
|
if([self.indefiniteAnimatedView respondsToSelector:@selector(stopAnimating)]) { |
|
[(id)self.indefiniteAnimatedView stopAnimating]; |
|
} |
|
// Remove from view |
|
[self.indefiniteAnimatedView removeFromSuperview]; |
|
} |
|
|
|
|
|
#pragma mark - Utilities |
|
|
|
+ (BOOL)isVisible { |
|
// Checking one alpha value is sufficient as they are all the same |
|
return [self sharedView].backgroundView.alpha > 0.0f; |
|
} |
|
|
|
|
|
#pragma mark - Getters |
|
|
|
+ (NSTimeInterval)displayDurationForString:(NSString*)string { |
|
CGFloat minimum = MAX((CGFloat)string.length * 0.06 + 0.5, [self sharedView].minimumDismissTimeInterval); |
|
return MIN(minimum, [self sharedView].maximumDismissTimeInterval); |
|
} |
|
|
|
- (UIColor*)foregroundColorForStyle { |
|
if(self.defaultStyle == SVProgressHUDStyleLight) { |
|
return [UIColor blackColor]; |
|
} else if(self.defaultStyle == SVProgressHUDStyleDark) { |
|
return [UIColor whiteColor]; |
|
} else { |
|
return self.foregroundColor; |
|
} |
|
} |
|
|
|
- (UIColor*)foregroundImageColorForStyle { |
|
if (self.foregroundImageColor) { |
|
return self.foregroundImageColor; |
|
} else { |
|
return [self foregroundColorForStyle]; |
|
} |
|
} |
|
|
|
- (UIColor*)backgroundColorForStyle { |
|
if(self.defaultStyle == SVProgressHUDStyleLight) { |
|
return [UIColor whiteColor]; |
|
} else if(self.defaultStyle == SVProgressHUDStyleDark) { |
|
return [UIColor blackColor]; |
|
} else { |
|
return self.backgroundColor; |
|
} |
|
} |
|
|
|
- (UIWindow *)getCurrentWindow |
|
{ |
|
UIWindow *current; |
|
|
|
if (@available(iOS 13.0, *)) { |
|
NSPredicate *foregroundActivePredict = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { |
|
|
|
return ([evaluatedObject activationState] == UISceneActivationStateForegroundActive); |
|
}]; |
|
|
|
NSSet<UIScene *> *setOfScene = [[[UIApplication sharedApplication] connectedScenes] filteredSetUsingPredicate:foregroundActivePredict]; |
|
UIWindowScene *windowScene = (UIWindowScene *)[[setOfScene allObjects] firstObject]; |
|
current = [[UIWindow alloc] initWithWindowScene:windowScene]; |
|
}else{ |
|
current = [[[UIApplication sharedApplication] delegate] window]; |
|
} |
|
return current; |
|
} |
|
|
|
- (UIControl*)controlView { |
|
if(!_controlView) { |
|
_controlView = [UIControl new]; |
|
_controlView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
|
_controlView.backgroundColor = [UIColor clearColor]; |
|
_controlView.userInteractionEnabled = YES; |
|
[_controlView addTarget:self action:@selector(controlViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown]; |
|
} |
|
|
|
// Update frames |
|
#if !defined(SV_APP_EXTENSIONS) |
|
CGRect windowBounds = [self getCurrentWindow].bounds; |
|
_controlView.frame = windowBounds; |
|
#else |
|
_controlView.frame = [UIScreen mainScreen].bounds; |
|
#endif |
|
|
|
return _controlView; |
|
} |
|
|
|
-(UIView *)backgroundView { |
|
if(!_backgroundView){ |
|
_backgroundView = [UIView new]; |
|
_backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
|
} |
|
if(!_backgroundView.superview){ |
|
[self insertSubview:_backgroundView belowSubview:self.hudView]; |
|
} |
|
|
|
// Update styling |
|
if(self.defaultMaskType == SVProgressHUDMaskTypeGradient){ |
|
if(!_backgroundRadialGradientLayer){ |
|
_backgroundRadialGradientLayer = [SVRadialGradientLayer layer]; |
|
} |
|
if(!_backgroundRadialGradientLayer.superlayer){ |
|
[_backgroundView.layer insertSublayer:_backgroundRadialGradientLayer atIndex:0]; |
|
} |
|
_backgroundView.backgroundColor = [UIColor clearColor]; |
|
} else { |
|
if(_backgroundRadialGradientLayer && _backgroundRadialGradientLayer.superlayer){ |
|
[_backgroundRadialGradientLayer removeFromSuperlayer]; |
|
} |
|
if(self.defaultMaskType == SVProgressHUDMaskTypeBlack){ |
|
_backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4]; |
|
} else if(self.defaultMaskType == SVProgressHUDMaskTypeCustom){ |
|
_backgroundView.backgroundColor = self.backgroundLayerColor; |
|
} else { |
|
_backgroundView.backgroundColor = [UIColor clearColor]; |
|
} |
|
} |
|
|
|
// Update frame |
|
if(_backgroundView){ |
|
_backgroundView.frame = self.bounds; |
|
} |
|
if(_backgroundRadialGradientLayer){ |
|
_backgroundRadialGradientLayer.frame = self.bounds; |
|
|
|
// Calculate the new center of the gradient, it may change if keyboard is visible |
|
CGPoint gradientCenter = self.center; |
|
gradientCenter.y = (self.bounds.size.height - self.visibleKeyboardHeight)/2; |
|
_backgroundRadialGradientLayer.gradientCenter = gradientCenter; |
|
[_backgroundRadialGradientLayer setNeedsDisplay]; |
|
} |
|
|
|
return _backgroundView; |
|
} |
|
- (UIVisualEffectView*)hudView { |
|
if(!_hudView) { |
|
_hudView = [UIVisualEffectView new]; |
|
_hudView.layer.masksToBounds = YES; |
|
_hudView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin; |
|
} |
|
if(!_hudView.superview) { |
|
[self addSubview:_hudView]; |
|
} |
|
|
|
// Update styling |
|
_hudView.layer.cornerRadius = self.cornerRadius; |
|
|
|
return _hudView; |
|
} |
|
|
|
- (UILabel*)statusLabel { |
|
if(!_statusLabel) { |
|
_statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
|
_statusLabel.backgroundColor = [UIColor clearColor]; |
|
_statusLabel.adjustsFontSizeToFitWidth = YES; |
|
_statusLabel.textAlignment = NSTextAlignmentCenter; |
|
_statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; |
|
_statusLabel.numberOfLines = 0; |
|
} |
|
if(!_statusLabel.superview) { |
|
[self.hudView.contentView addSubview:_statusLabel]; |
|
} |
|
|
|
// Update styling |
|
_statusLabel.textColor = self.foregroundColorForStyle; |
|
_statusLabel.font = self.font; |
|
|
|
return _statusLabel; |
|
} |
|
|
|
- (UIImageView*)imageView { |
|
if(_imageView && !CGSizeEqualToSize(_imageView.bounds.size, _imageViewSize)) { |
|
[_imageView removeFromSuperview]; |
|
_imageView = nil; |
|
} |
|
|
|
if(!_imageView) { |
|
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _imageViewSize.width, _imageViewSize.height)]; |
|
} |
|
if(!_imageView.superview) { |
|
[self.hudView.contentView addSubview:_imageView]; |
|
} |
|
|
|
return _imageView; |
|
} |
|
|
|
|
|
#pragma mark - Helper |
|
|
|
- (CGFloat)visibleKeyboardHeight { |
|
#if !defined(SV_APP_EXTENSIONS) |
|
UIWindow *keyboardWindow = nil; |
|
for (UIWindow *testWindow in UIApplication.sharedApplication.windows) { |
|
if(![testWindow.class isEqual:UIWindow.class]) { |
|
keyboardWindow = testWindow; |
|
break; |
|
} |
|
} |
|
|
|
for (__strong UIView *possibleKeyboard in keyboardWindow.subviews) { |
|
NSString *viewName = NSStringFromClass(possibleKeyboard.class); |
|
if([viewName hasPrefix:@"UI"]){ |
|
if([viewName hasSuffix:@"PeripheralHostView"] || [viewName hasSuffix:@"Keyboard"]){ |
|
return CGRectGetHeight(possibleKeyboard.bounds); |
|
} else if ([viewName hasSuffix:@"InputSetContainerView"]){ |
|
for (__strong UIView *possibleKeyboardSubview in possibleKeyboard.subviews) { |
|
viewName = NSStringFromClass(possibleKeyboardSubview.class); |
|
if([viewName hasPrefix:@"UI"] && [viewName hasSuffix:@"InputSetHostView"]) { |
|
CGRect convertedRect = [possibleKeyboard convertRect:possibleKeyboardSubview.frame toView:self]; |
|
CGRect intersectedRect = CGRectIntersection(convertedRect, self.bounds); |
|
if (!CGRectIsNull(intersectedRect)) { |
|
return CGRectGetHeight(intersectedRect); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
return 0; |
|
} |
|
|
|
- (UIWindow *)frontWindow { |
|
#if !defined(SV_APP_EXTENSIONS) |
|
NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; |
|
for (UIWindow *window in frontToBackWindows) { |
|
BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen; |
|
BOOL windowIsVisible = !window.hidden && window.alpha > 0; |
|
BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal && window.windowLevel <= self.maxSupportedWindowLevel); |
|
BOOL windowKeyWindow = window.isKeyWindow; |
|
|
|
if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) { |
|
return window; |
|
} |
|
} |
|
#endif |
|
return nil; |
|
} |
|
|
|
- (void)fadeInEffects { |
|
if(self.defaultStyle != SVProgressHUDStyleCustom) { |
|
// Add blur effect |
|
UIBlurEffectStyle blurEffectStyle = self.defaultStyle == SVProgressHUDStyleDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight; |
|
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurEffectStyle]; |
|
self.hudView.effect = blurEffect; |
|
|
|
// We omit UIVibrancy effect and use a suitable background color as an alternative. |
|
// This will make everything more readable. See the following for details: |
|
// https://www.omnigroup.com/developer/how-to-make-text-in-a-uivisualeffectview-readable-on-any-background |
|
|
|
self.hudView.backgroundColor = [self.backgroundColorForStyle colorWithAlphaComponent:0.6f]; |
|
} else { |
|
self.hudView.effect = self.hudViewCustomBlurEffect; |
|
self.hudView.backgroundColor = self.backgroundColorForStyle; |
|
} |
|
|
|
// Fade in views |
|
self.backgroundView.alpha = 1.0f; |
|
|
|
self.imageView.alpha = 1.0f; |
|
self.statusLabel.alpha = 1.0f; |
|
self.indefiniteAnimatedView.alpha = 1.0f; |
|
self.ringView.alpha = self.backgroundRingView.alpha = 1.0f; |
|
} |
|
|
|
- (void)fadeOutEffects |
|
{ |
|
if(self.defaultStyle != SVProgressHUDStyleCustom) { |
|
// Remove blur effect |
|
self.hudView.effect = nil; |
|
} |
|
|
|
// Remove background color |
|
self.hudView.backgroundColor = [UIColor clearColor]; |
|
|
|
// Fade out views |
|
self.backgroundView.alpha = 0.0f; |
|
|
|
self.imageView.alpha = 0.0f; |
|
self.statusLabel.alpha = 0.0f; |
|
self.indefiniteAnimatedView.alpha = 0.0f; |
|
self.ringView.alpha = self.backgroundRingView.alpha = 0.0f; |
|
} |
|
|
|
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 |
|
- (UINotificationFeedbackGenerator *)hapticGenerator NS_AVAILABLE_IOS(10_0) { |
|
// Only return if haptics are enabled |
|
if(!self.hapticsEnabled) { |
|
return nil; |
|
} |
|
|
|
if(!_hapticGenerator) { |
|
_hapticGenerator = [[UINotificationFeedbackGenerator alloc] init]; |
|
} |
|
return _hapticGenerator; |
|
} |
|
#endif |
|
|
|
|
|
#pragma mark - UIAppearance Setters |
|
|
|
- (void)setDefaultStyle:(SVProgressHUDStyle)style { |
|
if (!_isInitializing) _defaultStyle = style; |
|
} |
|
|
|
- (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { |
|
if (!_isInitializing) _defaultMaskType = maskType; |
|
} |
|
|
|
- (void)setDefaultAnimationType:(SVProgressHUDAnimationType)animationType { |
|
if (!_isInitializing) _defaultAnimationType = animationType; |
|
} |
|
|
|
- (void)setContainerView:(UIView *)containerView { |
|
if (!_isInitializing) _containerView = containerView; |
|
} |
|
|
|
- (void)setMinimumSize:(CGSize)minimumSize { |
|
if (!_isInitializing) _minimumSize = minimumSize; |
|
} |
|
|
|
- (void)setRingThickness:(CGFloat)ringThickness { |
|
if (!_isInitializing) _ringThickness = ringThickness; |
|
} |
|
|
|
- (void)setRingRadius:(CGFloat)ringRadius { |
|
if (!_isInitializing) _ringRadius = ringRadius; |
|
} |
|
|
|
- (void)setRingNoTextRadius:(CGFloat)ringNoTextRadius { |
|
if (!_isInitializing) _ringNoTextRadius = ringNoTextRadius; |
|
} |
|
|
|
- (void)setCornerRadius:(CGFloat)cornerRadius { |
|
if (!_isInitializing) _cornerRadius = cornerRadius; |
|
} |
|
|
|
- (void)setFont:(UIFont*)font { |
|
if (!_isInitializing) _font = font; |
|
} |
|
|
|
- (void)setForegroundColor:(UIColor*)color { |
|
if (!_isInitializing) _foregroundColor = color; |
|
} |
|
|
|
- (void)setForegroundImageColor:(UIColor *)color { |
|
if (!_isInitializing) _foregroundImageColor = color; |
|
} |
|
|
|
- (void)setBackgroundColor:(UIColor*)color { |
|
if (!_isInitializing) _backgroundColor = color; |
|
} |
|
|
|
- (void)setBackgroundLayerColor:(UIColor*)color { |
|
if (!_isInitializing) _backgroundLayerColor = color; |
|
} |
|
|
|
- (void)setShouldTintImages:(BOOL)shouldTintImages { |
|
if (!_isInitializing) _shouldTintImages = shouldTintImages; |
|
} |
|
|
|
- (void)setInfoImage:(UIImage*)image { |
|
if (!_isInitializing) _infoImage = image; |
|
} |
|
|
|
- (void)setSuccessImage:(UIImage*)image { |
|
if (!_isInitializing) _successImage = image; |
|
} |
|
|
|
- (void)setErrorImage:(UIImage*)image { |
|
if (!_isInitializing) _errorImage = image; |
|
} |
|
|
|
- (void)setViewForExtension:(UIView*)view { |
|
if (!_isInitializing) _viewForExtension = view; |
|
} |
|
|
|
- (void)setOffsetFromCenter:(UIOffset)offset { |
|
if (!_isInitializing) _offsetFromCenter = offset; |
|
} |
|
|
|
- (void)setMinimumDismissTimeInterval:(NSTimeInterval)minimumDismissTimeInterval { |
|
if (!_isInitializing) _minimumDismissTimeInterval = minimumDismissTimeInterval; |
|
} |
|
|
|
- (void)setFadeInAnimationDuration:(NSTimeInterval)duration { |
|
if (!_isInitializing) _fadeInAnimationDuration = duration; |
|
} |
|
|
|
- (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { |
|
if (!_isInitializing) _fadeOutAnimationDuration = duration; |
|
} |
|
|
|
- (void)setMaxSupportedWindowLevel:(UIWindowLevel)maxSupportedWindowLevel { |
|
if (!_isInitializing) _maxSupportedWindowLevel = maxSupportedWindowLevel; |
|
} |
|
|
|
@end
|
|
|