MBProgressHUD設(shè)計(jì)技巧小啟發(fā)

小結(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)潔淸新。

  1. 注冊(cè)observer
  2. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 對(duì)應(yīng)path的屬性值發(fā)生變化,需要觸發(fā)的邏輯
  3. 移除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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容