MBProcessHUD-源码分析与仿写(三)

前言

阅读优秀的开源项目是提高编程能力的有效手段,我们能够从中开拓思维、拓宽视野,学习到很多不同的设计思想以及最佳实践。阅读他人代码很重要,但动手仿写、练习却也是很有必要的,它能进一步加深我们对项目的理解,将这些东西内化为自己的知识和能力。然而真正做起来却很不容易,开源项目阅读起来还是比较困难,需要一些技术基础和耐心。
本系列将对一些著名的iOS开源类库进行深入阅读及分析,并仿写这些类库的基本实现,加深我们对底层实现的理解和认识,提升我们iOS开发的编程技能。

MBProcessHUD

MBProcessHUD是一个iOS上的提示框库,支持加载提示、进度框、文字提示等,使用简单,功能强大,还能够自定义显示内容,广泛应用于iOS app中。这是它的地址:https://github.com/jdg/MBProgressHUD
简单看一下界面效果:

Paste_Image.png

实现原理

MBProcessHUD继承自UIView,实际上是一个覆盖全屏的半透明指示器组件。它由以下几个部分构成,分别是:Loading加载动画,标题栏,背景栏以及其它栏(如详情栏、按钮)。我们把MBProcessHUD添加到页面上,显示任务进度及提示信息,同时屏蔽用户交互操作。
MBProcessHUD的Loading加载动画来自系统类UIActivityIndicatorView,在页面加载时,开启转圈动画,页面销毁时取消转圈动画。
MBProcessHUD根据加载内容动态布局,它通过计算需要显示的内容,动态调整页面元素的位置大小,放置到屏幕的中央,显示的内容可以由使用者指定。MBProcessHUD v1.0版之前是通过frame计算各个元素的位置,最新的版本采用了约束布局。
MBProcessHUD使用KVO监听一些属性值的变化,如labelText,model。这些属性被修改时,MBProcessHUD视图相应更新,传入新值。

仿写MBProcessHUD

我们模仿MBProcessHUD写一个简单的弹出框组件,以加深对它的理解。在这个demo中,我们不完全重写MBProcessHUD,只实现基本功能。
首先在demo中创建ZCJHUD,继承UIView。

Paste_Image.png

在ZCJHUD头文件中,定义几种显示模式

typedef NS_ENUM(NSInteger, ZCJHUDMode) {
    /** 转圈动画模式,默认值 */
    ZCJHUDModeIndeterminate,
    /** 只显示标题 */
    ZCJHUDModeText
};

定义对外的接口,显示模式mode,标题内容labelText
@interface ZCJHUD : UIView

@property (nonatomic, assign) ZCJHUDMode mode;

@property (nonatomic, strong) NSString *labelText;

- (instancetype)initWithView:(UIView *)view;

- (void)show;

- (void)hide;

@end

自身初始化,设置组件默认属性,更新布局,注册kvo监视属性变化。
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_mode = ZCJHUDModeIndeterminate;
_labelText = nil;
_size = CGSizeZero;
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
self.alpha = 0;

        [self setupView];
        [self updateIndicators];
        [self registerForKVO];
    }
    return self;
}

