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 方法。善加应用,能让代码更简洁淸新。

  1. 注册observer
  2. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 对应path的属性值发生变化,需要触发的逻辑
  3. 移除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/

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

推荐阅读更多精彩内容