SVProgressHUD框架解析
SVProgressHUD框架的简单易用让他深受多数开发者的青睐,在github上获得了11.5k个小星星!这个框架提供了获取数据时添加加载提示遮罩,获取数据加载完成时显示成功,亦或数据获取失败时的遮罩提示。
演示效果
代码结构
SVIndefiniteAnimatedView实现
- (CAShapeLayer*)indefiniteAnimatedLayer {
if(!_indefiniteAnimatedLayer) {
CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5);
UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES];
_indefiniteAnimatedLayer = [CAShapeLayer layer];
// 省略部分代码
// 设置绘制的圆形_indefiniteAnimatedLayer的相关属性
_indefiniteAnimatedLayer.path = smoothedPath.CGPath;
CALayer *maskLayer = [CALayer layer];
// 省略部分代码
// 设置遮罩_indefiniteAnimatedLayer的相关属性
_indefiniteAnimatedLayer.mask = maskLayer;
// 设置圆形layer旋转动画
NSTimeInterval animationDuration = 1;
CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.fromValue = (id) 0;
animation.toValue = @(M_PI*2);
animation.duration = animationDuration;
animation.timingFunction = linearCurve;
animation.removedOnCompletion = NO;
animation.repeatCount = INFINITY;
animation.fillMode = kCAFillModeForwards;
animation.autoreverses = NO;
[_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];
// 设置strokeStart和strokeEnd参数使圆形头和尾产生间距
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = animationDuration;
animationGroup.repeatCount = INFINITY;
animationGroup.removedOnCompletion = NO;
animationGroup.timingFunction = linearCurve;
CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @0.015;
strokeStartAnimation.toValue = @0.515;
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @0.485;
strokeEndAnimation.toValue = @0.985;
animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];
[_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];
}
return _indefiniteAnimatedLayer;
}
- 原理步骤:
- 使用图层layer并通过贝塞尔曲线绘制一个圆圈;
- 通过获取png图片angle-mask设置为图层layer的mask遮罩,图片可以设置不同透明值来控制图层layer的显示样式;当透明值为0时,图层layer的mask遮罩部分则不会显示,当透明值大于0时,则会按照透明值比例显示图层layer的mask遮罩部分;
- 通过CABasicAnimation动画设置图层_indefiniteAnimatedLayer的transform.rotation属性使其旋转;
- 通过CAAnimationGroup动画组来设置_indefiniteAnimatedLayer图层strokeStart和strokeEnd属性,使其头尾之间存在一定的距离;
- SVIndefiniteAnimatedView用于旋转加载的提示样式;
SVProgressAnimatedView
- (CAShapeLayer*)ringAnimatedLayer {
if(!_ringAnimatedLayer) {
CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5);
UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES];
_ringAnimatedLayer = [CAShapeLayer layer];
_ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
_ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
_ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
_ringAnimatedLayer.strokeColor = self.strokeColor.CGColor;
_ringAnimatedLayer.lineWidth = self.strokeThickness;
_ringAnimatedLayer.lineCap = kCALineCapRound;
_ringAnimatedLayer.lineJoin = kCALineJoinBevel;
_ringAnimatedLayer.path = smoothedPath.CGPath;
}
return _ringAnimatedLayer;
}
- 通过贝塞尔曲线绘制圆圈_ringAnimatedLayer图层;
- SVProgressAnimatedView主要用于加载或上传显示进度的提示样式;
SVRadialGradientLayer
- (void)drawInContext:(CGContextRef)context {
size_t locationsCount = 2;
CGFloat locations[2] = {0.0f, 1.0f};
CGFloat colors[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.75f};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount);
CGColorSpaceRelease(colorSpace);
float radius = MIN(self.bounds.size.width , self.bounds.size.height);
CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
}
- 通过生成的SVRadialGradientLayer图层调用setNeedsDisplay函数使图层执行drawInContext函数,生成一个渐变颜色的图层;
压轴SVProgressHUD
- 视图层级
代码结构
- 仅加载或加载进度样式 原理
+ (void)show {
[self showWithStatus:nil];
}
+ (void)showWithStatus:(NSString*)status {
[self showProgress:SVProgressHUDUndefinedProgress status:status];
}
- (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;
}
// 重置计时器
strongSelf.fadeOutTimer = nil;
strongSelf.graceTimer = nil;
// 设置视图层级view
[strongSelf updateViewHierarchy];
// 将图片view进行隐藏
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
// 设置文字文本及进度
strongSelf.statusLabel.hidden = status.length == 0;
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
// progress进度大于0
if(progress >= 0) {
// Cancel the indefiniteAnimatedView, then show the ringLayer
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 添加两个ringView:一个背景的RingView,一个显示进度RingView
if(!strongSelf.ringView.superview){
[strongSelf.hudView.contentView addSubview:strongSelf.ringView];
}
if(!strongSelf.backgroundRingView.superview){
[strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView];
}
// 设置当前进度动画
[CATransaction begin];
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
// 记录当前显示进度的activityCount数量
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// 移除显示进度样式的ringView图层
[strongSelf cancelRingLayerAnimation];
// 添加加载样式的图层
[strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView];
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
// 记录当前加载样式图层的个数
strongSelf.activityCount++;
}
// graceTimeInterval大于0并且backgroundView透明度为0时执行fadeIn函数
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 {
// 执行fadeIn函数
[strongSelf fadeIn:nil];
}
}
}];
}
- (void)fadeIn:(id)data {
[self updateHUDFrame]; // 设置各个视图的frame
[self positionHUD:nil]; // 根据当前屏幕方向或是否有键盘弹出高度计算hudView尺寸并添加动力视觉效果
// 省略部分代码...
// 获取data是否有时间,有的话则会延迟执行dismiss
id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data;
// backgroundView透明度不为0时
if(self.backgroundView.alpha != 1.0f) {
// 发送hudView即将显示的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// 按照1/1.5f比例先缩小
self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f);
__block void (^animationsBlock)(void) = ^{
// 执行动画时恢复1.0比例
self.hudView.transform = CGAffineTransformIdentity;
// 设置hudView毛玻璃模式及其他显示view的透明度alpha
[self fadeInEffects];
};
__block void (^completionBlock)(void) = ^{
//
if(self.backgroundView.alpha == 1.0f){
// 注册通知
[self registerNotifications];
// 发送hudView显示时的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification
object:self
userInfo:[self notificationUserInfo]];
// 根据duration是否不为nil开启定时器定时执行dismiss函数
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];
}
}
};
// 根据fadeInAnimationDuration设置动画执行函数
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();
}
[self setNeedsDisplay];
} else { // fadeInAnimationDuration为nil无需开启定时器,直接执行dismiss函数
// 根据duration是否不为nil开启定时器定时执行dismiss函数
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];
}
}
}
- 当showProgress传入SVProgressHUDUndefinedProgress时:
- SVProgressHUDUndefinedProgress时样式为加载提示样式;
- 撤销可能加载多个提示导致期间会执行dismiss函数,所以刚开始先将计时器清空;
- 根据当前HUD样式样式调整一下当前HUD的视图层级结构;
- 隐藏图片view和通过判断加载字符串是否显示文字label;
- 取消移除加载进度的layer,为了防止上一个可能是加载进度的样式影响到了当前的只加载样式;
- 将图层indefiniteAnimatedView样式添加到hudView的contentView上;
- 若graceTimeInterval大于0且backgroundView透明度为0时,按照时间添加graceTimer计时器定时执行fadeIn函数;反之直接执行fadeIn函数;
- 设置加载hudView尺寸及位置后通过动画进行先缩小再放大到正常比例显示;
- 当showProgress传入大于0时:
- progress大于0时显示加载进度提示样式;
- 撤销可能加载多个提示导致期间会执行dismiss函数,所以刚开始先将计时器清空;
- 根据当前HUD样式样式调整一下当前HUD的视图层级结构;
- 隐藏图片view和通过判断加载字符串是否显示文字label;
- 取消移除只加载样式的layer,为了防止上一个可能是只加载的样式影响到了当前的进度加载样式;
- 将加载进度背景ringView和提示加载进度的ringView添加到hudView上;
- 设置加载hudView尺寸及位置后通过动画进行先缩小再放大到正常比例显示;
- 通过progress的值来设置ringView的进度大小;
- showImage图片提示样式
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
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.foregroundColorForStyle;
} else {
strongSelf.imageView.image = image;
}
strongSelf.imageView.hidden = NO;
strongSelf.statusLabel.hidden = status.length == 0;
strongSelf.statusLabel.text = status;
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)];
}
}
}];
}
- 重置计时器,设置view视图层级结构;
- 将加载进度或加载的样式移除,将中间显示为imageView并设置图片image;
- 其它设置跟上面类似;
- 执行dismiss移除遮罩
- (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
__block void (^animationsBlock)(void) = ^{
// 按比例缩放1/1.3f比例
strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f);
// 将view设置为透明
[strongSelf fadeOutEffects];
};
__block void (^completionBlock)(void) = ^{
// 将self中的所有view移除了,并重置初始值
if(self.backgroundView.alpha == 0.0f){
[strongSelf.controlView removeFromSuperview];
[strongSelf.backgroundView removeFromSuperview];
[strongSelf.hudView removeFromSuperview];
[strongSelf removeFromSuperview];
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
[[NSNotificationCenter defaultCenter] removeObserver:strongSelf];
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
object:strongSelf
userInfo:[strongSelf notificationUserInfo]];
#if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS
UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
[rootController setNeedsStatusBarAppearanceUpdate];
#endif
if (completion) {
completion();
}
}
};
// 根据delay判断是否需要延时执行动画
dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{
if (strongSelf.fadeOutAnimationDuration > 0) {
[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];
}
}];
}
- 通过动画将SVProgressHUD中的所有view设置为透明,动画完成时将所有view移除!
其它
- 其它样式
- showInfoWithStatus
- showSuccessWithStatus
- showErrorWithStatus
原理:通过设置不同的图片来显示上面几种类型的提示,显示弹框时间默认是按照显示提示语长度来计算,也可以用户自己设置需要显示的时间长;
- popActivity
作用:在之前有记录过当前提示加载的个数,这里是退出当前加载的个数;