前言
閱讀優(yōu)秀的開源項目是提高編程能力的有效手段混滔,我們能夠從中開拓思維舶吗、拓寬視野征冷,學(xué)習到很多不同的設(shè)計思想以及最佳實踐。閱讀他人代碼很重要誓琼,但動手仿寫检激、練習卻也是很有必要的肴捉,它能進一步加深我們對項目的理解,將這些東西內(nèi)化為自己的知識和能力叔收。然而真正做起來卻很不容易齿穗,開源項目閱讀起來還是比較困難,需要一些技術(shù)基礎(chǔ)和耐心饺律。
本系列將對一些著名的iOS開源類庫進行深入閱讀及分析窃页,并仿寫這些類庫的基本實現(xiàn),加深我們對底層實現(xiàn)的理解和認識复濒,提升我們iOS開發(fā)的編程技能脖卖。
MBProcessHUD
MBProcessHUD是一個iOS上的提示框庫,支持加載提示巧颈、進度框畦木、文字提示等,使用簡單洛二,功能強大馋劈,還能夠自定義顯示內(nèi)容,廣泛應(yīng)用于iOS app中晾嘶。這是它的地址:https://github.com/jdg/MBProgressHUD
簡單看一下界面效果:
實現(xiàn)原理
MBProcessHUD繼承自UIView妓雾,實際上是一個覆蓋全屏的半透明指示器組件。它由以下幾個部分構(gòu)成垒迂,分別是:Loading加載動畫械姻,標題欄,背景欄以及其它欄(如詳情欄机断、按鈕)楷拳。我們把MBProcessHUD添加到頁面上,顯示任務(wù)進度及提示信息吏奸,同時屏蔽用戶交互操作欢揖。
MBProcessHUD的Loading加載動畫來自系統(tǒng)類UIActivityIndicatorView
,在頁面加載時奋蔚,開啟轉(zhuǎn)圈動畫她混,頁面銷毀時取消轉(zhuǎn)圈動畫。
MBProcessHUD根據(jù)加載內(nèi)容動態(tài)布局泊碑,它通過計算需要顯示的內(nèi)容坤按,動態(tài)調(diào)整頁面元素的位置大小,放置到屏幕的中央馒过,顯示的內(nèi)容可以由使用者指定臭脓。MBProcessHUD v1.0版之前是通過frame計算各個元素的位置,最新的版本采用了約束布局腹忽。
MBProcessHUD使用KVO監(jiān)聽一些屬性值的變化来累,如labelText砚作,model。這些屬性被修改時佃扼,MBProcessHUD視圖相應(yīng)更新偎巢,傳入新值。
仿寫MBProcessHUD
我們模仿MBProcessHUD寫一個簡單的彈出框組件兼耀,以加深對它的理解。在這個demo中求冷,我們不完全重寫MBProcessHUD瘤运,只實現(xiàn)基本功能。
首先在demo中創(chuàng)建ZCJHUD匠题,繼承UIView拯坟。
在ZCJHUD頭文件中,定義幾種顯示模式
typedef NS_ENUM(NSInteger, ZCJHUDMode) {
/** 轉(zhuǎn)圈動畫模式韭山,默認值 */
ZCJHUDModeIndeterminate,
/** 只顯示標題 */
ZCJHUDModeText
};
定義對外的接口郁季,顯示模式mode,標題內(nèi)容labelText
@interface ZCJHUD : UIView
@property (nonatomic, assign) ZCJHUDMode mode;
@property (nonatomic, strong) NSString *labelText;
- (instancetype)initWithView:(UIView *)view;
- (void)show;
- (void)hide;
@end
自身初始化钱磅,設(shè)置組件默認屬性梦裂,更新布局,注冊kvo監(jiān)視屬性變化盖淡。
- (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;
}
初始化轉(zhuǎn)圈動畫年柠,并添加到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動態(tài)布局
- (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監(jiān)控屬性變化,使用者在修改屬性時味赃,觸發(fā)頁面刷新掀抹,賦上新值。注意在頁面銷毀時要取消kvo監(jiān)控心俗,否則程序會崩潰
#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];
}
最終效果如下圖:
最后附上 demo的地址:https://github.com/superzcj/ZCJHUD
總結(jié)
MBProcessHUD還是比較簡單的傲武,都是一些常用的東西。
希望借助這篇文章另凌,動手仿寫一遍MBProcessHUD谱轨,能更深刻地理解和認識MBProcessHUD。