初始化转圈动画,并添加到hud上,ZCJHUDModeIndeterminate模式才有这个动画
- (void)updateIndicators {
BOOL isActivityIndicator = [_indicator isKindOfClass:[UIActivityIndicatorView class]];

    if (_mode == ZCJHUDModeIndeterminate) {
        if (!isActivityIndicator) {
            // Update to indeterminate indicator
            [_indicator removeFromSuperview];
            self.indicator = ([[UIActivityIndicatorView alloc]
                                             initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
            [(UIActivityIndicatorView *)_indicator startAnimating];
            [self addSubview:_indicator];
        }
    } else if (_mode == ZCJHUDModeText) {
        [_indicator removeFromSuperview];
        self.indicator = nil;
    }
}

两个主要方法,显示和隐藏hud

-(void)show {
    self.alpha = 1;
}

-(void)hide {
    self.alpha = 0;
    [self removeFromSuperview];
}

这里使用了frame动态布局
- (void)layoutSubviews {
[super layoutSubviews];

    // 覆盖整个视图,屏蔽交互操作
    UIView *parent = self.superview;
    if (parent) {
        self.frame = parent.bounds;
    }
    CGRect bounds = self.bounds;
    
    
    CGFloat maxWidth = bounds.size.width - 4 * kMargin;
    CGSize totalSize = CGSizeZero;
    
    CGRect indicatorF = _indicator.bounds;
    indicatorF.size.width = MIN(indicatorF.size.width, maxWidth);
    totalSize.width = MAX(totalSize.width, indicatorF.size.width);
    totalSize.height += indicatorF.size.height;
    
    CGSize labelSize = MB_TEXTSIZE(_label.text, _label.font);
    labelSize.width = MIN(labelSize.width, maxWidth);
    totalSize.width = MAX(totalSize.width, labelSize.width);
    totalSize.height += labelSize.height;
    if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
        totalSize.height += kPadding;
    }
    
    totalSize.width += 2 * kMargin;
    totalSize.height += 2 * kMargin;
    
    // Position elements
    CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + kMargin;
    CGFloat xPos = 0;
    indicatorF.origin.y = yPos;
    indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos;
    _indicator.frame = indicatorF;
    yPos += indicatorF.size.height;
    
    if (labelSize.height > 0.f && indicatorF.size.height > 0.f) {
        yPos += kPadding;
    }
    CGRect labelF;
    labelF.origin.y = yPos;
    labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos;
    labelF.size = labelSize;
    _label.frame = labelF;
    
    _size = totalSize;
}

绘制背景框
- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();
    UIGraphicsPushContext(context);
    
    
    CGContextSetGrayFillColor(context, 0.0f, 0.8);
    
    // Center HUD
    CGRect allRect = self.bounds;
    // Draw rounded HUD backgroud rect
    CGRect boxRect = CGRectMake(round((allRect.size.width - _size.width) / 2),
                                round((allRect.size.height - _size.height) / 2) , _size.width, _size.height);
    float radius = 10;
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect));
    CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0);
    CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0);
    CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0);
    CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0);
    CGContextClosePath(context);
    CGContextFillPath(context);
    
    UIGraphicsPopContext();
}

kvo监控属性变化,使用者在修改属性时,触发页面刷新,赋上新值。注意在页面销毁时要取消kvo监控,否则程序会崩溃

#pragma mark - KVO
- (void)registerForKVO {
    for (NSString *keyPath in [self observableKeypaths]) {
        [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
    }
}

- (void)unregisterFromKVO {
    for (NSString *keyPath in [self observableKeypaths]) {
        [self removeObserver:self forKeyPath:keyPath];
    }
}

- (NSArray *)observableKeypaths {
    return [NSArray arrayWithObjects:@"mode", @"labelText", nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO];
    } else {
        [self updateUIForKeypath:keyPath];
    }
}

- (void)updateUIForKeypath:(NSString *)keyPath {
    if ([keyPath isEqualToString:@"mode"]) {
        [self updateIndicators];
    } else if ([keyPath isEqualToString:@"labelText"]) {
        _label.text = self.labelText;
    }
}


- (void)dealloc {
    [self unregisterFromKVO];
}

最终效果如下图:

Paste_Image.png

最后附上 demo的地址:https://github.com/superzcj/ZCJHUD

总结

MBProcessHUD还是比较简单的,都是一些常用的东西。
希望借助这篇文章,动手仿写一遍MBProcessHUD,能更深刻地理解和认识MBProcessHUD。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 4.13 半块鹅蛋 饼子 青菜 八宝粥 中午,油饼一小块,米饭6口 土豆丝➕西红柿鸡蛋汤 晚上一块饼干 一个橘子 红豆汤
    麦子旋阅读 150评论 0 0
  • 在我实习之前,我和大多数人一样,认为小孩子都特别难管、特别烦人、特别不听话。而在我当了小学教师之后,我才发现原来孩...
    Airing阅读 1,026评论 0 3