runtime為類別增加屬性

weixin_33782386發表於2018-01-29

runtime的一個重要功能就是增加類的屬性,而一般通過類別我們是無法增加類的屬性的,runtime很好的實現了這個功能。下面我們就以當為導航欄增加透明屬性來一步步分析增加屬性的實現。我們通過剖析UINavigationBar+Awesome.m第三方來逐步分析,目前這個第三方在iOS11上獲取子檢視上有bug,本人也沒有找到好的解決方法,如果有同學有好的思路歡迎指點。demo在文末獻上:

提供的方法如下:

//設定背景色
- (void)lt_setBackgroundColor:(UIColor *)backgroundColor;
//設定透明度
- (void)lt_setElementsAlpha:(CGFloat)alpha;
//設定移動的透明高度
- (void)lt_setTranslationY:(CGFloat)translationY;
//移除新增runtime新增的view,所有設定置nil
- (void)lt_reset;

1、首先建立UINavigationBar的類別,匯入runtime相關的庫,如下:

#import "UINavigationBar+Awesome.h"
#import <objc/runtime.h>

2、利用runtime設定屬性

//定義常量,必須是C語言字串
static char overlayKey;

//通過自己編寫setter和getter方法,新增真正可以使用的屬性。
- (UIView *)overlay
{
    //我們返回的是一個UIView的物件
    return objc_getAssociatedObject(self, &overlayKey);
}

- (void)setOverlay:(UIView *)overlay
{
    // 引數1:源物件
    // 引數2:關聯時用來標記屬性的key(因為可能要新增很多屬性)
    // 引數3:關聯的物件
    // 引數4:關聯策略
    // 關聯方法
    objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    /*
     objc_AssociationPolicy引數使用的策略:
     OBJC_ASSOCIATION_ASSIGN;            //assign策略
     OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略
     OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略
     
     OBJC_ASSOCIATION_RETAIN;
     OBJC_ASSOCIATION_COPY;
     */
}

3、開始使用屬性,改變背景顏色

- (void)lt_setBackgroundColor:(UIColor *)backgroundColor
{
    if (!self.overlay) {
//        https://www.jianshu.com/p/31f1637e2349對此也有介紹
        //呼叫- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics方法設定空圖片,置顏色看不到,僅能看到一個透明背景的UINavigationBar
        [self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
        self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, -20, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + 20)];
        self.overlay.userInteractionEnabled = NO;
        //設定View寬高自適應
        self.overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        /*在特定位置插入子檢視
        view:被插入的子檢視。
        index:被插入的位置下標,位置下標從0開始;下標不能大於子檢視的總數。
        View只能有一個父檢視,如果,該子檢視已經存在父檢視,那麼執行該方法後,舊的父檢視將被新父檢視所覆蓋。
        */
        [self insertSubview:self.overlay atIndex:0];
    }
    self.overlay.backgroundColor = backgroundColor;
}

4、導航欄移動

導航欄隨著表的移動而移動,同時改變title以及兩邊的view檢視的透明度,這裡需要注意下,發現_liftViews在iOS11裡面無法使用了(iPhone X釋出之前使用這些方法都沒問題),會導致崩潰,如下分析:

//設定移動的高度
- (void)lt_setTranslationY:(CGFloat)translationY
{
    //以初始位置為基準,x軸方向上平移x單位,在y軸方向上平移y單位。此處為在Y軸上向上移動單位數。
    self.transform = CGAffineTransformMakeTranslation(0, translationY);

}

//設定
- (void)lt_setElementsAlpha:(CGFloat)alpha
{
    /*
     本來我們可以通過用runtime獲取Bar屬性,可以獲取所有的檢視屬性,通過以下的遍歷方法可以獲取到對應的檢視,然後它就可以被我們隨意擺佈了,嘿嘿嘿~~
     でも(但是):我們獲取的_liftViews,_rightViews發現在iOS11裡面不能用了,使用的話會崩潰,提示如下:
     Terminating app due to uncaught exception 'NSUnknownKeyException',
     reason: '[<UINavigationBar 0x7fbdc55141b0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _liftViews.'
     沒有_liftViews這個key了,也是無奈,我的處理方式是粗暴的用self.alpha = alpha;統一設定了檢視透明度了。
         self.alpha = alpha;
     */

//    [[self valueForKey:@"_liftViews"] enumerateObjectsUsingBlock:^(UIView *view, NSUInteger i, BOOL *stop) {
//        view.alpha = alpha;
//    }];
//
//    [[self valueForKey:@"_rightViews"] enumerateObjectsUsingBlock:^(UIView *view, NSUInteger i, BOOL *stop) {
//        view.alpha = alpha;
//    }];
//
//    UIView *titleView = [self valueForKey:@"_titleView"];
//    titleView.alpha = alpha;
////    when viewController first load, the titleView maybe nil
//    [[self subviews] enumerateObjectsUsingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
//        if ([obj isKindOfClass:NSClassFromString(@"UINavigationItemView")]) {
//            obj.alpha = alpha;
//            *stop = YES;
//        }
//    }];
}

5、為了靈活設定,在離開頁面的時候呼叫一下方法去除runtime新增的view的影響:

//最終在離開頁面的時候呼叫,然後移除通過runtime新增的所有檢視。
- (void)lt_reset
{
    [self setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
    [self.overlay removeFromSuperview];
    self.overlay = nil;
}

最後感謝作者,demo下載地址:https://github.com/ltebean/LTNavigationBar

相關文章