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

//
// 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