SVProgressHUD源码解读(2.0.3)
SVProgressHUD
是iOS
开发中比较常用的一个三方库,用来在执行耗时操作或者指示用户操作结果的场合,由于使用简单,功能丰富,交互友好,被广泛应用。本文从源码的角度,解读一下实现的过程,希望能起到抛砖引玉的作用。
一. 效果预览
1. SVPIndefiniteAnimatedView
2. SVProgressAnimatedView
3. SVRadialGradientLayer
二. 类分析
1. SVProgressHUD
这是SVProgressHUD
显示提示框的类,提供类方法和属性来进行不同的设置。
** HUD
提示框背景
typedef NS_ENUM(NSInteger, SVProgressHUDStyle) {
SVProgressHUDStyleLight, // 白色
SVProgressHUDStyleDark, // 黑色
SVProgressHUDStyleCustom // 用户自定义
};
** 遮罩层背景
typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) {
SVProgressHUDMaskTypeNone = 1, // 默认mask,用户和交互
SVProgressHUDMaskTypeClear, // 不允许交互
SVProgressHUDMaskTypeBlack, // 不允许交互,遮罩层呈黑色部分透明
SVProgressHUDMaskTypeGradient, // 不允许交互,遮罩层呈渐变效果
SVProgressHUDMaskTypeCustom // 不允许交互,遮罩层颜色自定义
};
** 无限循环的显示类型
typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) {
SVProgressHUDAnimationTypeFlat, // SVPIndefiniteAnimatedView
SVProgressHUDAnimationTypeNative // 系统的UIActivityIndicatorView
};
**常用属性介绍
hud
最小尺寸,默认是(100,100)
@property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR;
圆环厚度,默认是2px
@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR;
圆环半径,默认是18px
@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR;
提示语字体,默认是14px
@property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR;
Image提示框显示时间,默认是5s
@property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval;
** 常用方法介绍
- 无限循环状态显示,不会自动小时,需主动调用
dismiss
方法
+ (void)show;
+ (void)showWithStatus:(NSString*)status;
+ (void)dismiss;
+ (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion;
+ (void)dismissWithDelay:(NSTimeInterval)delay;
+ (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion;
- 进度条状态显示
+ (void)showProgress:(float)progress;
+ (void)showProgress:(float)progress status:(NSString*)status;
- 图片状态显示
+ (void)showInfoWithStatus:(NSString*)status;
+ (void)showSuccessWithStatus:(NSString*)status;
+ (void)showErrorWithStatus:(NSString*)status;
hud
距离中心点的偏移量
+ (void)setOffsetFromCenter:(UIOffset)offset;
+ (void)resetOffsetFromCenter;
** 通知
通过监听不同的通知事件,可以获取hud
的状态
extern NSString * const SVProgressHUDWillDisappearNotification;
extern NSString * const SVProgressHUDDidDisappearNotification;
extern NSString * const SVProgressHUDWillAppearNotification;
extern NSString * const SVProgressHUDDidAppearNotification;
** hud
显示流程
SVProgressHUD
采用单例模式,简化代码维护;同时,根据SVProgressHUD
的层级结构可以看出,从底层到顶层依次是:UIControl (overlayView) -> SVProgressHUD -> UIView (hudView) -> UIVisualEffectView -> AnimatedView
(具体动画视图) 。
-(void)showStatus:(NSString*)status
, 这是显示无限循环状态的提示框,可以添加文字进一步详细补充。其中,SVProgressHUD
采用图形和文字分离的模式,方面文字视图的复用。所有,显示文字的视图,最终都会调用下面的方法。
- (void)showStatus:(NSString*)status {
// 更新frame及位置,因为frame是更加status来确定的而postion是根据参数控制的。
[self updateHUDFrame];
[self positionHUD:nil];
// 更新 accesibilty 和是否可以点击
if(self.defaultMaskType != SVProgressHUDMaskTypeNone) {
self.accessibilityLabel = status;
self.isAccessibilityElement = YES;
} else {
self.hudView.accessibilityLabel = status;
self.hudView.isAccessibilityElement = YES;
}
// Show if not already visible
// Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.hudView.contentView.alpha != 1.0f){
#else
根据alpha值判断是是否可见
if(self.hudView.alpha != 1.0f){
#endif
// 如果之前不可见则发出SVProgressHUDWillAppearNotification通知,告诉马上显示
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification
object:self
userInfo:[self notificationUserInfo]];
//缩放效果
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3);
// Activate blur on view before animation on older iOS versions,
// as we cannot animate this property and use alpha values instead
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
bool greateriOS9 = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0;
if (self.defaultStyle != SVProgressHUDStyleCustom && !greateriOS9) {
[self addBlur];
// Update alpha
self.hudView.contentView.alpha = 1.0f;
}
#endif
// Define blocks
__block void (^animationsBlock)(void) = ^{
// Shrink HUD to finish pop up animation
self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3f, 1/1.3f);
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.defaultStyle != SVProgressHUDStyleCustom && greateriOS9){
// Fade in blur effect
[self addBlur];
// Update alpha
self.hudView.contentView.alpha = 1.0f;
} else {
// This gives a warning on iOS 8, however it works, see #703
self.hudView.alpha = 1.0f;
}
#else
self.hudView.alpha = 1.0f;
self.hudView.contentView.alpha = 1.0f;
#endif
self.backgroundView.alpha = 1.0f;
};
__block void (^completionBlock)(void) = ^{
// Check if we really achieved to show the HUD (<=> alpha values are applied)
// and the change of these values has not been cancelled in between e.g. due to a dismissal
// Checking one alpha value is sufficient as they are all the same
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if(self.hudView.contentView.alpha == 1.0f){
#else
if(self.hudView.alpha == 1.0f){
#endif
// 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]];
}
// 更新 accesibilty
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status);
};
if (self.fadeInAnimationDuration > 0) {
// 如果设置了动画时间则进行动画效果
[UIView animateWithDuration:self.fadeInAnimationDuration
delay:0
options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState)
animations:^{
animationsBlock();
} completion:^(BOOL finished) {
completionBlock();
}];
} else {
animationsBlock();
completionBlock();
}
// 完成了更新视图层次,视图的frame以及视图的各种属性之后,告诉系统稍微进行重绘
[self setNeedsDisplay];
}
}
-(void)showProgress:(float)progress status:(NSString*)status
,这是显示单次滚动效果的提示框,每次显示视图前,都会取消其它视图,防止上次显示不同视图产生的干扰。其中,在设置strokeEnd
时,使用事物类CATransaction
,确保操作不被干扰。
- (void)showProgress:(float)progress status:(NSString*)status {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// 更新并且检查视图层次确保SVProgressHUD可见
[strongSelf updateViewHierarchy];
// 重置imageView和消失时间。防止之前调用过,使用上次存在的样式设置
strongSelf.imageView.hidden = YES;
strongSelf.imageView.image = nil;
if(strongSelf.fadeOutTimer) {
strongSelf.activityCount = 0;
}
strongSelf.fadeOutTimer = nil;
// 更新statusLabel显示的内容和显示的进度
strongSelf.statusLabel.text = status;
strongSelf.progress = progress;
//根据progersss的值来确定正确的样式,当progress>=0的时候,显示进度样式,当progress = -1的时候为无限旋转的样式
if(progress >= 0) {
// 防止上次为无限旋转的样式导致重叠
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 添加进度视图到hudview上,并且设置当前进度值
if(!strongSelf.ringView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.ringView];
#else
[strongSelf.hudView addSubview:strongSelf.ringView];
#endif
}
if(!strongSelf.backgroundRingView.superview){
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.backgroundRingView];
#else
[strongSelf.hudView addSubview:strongSelf.backgroundRingView];
#endif
}
// Set progress animated
[CATransaction begin];
[CATransaction setDisableActions:YES];
strongSelf.ringView.strokeEnd = progress;
[CATransaction commit];
// 更新activityCount
if(progress == 0) {
strongSelf.activityCount++;
}
} else {
// 防止上次为进度的样式导致重叠
[strongSelf cancelRingLayerAnimation];
// 增加无限旋转视图到hudview上
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
[strongSelf.hudVibrancyView.contentView addSubview:strongSelf.indefiniteAnimatedView];
#else
[strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView];
#endif
if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) {
[(id)strongSelf.indefiniteAnimatedView startAnimating];
}
// Update the activity count
strongSelf.activityCount++;
}
// 显示提示的文字信息
[strongSelf showStatus:status];
// Tell the Haptics Generator to prepare for feedback, which may come soon
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
[strongSelf.hapticGenerator prepare];
#endif
}
}];
}
-(void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration
,这是显示带Image
的提示框,自带info
/success
/error
三种类型,也可以自定义图片。
- (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration {
__weak SVProgressHUD *weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
__strong SVProgressHUD *strongSelf = weakSelf;
if(strongSelf){
// 更新并且检查视图层次确保SVProgressHUD可见
[strongSelf updateViewHierarchy];
// 重置progress,并取消其它动画
strongSelf.progress = SVProgressHUDUndefinedProgress;
[strongSelf cancelRingLayerAnimation];
[strongSelf cancelIndefiniteAnimatedViewAnimation];
// 更新imageView
UIColor *tintColor = strongSelf.foregroundColorForStyle;
UIImage *tintedImage = image;
if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
strongSelf.imageView.tintColor = tintColor;
strongSelf.imageView.image = tintedImage;
strongSelf.imageView.hidden = NO;
// 更新文字
strongSelf.statusLabel.text = status;
// 显示文字视图
[strongSelf showStatus:status];
// An image will be dismissed automatically. Therefore, we start a timer
// which then will call dismiss after the predefined duration
// 添加定时消失timer
strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes];
}
}];
}
**其它方法
- 设置
hud
位置的方法
- (void)positionHUD:(NSNotification*)notification
- 调整
hud
尺寸的方法
- (void)updateHUDFrame
- 更新模糊背景视图的方法
- (void)updateBlurBounds
2. SVPIndefiniteAnimatedView 类
这个类提供了一个无线旋转的动画,实现方法是把一个颜色渐变的图片旋转,然后利用UIBezierPath
/CAShapeLayer
/Mask
等遮住不需要的部分,最后利用CABasicAnimation
设置无限旋转动画。其中,核心部分是利用layer
的mask
属性实现遮罩功能,而mask
的实现方法是显示显示bounds的非透明部分,实例图如下:
3. SVProgressAnimatedView 类
这个类提供一个画圆环的视图,通过不断改变layer
的strokeEnd
的值,实现了进度的显示。顺便提一下,storkeStart
使用的默认值是0, 所以是从正上方开始的。
- (void)setStrokeEnd:(CGFloat)strokeEnd {
_strokeEnd = strokeEnd;
_ringAnimatedLayer.strokeEnd = _strokeEnd;
}
4. SVRadialGradientLayer 类
这个类继承自CALayer
,通过CGContextDrawRadialGradient
来画渐变颜色层;其中,CoreFoundation
中通过create
创建的需要用release
释放,否则会造成内存泄漏。
至此,SVProgressHUD
分析暂告一段落,分析的不全面的地方,欢迎交流。
参考资料
https://github.com/SVProgressHUD/SVProgressHUD
http://www.jianshu.com/p/a08d4597cf24
SVProgressHUD源码解读(2.0.3)的更多相关文章
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...
- AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
随机推荐
- angular4.0 父子组建之间的相互通信
父组建---->子组建 传递信息 首先先通过angular脚手架生成两个基本组件,有一个好处是 会自动关联到跟模版,节约时间,而且还是偷懒 ng generate component compo ...
- spring的applicationContext.xml配置SessionFactory抛异常
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFa ...
- 关于Calendar中设置月份比实际小1的问题
有如下程序,转化两个字符串数字为date类型,并判断是历史上的星期几,是否同为星期一 代码如下: public static void main(String[] args) throws Parse ...
- PB程序源码文件结构 pbl文件 pbd文件
最近公司给了一套PB的源码,一个8.0,一个9.0,让给一个客户做软件整合,之前只听过PB看过别人写代码,为了快速上手,了解了一下PB的文件,记录如下:pbl为pb源码文件 pbd为程序编译后的文件 ...
- JavaScript 的 作用域
在看了几本书之后的一些理解和自己的想法. 作用域,变量的作用范围 在ES6之前 变量的声明 只有var可以声明变量属于某个作用域,并且,也只有全局作用域和函数作用域. (没有var声明的变 ...
- HTML5中a标签的锚点使用
前几天有个用户问我关于在线手册功能里的锚点问题.因为他通过代码发现,在编辑手册内容时,锚点的设置是通过id选择器来制定的,而不是带有name属性的a标签.其实这是HTML5和HTML4(XHTML)等 ...
- Ambari安装之部署本地库(镜像服务器)(二)
部署本地库(镜像服务器) (1)下载HortWorks官网上的3个库到本地(也可以在线下载,但是速度会很慢) 我们先把hortworks官网上需要下载的3个库下载到本地(这个还是需要很长时间的,当然你 ...
- Android 自定义 permission
Android 自定义 permission Android 添加自定义权限 permission-tree 权限的根节点,3个成员都要定义 name 一般来说需要2个".":比如 ...
- Java之戳中痛点 - (6)避免类型自动转换,例如两个整数相除得浮点数遇坑
先来看一个例子: package com.test; public class calculate { /** * 光速30万公里/秒 */ public static final int LIGHT ...
- (转)sql union和union all的用法及效率
1 熟悉union的相关操作 UNION指令的目的是将两个SQL语句的结果合并起来.从这个角度来看, 我们会产生这样的感觉,UNION跟JOIN似乎有些许类似,因为这两个指令都可以由多个表格中撷取资料 ...