美文网首页js css html
【源码解读】MBProgressHUD

【源码解读】MBProgressHUD

作者: WellsCai | 来源:发表于2017-11-26 09:40 被阅读0次

    MBProgressHUD是一个很受欢迎的第三方库,其用法也简单,内部实现代码也简洁易懂,十分适合初学者学习。通过该开源库的学习,我们可以学到如何自定义view,以及一些写开源库的小知识点。
    以下我会分成三部分来讲解:
    ① 简单介绍其组成和核心API
    ② 深入了解其内部实现
    ③ 值得学习的技巧及思路

    ① 简单介绍其组成和核心API

    MBProgressHUD有五个类,都写在同一个.h、.m中。

    • MBProgressHUD(核心类)
    /*     暴露的API     */
    //将HUD添加到指定的view,并返回HUD
    + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;
    //将HUD从指定的view移除
    + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;
    //获取指定view上的HUD
    + (nullable MBProgressHUD *)HUDForView:(UIView *)view;
    
    //创建在指定view上的HUD
    - (instancetype)initWithView:(UIView *)view;
    //显示HUD
    - (void)showAnimated:(BOOL)animated;
    //隐藏HUD
    - (void)hideAnimated:(BOOL)animated;
    //延迟隐藏HUD
    - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;
    
    /*     可修改的属性     */
    @property (weak, nonatomic) id<MBProgressHUDDelegate> delegate;
    @property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;
    @property (assign, nonatomic) NSTimeInterval graceTime;//设定一个最小完成任务时间,超过该时间未完成才需要显示HUD
    @property (assign, nonatomic) NSTimeInterval minShowTime;//设定一个最小展示时间,至少展示这个时间
    @property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
    
    @property (assign, nonatomic) MBProgressHUDMode mode;//HUD样式
    @property (assign, nonatomic) MBProgressHUDAnimation animationType;//动画类型
    
    @property (strong, nonatomic, nullable) UIColor *contentColor;//HUD内容颜色
    @property (assign, nonatomic) CGPoint offset;//HUD距离背景view中心的offset
    @property (assign, nonatomic) CGFloat margin;//content距离HUD边的margin
    @property (assign, nonatomic) CGSize minSize;//HUD的最小size
    @property (assign, nonatomic, getter = isSquare) BOOL square;//HUD是否正方形
    @property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled;//是否支持背景视差效果
    
    @property (assign, nonatomic) float progress;//进度条进度
    @property (strong, nonatomic, nullable) NSProgress *progressObject;//进度条,不过实际使用到的还是自制的
    
    // 以下主要用于设置内容 
    @property (strong, nonatomic, readonly) MBBackgroundView *bezelView;//HUDView
    @property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;//HUD背景View
    @property (strong, nonatomic, nullable) UIView *customView;//自定义View,在label上方,用于放置图片或进度器
    @property (strong, nonatomic, readonly) UILabel *label;
    @property (strong, nonatomic, readonly) UILabel *detailsLabel;
    @property (strong, nonatomic, readonly) UIButton *button;
    
    • MBBarProgressView(条状进度器)
    • MBBackgroundView (背景View,带有高斯模糊)
    • MBRoundProgressView (圆形进度器)
    • MBProgressHUDRoundedButton (带圆角的按钮)
    ② 深入了解其内部实现

    要看内部实现我们又分成三部分(初始化、显示、隐藏):

    • HUD的初始化
      我们调用showHUDAddedTo:animated:创建HUD,并显示。
    + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
        MBProgressHUD *hud = [[self alloc] initWithView:view];//初始化HUD
        hud.removeFromSuperViewOnHide = YES;
        [view addSubview:hud];//添加到view上
        [hud showAnimated:animated];//显示
        return hud;
    }
    

    我们先来看HUD如何初始化的,这边实现了initWithFrame:initWithCoder:,并在方法里面进行数据的初始化。这样无论是使用代码还是可视化都可以用该框架。

    //用于纯代码
    - (instancetype)initWithFrame:(CGRect)frame {
        if ((self = [super initWithFrame:frame])) {
            [self commonInit];
            self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
        }
        return self;
    }
    //用于可视化(nib)
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if ((self = [super initWithCoder:aDecoder])) {
            [self commonInit];
        }
        return self;
    }
    - (id)initWithView:(UIView *)view {
        NSAssert(view, @"View must not be nil.");
        return [self initWithFrame:view.bounds];
    }
    

    commonInit方法中除了设置默认值、默认属性,主要还设置了HUD里面的子视图(进度器,提示文字等)

    - (void)commonInit {
        // 设置默认值、默认属性
        _animationType = MBProgressHUDAnimationFade;
        _mode = MBProgressHUDModeIndeterminate;
        _margin = 20.0f;
        _opacity = 1.f;
        _defaultMotionEffectsEnabled = YES;
    
        ...(此处省略很多代码)
    
        [self setupViews];//初始化子视图
        [self updateIndicators];//根据进度器模式选择进度器,并且设置颜色
        [self registerForNotifications];//注册通知,屏幕旋转时做视图的旋转
    }
    

    从这里可以我们可以看出HUD的组成,到这一步就能确定下子视图的内容, 并且设置setNeedsUpdateConstraints。
    updateConstraints方法中添加约束,这边用的是AutoLayout,不清楚的可以看看这篇文章:NSLayoutConstraint-代码实现自动布局
    通过AutoLayout,某个视图不显示时也能设置好间距,达到灵活布局的作用。

    HUD组成.png
    • HUD的显示
      这边有一点需要注意的是,有个graceTime(优化时间),比如当请求任务很快时,HUD就会一闪而过。所以为防止这种现象,设置一个graceTime,只有当超过该时间未完成才会执行显示HUD的任务。
    - (void)showAnimated:(BOOL)animated {
        MBMainThreadAssert();
        [self.minShowTimer invalidate];
        self.useAnimation = animated;
        self.finished = NO;
        //设定一个最小完成任务时间,超过该时间未完成才会执行显示HUD的任务
        if (self.graceTime > 0.0) {
            NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
            self.graceTimer = timer;
        } 
        else {//否则马上执行显示
            [self showUsingAnimation:self.useAnimation];
        }
    }
    
    - (void)showUsingAnimation:(BOOL)animated {
        //取消之前的动画
        [self.bezelView.layer removeAllAnimations];
        [self.backgroundView.layer removeAllAnimations];
    
        // Cancel any scheduled hideDelayed: calls
        [self.hideDelayTimer invalidate];
    
        //记录开始时间
        self.showStarted = [NSDate date];
        self.alpha = 1.f;
    
        // 进度器模式需要创建CADisplayLink
        [self setNSProgressDisplayLinkEnabled:YES];
    
        if (animated) {
            //执行动画
            [self animateIn:YES withType:self.animationType completion:NULL];
        } else {
            //直接显示
            self.bezelView.alpha = 1.f;
            self.backgroundView.alpha = 1.f;
        }
    }
    
    • HUD的隐藏
      通过hideHUDForView:获取view上所有的view执行隐藏任务。
    + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
        MBProgressHUD *hud = [self HUDForView:view];
        if (hud != nil) {
            hud.removeFromSuperViewOnHide = YES;
            [hud hideAnimated:animated];
            return YES;
        }
        return NO;
    }
    

    隐藏有两种方式,一种是执行调用隐藏,一种是延迟后隐藏(也是利用定时器),不过最终都是调用hideAnimated:。在hideAnimated:方法中,有个minShowTime(minShowTime),比如当HUD刚展示出来,任务刚好完成,HUD就会一闪而过。所以为防止这种现象,设置一个设置最小展示时间minShowTime,就要先展示够时间再执行隐藏任务。

    - (void)hideAnimated:(BOOL)animated {
        MBMainThreadAssert();
        [self.graceTimer invalidate];
        self.useAnimation = animated;
        self.finished = YES;
        // 如果有设置最小展示时间minShowTime,就要先展示够时间再执行隐藏任务
        if (self.minShowTime > 0.0 && self.showStarted) {
            NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
            if (interv < self.minShowTime) {
                NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
                [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
                self.minShowTimer = timer;
                return;
            } 
        }
        // 否则马上进行隐藏任务
        [self hideUsingAnimation:self.useAnimation];
    }
    
    - (void)hideUsingAnimation:(BOOL)animated {
        if (animated && self.showStarted) {//有动画
            self.showStarted = nil;
            //执行动画
            [self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
                [self done];//状态的重置和定时器的清空,block的回调
            }];
        } else {//无动画
            self.showStarted = nil;
            self.bezelView.alpha = 0.f;
            self.backgroundView.alpha = 1.f;
            [self done];//状态的重置和定时器的清空,block的回调
        }
    }
    
    - (void)done {
        // Cancel any scheduled hideDelayed: calls
        [self.hideDelayTimer invalidate];
        [self setNSProgressDisplayLinkEnabled:NO];
    
        if (self.hasFinished) {
            self.alpha = 0.0f;
            if (self.removeFromSuperViewOnHide) {
                [self removeFromSuperview];
            }
        }
       //完成后的回调
        MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
        if (completionBlock) {
            completionBlock();
        }
        id<MBProgressHUDDelegate> delegate = self.delegate;
        if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
            [delegate performSelector:@selector(hudWasHidden:) withObject:self];
        }
    }
    
    ③ 值得学习的技巧及思路

    通过MBProgressHUD源码的解读,可以学到API接口的设计,控件的封装,同时也可以掌握自定义控件的一些细节,比如:

    • AutoLayout布局(当我们自定义控件时,肯定要减少其他第三方的引入,所以布局方式上就不能直接用masony)。
    //设置固有size
    - (CGSize)intrinsicContentSize;
    //更新约束
    - (void)updateConstraints;
    
    • 细节上的优化,比如graceTime和minShowTime(使用NSTimer来实现),以及使用CADisplayLink来处理刷新频率高的事件。
    _progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self 
    [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    selector:@selector(updateProgressFromProgressObject)];
    
    • 宏定义和静态常量的使用。
    #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
    
    CGFloat const MBProgressMaxOffset = 1000000.f;
    static const CGFloat MBDefaultPadding = 4.f;
    
    • 一些属性设置上的小细节(先判断是否相等,最后看是否更新display还是Constraints)。
    - (void)setProgress:(float)progress {
        if (progress != _progress) {
            _progress = progress;
            [self setNeedsDisplay];
        }
    }
    - (void)setMargin:(CGFloat)margin {
        if (margin != _margin) {
            _margin = margin;
            [self setNeedsUpdateConstraints];
        }
    }
    
    • drawRect:方法中绘制自定义图形。
    • UIBlurEffect(高斯模糊)、UIInterpolatingMotionEffect(背景视差效果)、屏幕横竖屏切换的处理。

    相关文章

      网友评论

        本文标题:【源码解读】MBProgressHUD

        本文链接:https://www.haomeiwen.com/subject/cxhovxtx.html