SVProgressHUD(2.0.3)原来是这样

来源:互联网 发布:栗山千明知乎 编辑:程序博客网 时间:2024/06/05 12:42

简介

SVProgressHUD在iOS开发中用作提示的场景还是非常多的。这里主要从整个项目的使用及源码方面就行分析以及附上相关效果图。希望能起到抛砖引玉的作用。

使用

SVProgrossHUD是通过单例的方式来使用,这种方式也是许多第三方所使用的。也就是快速创建,不需要手动的alloc进行实例化。

  • 使用的场景: 比较合理的场景是在推荐用户操作之前确定需要执行任务其他任务的时候,而不是在刷新,无限的滑动或者发送消息等场景。

常见的使用方式如下:

[SVProgressHUD show];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{    // 处理耗时的操作    dispatch_async(dispatch_get_main_queue(), ^{        [SVProgressHUD dismiss];    });});

使用+ (void)show; + (void)showWithStatus:(NSString*)string;来显示状态不明确的操作,用+ (void)showProgress:(CGFloat)progress;+ (void)showProgress:(CGFloat)progress status:(NSString*)status;来显示状态明确的操作,显示当前操作的进度。


取消+ (void)dismiss;+ (void)dismissWithDelay:(NSTimeInterval)delay;这里顺便提一下dismissWithDelay这个方法之前没注意。可以延迟取消,这样就不用手动用GCD的延迟去dismiss了。


如果想平衡调用的次数,可以使用+ (void)popActivity; 一旦匹配了调用show的次数则会消失。如果没有匹配争取则不会消失。其源码为

+ (void)popActivity {    if([self sharedView].activityCount > 0) {        [self sharedView].activityCount--;    }    if([self sharedView].activityCount == 0) {        [[self sharedView] dismiss];    }}

或者根据字符串的长度来自动确定显示的时间。当调用下面的这些方法的时候会用这种方式

+ (void)showInfoWithStatus:(NSString*)string;+ (void)showSuccessWithStatus:(NSString*)string;+ (void)showErrorWithStatus:(NSString*)string;+ (void)showImage:(UIImage*)image status:(NSString*)string;

我们可以自定义里面的一些属性,比如字体大小,提示图片等。可以自定的方法如下:

+ (void)setDefaultStyle:(SVProgressHUDStyle)style;                  // default is SVProgressHUDStyleLight+ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType;         // default is SVProgressHUDMaskTypeNone+ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type;   // default is SVProgressHUDAnimationTypeFlat+ (void)setMinimumSize:(CGSize)minimumSize;                         // default is CGSizeZero, can be used to avoid resizing for a larger message+ (void)setRingThickness:(CGFloat)width;                            // default is 2 pt+ (void)setRingRadius:(CGFloat)radius;                              // default is 18 pt+ (void)setRingNoTextRadius:(CGFloat)radius;                        // default is 24 pt+ (void)setCornerRadius:(CGFloat)cornerRadius;                      // default is 14 pt+ (void)setFont:(UIFont*)font;                                      // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]+ (void)setForegroundColor:(UIColor*)color;                         // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom+ (void)setBackgroundColor:(UIColor*)color;                         // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom+ (void)setBackgroundLayerColor:(UIColor*)color;                    // default is [UIColor colorWithWhite:0 alpha:0.4], only used for SVProgressHUDMaskTypeCustom+ (void)setInfoImage:(UIImage*)image;                               // default is the bundled info image provided by Freepik+ (void)setSuccessImage:(UIImage*)image;                            // default is bundled success image from Freepik+ (void)setErrorImage:(UIImage*)image;                              // default is bundled error image from Freepik+ (void)setViewForExtension:(UIView*)view;                          // default is nil, only used if #define SV_APP_EXTENSIONS is set+ (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval;     // default is 5.0 seconds+ (void)setFadeInAnimationDuration:(NSTimeInterval)duration;        // default is 0.15 seconds+ (void)setFadeOutAnimationDuration:(NSTimeInterval)duration;       // default is 0.15 seconds

SVProgressHUD默认提供两种样式SVProgressHUDStyleLight, SVProgressHUDStyleDark,一个是白色主题,一个是黑色主题如果想自定义一些颜色可以通过setForegroundColor and setBackgroundColor不要忘记设置默认样式SVProgressHUDStyleCustom


通知,SVProgressHUD会使用到四个通知

SVProgressHUDWillAppearNotification SVProgressHUDDidAppearNotification SVProgressHUDWillDisappearNotification SVProgressHUDDidDisappearNotification

每一通知会传递一个userinfo字典传递HUD的提示信息,key为SVProgressHUDStatusUserInfoKey。当用户触摸提示的整个屏幕的时候会发出SVProgressHUDDidReceiveTouchEventNotification通知,当用户直接触摸HUD的时候会发出SVProgressHUDDidTouchDownInsideNotification通知。

关键类

SVProgroessHUD一共有四个重要的类。它们分别是

  • SVPIndefiniteAnimatedView:无限旋转视图组件。如下图:

  • SVProgressAnimatedView:进度视图组件.如下图


  • SVProgressHUD: 视图显示控制类(我们通过SVProgressHUD这个类来使用上两种视图组件)。类似于一个管理类。

  • SVRadialGradientLayer:渐变层,当我们设置遮罩样式为SVProgressHUDMaskTypeGradient,就需要用到这个层。模仿系统UIAlterView的背景效果。

  • SVProgressHUD.bundle: 这里面放的是一些图片资源文件

关键类分析

SVPIndefiniteAnimatedView

关于这个类,主要是需要讲的就是一个如果实现无限加载的动画效果。如上图的上图一样。原理其实不难,我这个给出一个图,大家应该就明白了。


  • 原理也就是不断地旋转一张具有渐变颜色的图片,然后通过使用mask来遮住不需要的部分(结合layer使用)。

讲到这里就不得不提到iOS动画中的CALayer以及Mask。常见的场景就是CAShapeLayer和mask结合使用

/* A layer whose alpha channel is used as a mask to select between the * layer's background and the result of compositing the layer's * contents with its filtered background. Defaults to nil. When used as * a mask the layer's `compositingFilter' and `backgroundFilters' * properties are ignored. When setting the mask to a new layer, the * new layer must have a nil superlayer, otherwise the behavior is * undefined. Nested masks (mask layers with their own masks) are * unsupported. */@property(nullable, strong) CALayer *mask;

以上是CALayer的头文件关于mask的说明,mask实际上layer内容的一个遮罩。
如果我们把mask是透明的,实际看到的layer是完全透明的,也就是说只有mask的内容不透明的部分和layer的叠加部分才会显示。如下图:


有许多很炫酷的动画效果都是通过这样实现的。比如以下几种



其中还有Twitter的启动效果

  • 代码片段
        // 初始化,设置参数        _indefiniteAnimatedLayer = [CAShapeLayer layer];        _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];        _indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);        _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor;        _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor;        _indefiniteAnimatedLayer.lineWidth = self.strokeThickness;        _indefiniteAnimatedLayer.lineCap = kCALineCapRound;        _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel;        _indefiniteAnimatedLayer.path = smoothedPath.CGPath;        // 初始化mask,从资源库中读取图片        CALayer *maskLayer = [CALayer layer];        NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]];        NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"];        NSBundle *imageBundle = [NSBundle bundleWithURL:url];        NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"];        // 大部分用法都是类似的,通过图片来作为maskLayer的contents        maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage];        maskLayer.frame = _indefiniteAnimatedLayer.bounds;        _indefiniteAnimatedLayer.mask = maskLayer;

开始做动画,做动画分为了两个部分,一个是图片旋转,一个是动画组

  • 旋转动画
         // 设置动画的延迟及类型        NSTimeInterval animationDuration = 1;        CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];        // 注意value类型为id类型        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;        // 将动画加到mask上        [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];

通过旋转动画我们看到的就是


然后来看看动画组

        // 创建动画组,并设置相关属性        CAAnimationGroup *animationGroup = [CAAnimationGroup animation];        animationGroup.duration = animationDuration;        animationGroup.repeatCount = INFINITY;        animationGroup.removedOnCompletion = NO;        animationGroup.timingFunction = linearCurve;        // strokeStart动画        CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];        strokeStartAnimation.fromValue = @0.015;        strokeStartAnimation.toValue = @0.515;        // strokeEnd动画        CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];        strokeEndAnimation.fromValue = @0.485;        strokeEndAnimation.toValue = @0.985;        // 将动画加到动画组        animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];        [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];

动画组的效果


让我们来找找数字之间的关系

strokeStartAnimation.fromValue = @0.015;strokeStartAnimation.toValue = @0.515;strokeEndAnimation.fromValue = @0.485;strokeEndAnimation.toValue = @0.985;

有些规律吧。这样就能达到不断改变strokeStart和strokeEnd的值并让之间的差值为一个常量。我们看到的就是一个白色的缺口不断旋转的效果。个人觉得实现这种效果是想到巧妙的

改变strokeEnd参数的效果

strokeEndAnimation.fromValue = @0.285;strokeEndAnimation.toValue = @0.785;

  • 其他

其他值得学习的大概应该算用到了懒加载的方式吧。便于代码的管理以及缕清逻辑关系。重写属性的setter方法,在setter方法里面完成和这个属性相关一些赋值,逻辑判断操作。比如:

- (void)setRadius:(CGFloat)radius {    if(radius != _radius) {        _radius = radius;        // 在setter方法中进行相关逻辑判断,        [_indefiniteAnimatedLayer removeFromSuperlayer];        _indefiniteAnimatedLayer = nil;        if(self.superview) {            [self layoutAnimatedLayer];        }    }}

SVProgressAnimatedView

这个是用于处理进度的视图组件,实现进度的原理也很简单,也就是不断改变strokeEnd的值。
来看看那.h文件

@interface SVProgressAnimatedView : UIView// 半径@property (nonatomic, assign) CGFloat radius;// 厚度@property (nonatomic, assign) CGFloat strokeThickness;// 进度指示颜色@property (nonatomic, strong) UIColor *strokeColor;// 当前进度,@property (nonatomic, assign) CGFloat strokeEnd;@end

.m文件的实现大致和SVIndefiniteAnimatedView一样。使用懒加载,在willMoveToSuperview方法中添加layer。实现进度的关键就是重写strokeEnd的setter方法

- (void)setStrokeEnd:(CGFloat)strokeEnd {    _strokeEnd = strokeEnd;    // 改变结束的位置    _ringAnimatedLayer.strokeEnd = _strokeEnd;}

进度写死的效果0.4


顺便提一下,storkeStart使用的默认值是0。所以是从正上方开始的。

SVProgressHUD

这个类的作用想到于管理类的作用,负责和外部交互和调用视图组件。进行重要逻辑判断。

.h文件

extern相关

来看看.h文件中extern的使用

extern NSString * const SVProgressHUDDidReceiveTouchEventNotification;extern NSString * const SVProgressHUDDidTouchDownInsideNotification;extern NSString * const SVProgressHUDWillDisappearNotification;extern NSString * const SVProgressHUDDidDisappearNotification;extern NSString * const SVProgressHUDWillAppearNotification;extern NSString * const SVProgressHUDDidAppearNotification;extern NSString * const SVProgressHUDStatusUserInfoKey;

与extern相关的还有const,static等。下面扩展一下

  • const 最好理解,修饰的东西不能被修改.指针类型根据位置的不同可以理解成3种情况:

  • 常量指针
    初始化之后不能赋值,指向的对象可以是任意对象,对象可变。
    NSString * const pt1;

  • 指向常量的指针
    初始化之后可以赋值,即指向别的常量,指针本身的值可以修改,指向的值不能修改
    const NSString * pt2;

  • 指向常量的常量指针
    const NSString * const pt3;

  • extern
    等同于c,全局变量的定义,声明
    extern const NSString AA;
    定义
    const NSString
    AA = @"abc";

  • static
    等同于c,将变量的作用域限定于本文件?
    不同于java C++里面的类变量,oc没有类变量

--

结论

static
// static变量属于本类,不同的类对应的是不同的对象
// static变量同一个类所有对象中共享,只初始化一次const
// static const变量同static的结论I,只是不能修改了,但是还是不同的对象
// extern const变量只有一个对象,标准的常量的定义方法
// extern的意思就是这个变量已经定义了,你只负责用就行了

typedef NS_ENUM

定义常见的枚举,注意命令的方式,***Type值的命名方式 ***TypeLight

UI_APPEARANCE_SELECTOR

UI_APPEARANCE_SELECTOR:这个关键字是外观属性都用到的,用一个例子简单说一下它的作用。
[[UIBarButtonItem appearance] setTintColor:[UIColor redColor]];可以定制应用中所有条形按钮的颜色为redColor。没有这个UI_APPEARANCE_SELECTOR之前,只要一个一个控件的去修改。也就是通过UI_APPEARANCE_SELECTOR可以批量设置控件的颜色了。

深入了解可以看看使用UIAppearance协议自定义视图

当我在做公用组件的时候,一定要记得把默认值是什么要说明一下。

类方法

类方法主要有两大类,一种是set***一种是show**。前者用于设置外观样式,后者是直接使用的方式。
比如:

  • set**
+ (void)setDefaultStyle:(SVProgressHUDStyle)style;                  // default is SVProgressHUDStyleLight+ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType;         // default is SVProgressHUDMaskTypeNone+ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type;   // default is SVProgressHUDAnimationTypeFlat
  • show**
+ (void)show;+ (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead.")));+ (void)showWithStatus:(NSString*)status;+ (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead.")));

这里可以注意一下如何标示方法过期的方式maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")))

.m文件

.m文件包含的内容是整个项目中最复杂或者说是最需要梳理与值得学习的。下面重点介绍下.m文件里面的内容。

常量的定义

关于define和statc const定义常量的区别,这里就不讲了。主要是提醒一下,尽量用statci const来定义更符合风格吧。比如
static const CGFloat SVProgressHUDParallaxDepthPoints = 10;

readonly及getter的使用

虽然这样的用法有些麻烦,对于有强迫症的程序员还是蛮推荐这种写法的

@property (nonatomic, readonly, getter = isClear) BOOL clear;

getter方法

- (BOOL)isClear {    return (self.defaultMaskType == SVProgressHUDMaskTypeClear ||     self.defaultMaskType == SVProgressHUDMaskTypeNone);}

事先定义好私有方法,也就是外界不能直接调用的实例方法

这种习惯能够快速的了解整个类一共有哪些方法以及方法归类等。比如:

- (void)setStatus:(NSString*)status;- (void)setFadeOutTimer:(NSTimer*)timer;- (void)registerNotifications;- (NSDictionary*)notificationUserInfo;- (void)positionHUD:(NSNotification*)notification;- (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle;- (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event;

这样比较有条理,私有方法的定义是在类的扩展里面。

使用单例

常见的一些关于UI的第三方都是通过类方法调用,而且全局可以只用一个实例对象来维护就可以了。

+ (SVProgressHUD*)sharedView {    static dispatch_once_t once;    static SVProgressHUD *sharedView;#if !defined(SV_APP_EXTENSIONS)    // 创建单例对象    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;}

show方法参数化

关于方法的参数化说白了就是为了实现代码的复用。因为有存在相同的逻辑,则把相同部分抽离出来,不同的部分通过传入不同参数来控制来达到代码的复用。在实际工作中,这一点也非常重要。

经过整理,最终得出所有调用show**方法最终调用的只有两个个方法+ (void)showProgress:(float)progress status:(NSString*)status+ (void)showImage:(UIImage*)image status:(NSString*)status

  • showProgress:(float)progress status:(NSString*)status

当显示的是无限旋转提示的时候,会传入progrerss = -1来区别显示进度的样式。

+ (void)showWithStatus:(NSString*)status {    [self sharedView];    [self showProgress:SVProgressHUDUndefinedProgress status:status];}

这里的SVProgressHUDUndefinedProgress其实是一个常量。其定义为static const CGFloat SVProgressHUDUndefinedProgress = -1;

这里需要提一提的是,设置遮罩样式没有通过参数传递来设置而是通过设置属性的方式来做的。

+ (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType {    SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType;    [self setDefaultMaskType:maskType];    [self showProgress:progress];    // 显示完之后回到默认的遮罩样式    [self setDefaultMaskType:existingMaskType];}

我简单分析了一下不通过参数来传递遮罩样式的原因应该是为了每次显示完之后保证下一次遮罩的样式依然是默认的样式。可以看到每次调用完show**之后都会把mask恢复到默认值。

  • + (void)showImage:(UIImage*)image status:(NSString*)status

这个方法是会自动消失show**最终会调用的方法,比如

+ (void)showInfoWithStatus:(NSString*)status;+ (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead.")));+ (void)showSuccessWithStatus:(NSString*)status;+ (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead.")));+ (void)showErrorWithStatus:(NSString*)status;+ (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead.")));// shows a image + status, use 28x28 white PNGs+ (void)showImage:(UIImage*)image status:(NSString*)status;+ (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead.")));

UIAccessibility

UIAccessibility协议用于让外界程序了解到自己身的执情情况。Accessibility是一个交互协议,基于查询<->应答,通知<->监听模型的协议。外部程序通过查询来获取APP应答,从而了解程序。另外通过监听来自APP的消息,来通知用户当前状态。

  • 1.常用的协议与元素包括:

UIAccessibility, protocol,核心协议。
UIAccessibilityAction,protocol,添加行为的协议。 UIAccessibilityElement, class。
UIAccessibilityContainer,protocol,容器协议。

  • 2.常用函数 UIAccessibilityPostNotification。

可以看到SVProgressHUD支持UIAccessibility

     // Accessibility support    self.accessibilityIdentifier = @"SVProgressHUD";    self.accessibilityLabel = @"SVProgressHUD";    self.isAccessibilityElement = YES;

看一下官方介绍

/* UIAccessibility UIAccessibility is implemented on all standard UIKit views and controls so that assistive applications can present them to users with disabilities. Custom items in a user interface should override aspects of UIAccessibility to supply details where the default value is incomplete. For example, a UIImageView subclass may need to override accessibilityLabel, but it does not need to override accessibilityFrame. A completely custom subclass of UIView might need to override all of the UIAccessibility methods except accessibilityFrame. */

showProgress:(float)progress status:(NSString*)status

我们都知道关于UI的操作都需要放在主线程中。一般会通过GCD的方式如下:

dispatch_async(dispatch_get_main_queue(), ^{    });

但是SVProgressHUD里面用的是NSOperation来实现的,

[[NSOperationQueue mainQueue] addOperationWithBlock:^{    }];

这也给了我们另外一种方式回到主线程。