MBProgressHUD设计技巧小启发
小结MBProgressHUD的设计
http://www.jianshu.com/p/485b8d75ccd4
http://ju.outofmemory.cn/entry/124817
1. 工厂模式
updateIndicators
中根据mode属性生成不同的View. 这种方式,在写UI组件会经常用到。
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/** 转菊花 Progress is shown using an UIActivityIndicatorView. This is the default. */
MBProgressHUDModeIndeterminate,
/** 饼图 Progress is shown using a round, pie-chart like, progress view. */
MBProgressHUDModeDeterminate,
/** 水平方向的进度条 Progress is shown using a horizontal progress bar */
MBProgressHUDModeDeterminateHorizontalBar,
/** 圆环 Progress is shown using a ring-shaped progress view. */
MBProgressHUDModeAnnularDeterminate,
/** 自定义View Shows a custom view */
MBProgressHUDModeCustomView,
/** Shows only labels */
MBProgressHUDModeText
};
3.View管理自己的生命周期
开发中经常看到别人为了维护偶尔出现的View的生命周期(初始化、show、hide),在父view里添加属性或实例变量来持有子View的指针。看着总觉繁琐难受。MBProgressHUD这样的菊花,在一个页面的可能会多次出现、消失,但是我又不想在父View中持有它。 它的几个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调用此类方法时传入self指针,MBProgressHUD实例化自己,并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数组中找出菊花视图,隐藏。
这种设计很适合一些不常驻的View。比如页面弹框。再配合Block使用(Block使用可以学习Masonry),代码看起来妥妥的。比如:
/**
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;
又比如一个结果弹层:
// 事件回调
typedef void(^RYButtonActionBlock)(void);
// 定义一个数据模型
@interface RYResultViewParams : NSObject
@property (nonatomic , strong) NSString *param;
// ...
@interface RYResultView : UIView
/**
用Block传递参数, 事件回调
*/
+ (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];
// 获取外部传入的参数
RYResultViewParams *params = [RYResultViewParams new];
if (params_block) {
params_block(params);
}
return self;
}
外部使用简洁明了:
[RYResultView showReusltViewWithParams:^(RYResultViewParams *parmas) {
parmas.param = @"....";
//...
} buttonActionBlock:^{
//....
}];
KVO
具体KVO的介绍,仔细看看objc的文章哦 https://objccn.io/issue-7-3/
通常我们有这样的需求,一个对象属性值发生变化,会自动触发一些逻辑。就是观察者模式啦。而Observer模式中使用Notification, 走全局NotificationCenter太重了,KVO可以一对象对一对象, 被观察的属性setter方法或者通过KVC方式改变值时,都会触发- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
方法。善加应用,能让代码更简洁淸新。
- 注册observer
- 在
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
对应path的属性值发生变化,需要触发的逻辑 - 移除observer,否则我们我们的 app 会因为某些奇怪的原因崩溃。
比如被观察的对象已经delloc了,但是observers还没反注册,有野指针问题.
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初始化时被调用
- (void)registerForKVO {
for (NSString *keyPath in [self observableKeypaths]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
}
}
// dealloc 中调用
- (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属性发生变化,会触发一些UI刷新。这里的使用还比较简单初级。至于KVO和Context、NSKeyValueObservingOptions、线程啊,还是要强烈推荐这文章https://objccn.io/issue-7-3/