小結(jié)MBProgressHUD的設(shè)計(jì)
http://www.lxweimin.com/p/485b8d75ccd4
http://ju.outofmemory.cn/entry/124817
1. 工廠模式
updateIndicators
中根據(jù)mode屬性生成不同的View. 這種方式,在寫UI組件會(huì)經(jīng)常用到。
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/** 轉(zhuǎn)菊花 Progress is shown using an UIActivityIndicatorView. This is the default. */
MBProgressHUDModeIndeterminate,
/** 餅圖 Progress is shown using a round, pie-chart like, progress view. */
MBProgressHUDModeDeterminate,
/** 水平方向的進(jìn)度條 Progress is shown using a horizontal progress bar */
MBProgressHUDModeDeterminateHorizontalBar,
/** 圓環(huán) Progress is shown using a ring-shaped progress view. */
MBProgressHUDModeAnnularDeterminate,
/** 自定義View Shows a custom view */
MBProgressHUDModeCustomView,
/** Shows only labels */
MBProgressHUDModeText
};
3.View管理自己的生命周期
開(kāi)發(fā)中經(jīng)??吹絼e人為了維護(hù)偶爾出現(xiàn)的View的生命周期(初始化、show、hide),在父view里添加屬性或?qū)嵗兞縼?lái)持有子View的指針??粗傆X(jué)繁瑣難受。MBProgressHUD這樣的菊花,在一個(gè)頁(yè)面的可能會(huì)多次出現(xiàn)、消失,但是我又不想在父View中持有它。 它的幾個(gè)show和hide的類方法就很小淸新:
show
+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [[self alloc] initWithView:view];
hud.removeFromSuperViewOnHide = YES;
[view addSubview:hud];
[hud show:animated];
return MB_AUTORELEASE(hud);
}
+ (MB_INSTANCETYPE)HUDForView:(UIView *)view {
NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:self]) {
return (MBProgressHUD *)subview;
}
}
return nil;
}
父view調(diào)用此類方法時(shí)傳入self指針,MBProgressHUD實(shí)例化自己,并add到super view上。
hide
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
MBProgressHUD *hud = [self HUDForView:view];
if (hud != nil) {
hud.removeFromSuperViewOnHide = YES;
[hud hide:animated];
return YES;
}
return NO;
}
+ (MB_INSTANCETYPE)HUDForView:(UIView *)view {
NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:self]) {
return (MBProgressHUD *)subview;
}
}
return nil;
}
+ (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated {
NSArray *huds = [MBProgressHUD allHUDsForView:view];
for (MBProgressHUD *hud in huds) {
hud.removeFromSuperViewOnHide = YES;
[hud hide:animated];
}
return [huds count];
}
+ (NSArray *)allHUDsForView:(UIView *)view {
NSMutableArray *huds = [NSMutableArray array];
NSArray *subviews = view.subviews;
for (UIView *aView in subviews) {
if ([aView isKindOfClass:self]) {
[huds addObject:aView];
}
}
return [NSArray arrayWithArray:huds];
}
MBProgressHUD自己拿到父View的指針,從superView.subviews數(shù)組中找出菊花視圖,隱藏。
這種設(shè)計(jì)很適合一些不常駐的View。比如頁(yè)面彈框。再配合Block使用(Block使用可以學(xué)習(xí)Masonry),代碼看起來(lái)妥妥的。比如:
/**
alert
*/
+ (instancetype)presentAlertInView:(UIView *)superView
withTitle:(NSString *)title
confirmTitle:(NSString *)confirmTitle
cancelTitle:(NSString *)cancelTitle
confirmBlock:(void (^)(id sender))confirmBlock
cancelBlock:(void (^)(id sender))cancelBlock;
+ (void)hideAllAlertForView:(UIView *)superview;
又比如一個(gè)結(jié)果彈層:
// 事件回調(diào)
typedef void(^RYButtonActionBlock)(void);
// 定義一個(gè)數(shù)據(jù)模型
@interface RYResultViewParams : NSObject
@property (nonatomic , strong) NSString *param;
// ...
@interface RYResultView : UIView
/**
用Block傳遞參數(shù), 事件回調(diào)
*/
+ (RYResultView *)showReusltViewWithParams:(void (^) (RYResultViewParams *))params_block
buttonActionBlock:(RYButtonActionBlock)action;
/**
隱藏
*/
+ (NSInteger)hideAllResultViewFromView:(UIView *)superView;
@end
@implementation RYResultView
+ (RYResultView *)showReusltViewWithParams:(void (^)(RYResultViewParams *))params_block buttonActionBlock:(RYButtonActionBlock)action
{
RYResultView *resultView = [[RYResultView alloc] initWithParams:params_block buttonActionBlock:action];
// ...
return resultView;
}
+ (NSInteger)hideAllResultViewFromView:(UIView *)superView
{
// ...
}
- (RYResultView *)initWithParams:(void (^)(RYResultViewParams *))params_block buttonActionBlock:(RYButtonActionBlock)action
{
self = [super init];
// 獲取外部傳入的參數(shù)
RYResultViewParams *params = [RYResultViewParams new];
if (params_block) {
params_block(params);
}
return self;
}
外部使用簡(jiǎn)潔明了:
[RYResultView showReusltViewWithParams:^(RYResultViewParams *parmas) {
parmas.param = @"....";
//...
} buttonActionBlock:^{
//....
}];
KVO
具體KVO的介紹,仔細(xì)看看objc的文章哦 https://objccn.io/issue-7-3/
通常我們有這樣的需求,一個(gè)對(duì)象屬性值發(fā)生變化,會(huì)自動(dòng)觸發(fā)一些邏輯。就是觀察者模式啦。而Observer模式中使用Notification, 走全局NotificationCenter太重了,KVO可以一對(duì)象對(duì)一對(duì)象, 被觀察的屬性setter方法或者通過(guò)KVC方式改變值時(shí),都會(huì)觸發(fā)- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
方法。善加應(yīng)用,能讓代碼更簡(jiǎn)潔淸新。
- 注冊(cè)observer
- 在
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
對(duì)應(yīng)path的屬性值發(fā)生變化,需要觸發(fā)的邏輯 - 移除observer,否則我們我們的 app 會(huì)因?yàn)槟承┢婀值脑虮罎ⅰ?br> 比如被觀察的對(duì)象已經(jīng)delloc了,但是observers還沒(méi)反注冊(cè),有野指針問(wèn)題.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fde9ae08040 of class RYMultimediaRecordView was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800042cd00> (
<NSKeyValueObservance 0x608000450980: Observer: 0x7fde9ae08040, Key path: isVideoRecording, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x6080006500b0>
)'
// MBProgressHUD初始化時(shí)被調(diào)用
- (void)registerForKVO {
for (NSString *keyPath in [self observableKeypaths]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
}
}
// dealloc 中調(diào)用
- (void)unregisterFromKVO {
for (NSString *keyPath in [self observableKeypaths]) {
[self removeObserver:self forKeyPath:keyPath];
}
}
// 需要觀察的屬性
- (NSArray *)observableKeypaths {
return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor",
@"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil];
}
// UI 更新
- (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"] || [keyPath isEqualToString:@"customView"] ||
[keyPath isEqualToString:@"activityIndicatorColor"]) {
[self updateIndicators];
} else if ([keyPath isEqualToString:@"labelText"]) {
label.text = self.labelText;
} else if ([keyPath isEqualToString:@"labelFont"]) {
label.font = self.labelFont;
} else if ([keyPath isEqualToString:@"labelColor"]) {
label.textColor = self.labelColor;
} else if ([keyPath isEqualToString:@"detailsLabelText"]) {
detailsLabel.text = self.detailsLabelText;
} else if ([keyPath isEqualToString:@"detailsLabelFont"]) {
detailsLabel.font = self.detailsLabelFont;
} else if ([keyPath isEqualToString:@"detailsLabelColor"]) {
detailsLabel.textColor = self.detailsLabelColor;
} else if ([keyPath isEqualToString:@"progress"]) {
if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(progress) forKey:@"progress"];
}
return;
}
[self setNeedsLayout];
[self setNeedsDisplay];
}
比如MBProgressHUD的labelFont屬性發(fā)生變化,會(huì)觸發(fā)一些UI刷新。這里的使用還比較簡(jiǎn)單初級(jí)。至于KVO和Context、NSKeyValueObservingOptions、線程啊,還是要強(qiáng)烈推薦這文章https://objccn.io/issue-7-3/