iOS11適配之:0代碼實(shí)現(xiàn)導(dǎo)航欄UIBarButtonItem間距調(diào)整

下載源碼

GitHub上的代碼已經(jīng)更新(2017-09-28)惠啄,請(qǐng)不要copy文中代碼慎恒,代碼有遺漏!D於伞G珊拧!
另外姥闭,請(qǐng)?zhí)貏e注意,本套類擴(kuò)展只能在Xcode9以上的版本中使用越走,在Xcode9以前的開發(fā)環(huán)境中編譯打包都沒問題棚品,但 APP在iOS 11中運(yùn)行會(huì)奔潰!@鹊小M堋!

注意:

截止目前(2017-09-28)代碼已經(jīng)發(fā)生兩次更新骡澈,適配解決思路已經(jīng)發(fā)生變化锅纺,原來舊文章中的解決方式,會(huì)碰到如下問題:
1. iOS 11下肋殴,進(jìn)入一個(gè)添加了導(dǎo)航欄按鈕的頁(yè)面起初是正常的囤锉,當(dāng)push到下一個(gè)頁(yè)面再pop回來時(shí),會(huì)發(fā)現(xiàn)原來設(shè)置好的間距又失效了护锤;
2. UIBarButtonItem被添加到UIToolBar時(shí)有問題官地,嚴(yán)重時(shí)閃退;
3. 通過initWithBarButtonSystemItem:target:action:方法創(chuàng)建的item不能實(shí)現(xiàn)布局適配烙懦;

更新記錄:

版本1.2(2017-09-28)
  • 替換UIBarButtonItem原生實(shí)例化方法initWithBarButtonSystemItem:target:action:(最新版本中驱入,stone哥自己用ps摳了一套SystemItem的icon圖,不是很精細(xì),畢竟stone哥的強(qiáng)項(xiàng)是敲代碼亏较,如有用到該方法創(chuàng)建item莺褒,建議要自己家的美工做一套圖來替換)
  • 替換UIBarButtonItem原生的setTarget: 和 setAction: 方法;
  • 解決popBack后雪情,約束失效的問題遵岩,添加約束的位置發(fā)生變化,新增UIStackView的類擴(kuò)展旺罢,在其layoutSoubviews方法中實(shí)現(xiàn)約束條件的設(shè)置旷余;
版本1.1(2017-09-26)
  • 增加UIBarButtonItem被添加到UIToolbar上的處理;
  • 增加對(duì)UIBarButtonItem的UIBarButtonItemStyleDone類型的解析扁达;

對(duì)實(shí)現(xiàn)方式進(jìn)行的修改做一點(diǎn)解釋:

  1. 在iOS 11中正卧,UINavigationBar上有個(gè)contentView,contentView的兩邊各有個(gè)stackView跪解,分別用于渲染左右兩邊添加的UIBarButtonItem炉旷;
  2. 從一個(gè)頁(yè)面push到另一個(gè)頁(yè)面完成時(shí),前一個(gè)頁(yè)面叉讥,contentView上兩邊的stackView都會(huì)被移除并銷毀窘行;
  3. 頁(yè)面將要pop回去時(shí),會(huì)實(shí)例化兩個(gè)新的stackView用于承載前一個(gè)頁(yè)面的Item;

這就是為什么pop回去后图仓,原來添加的約束條件失效了罐盔,因?yàn)檫@個(gè)時(shí)候stackView已經(jīng)是一個(gè)新的對(duì)象了,并沒有對(duì)其添加約束救崔,所以解決思路變成了在stackView 的 layoutSubViews方法里進(jìn)行約束設(shè)置惶看。


想了解整個(gè)適配思路的演變驳癌,請(qǐng)看以下最初文章初稿政基,看思路,看知識(shí)點(diǎn)闽晦,代碼有誤劫窒,請(qǐng)download源碼本今。 (2017-09-22)

最新版Xcode9在20號(hào)已經(jīng)提供下載了,Stone哥哥作為一個(gè)凡事喜歡走在前面的人(不要臉了主巍,哈哈哈冠息,不過Stone哥哥的手機(jī)系統(tǒng)確實(shí)是從iOS 11第一個(gè)beta版開始使用的,體驗(yàn)過各種bug煎熬煤禽,終于熬到正式版了铐达,內(nèi)牛滿面...),當(dāng)然第一時(shí)間就升級(jí)了檬果,下載安裝完5個(gè)多雞的安裝包瓮孙,Stone哥哥激動(dòng)的打開了目前正在開發(fā)的項(xiàng)目唐断,Command+B,成功編譯杭抠!但是當(dāng)我點(diǎn)擊運(yùn)行脸甘,在APP中跳轉(zhuǎn)幾個(gè)頁(yè)面后,忽然注意到導(dǎo)航欄...WTF!!!

WTF.png

這間距可就大得有點(diǎn)驚人哈偏灿,頓時(shí)把Stone哥哥臉都嚇白了...

于是Stone哥哥趕緊把原來用于調(diào)整間距的BarButton的負(fù)寬度一口氣調(diào)到-50丹诀,[UIBarButtonItem zg_fixedSpaceWithWidth:-50];

+ (UIBarButtonItem *)zg_fixedSpaceWithWidth:(CGFloat)width {
    UIBarButtonItem *spaceBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                                                                                    target:nil
                                                                                    action:nil];
    spaceBarButton.width = width;
    return spaceBarButton;
}

然鵝...這 并沒什么卵用!并沒什么卵用翁垂!并沒什么卵用铆遭!

有點(diǎn)意思,就喜歡可以折騰一番的問題沿猜,所以我們還是和以往一樣枚荣,從發(fā)現(xiàn)問題開始,冷靜滴一步一步來把問題攻破啼肩。

一. 找出布局錯(cuò)亂的原因

既然老一套調(diào)整間距的方式已經(jīng)不起作用橄妆,那要么iOS 11增加了一種新的用于調(diào)整間距的UIBarButtonItem類型,要么就是整個(gè)解析發(fā)生變更祈坠,圖層有變化害碾,看圖層是否有變化這個(gè)最直觀又方便,所以我們先對(duì)圖層來一探究竟
![

iOS 11的NavBar布局.png

這是最新的iOS 11導(dǎo)航欄的視圖層級(jí)結(jié)構(gòu)赦拘,好復(fù)雜的樣子慌随,一個(gè)小小的item,疊了這么多層級(jí)躺同,而且使用了autoLayout來布局儒陨,雖然圖層很多,但看起來也很合理的樣子笋籽,而且基本上就可以確定對(duì)item間距的調(diào)整方向——更改layout約束條件。忘了iOS 11以前是什么樣子了椭员,但感覺有變化车海,所以我們?cè)賮砜纯磇OS 11以前的視圖層級(jí)結(jié)構(gòu)

iOS 11以前系統(tǒng)導(dǎo)航欄視圖層級(jí)結(jié)構(gòu).png

還真是不一樣,很顯然隘击,iOS 11以前系統(tǒng)導(dǎo)航欄的層級(jí)結(jié)構(gòu)好簡(jiǎn)單很多侍芝,而且沒有使用autoLayout。所有的視圖都堆在UINavigationBar上埋同,對(duì)比起來州叠,老版本確實(shí)不是很合理的樣子,心里默默為這個(gè)細(xì)小的優(yōu)化點(diǎn)個(gè)贊凶赁!
新版本的調(diào)整方向已經(jīng)確定了咧栗,那能不能讓老版本也統(tǒng)一呢逆甜,也給UINavigationButton也加上約束條件,拋棄fixedSpace類型的UIBarButtonItem致板。然鵝交煞,這種操作是被禁止的,程序會(huì)無情的crash


不能給NavigationBar添加約束.png

蘋果不允許開發(fā)者給UINavigationBar添加約束...

唉斟或!沒事素征,堅(jiān)強(qiáng)的Stone哥并沒有哭,那就分開兩種不同的適配方式吧萝挤,那我們?cè)賮韺?duì)iOS 11以前的老版本的渲染規(guī)則好好了解一番御毅,從前面圖“iOS 11以前系統(tǒng)導(dǎo)航欄視圖層級(jí)結(jié)構(gòu).png”和Stone哥的一番測(cè)試,得出了一下一些結(jié)論:

  • 系統(tǒng)自己創(chuàng)建的UINavigationButton內(nèi)含的圖片和標(biāo)題水平和垂直方向都是居中對(duì)齊怜珍;
  • UINavigationButton高度上沒有撐滿整個(gè)UINavigationBar的高度端蛆,并且沒有居中對(duì)齊,圖中右邊的兩個(gè)item就很明顯沒在垂直方向?qū)R绘面;
  • 另外經(jīng)過一點(diǎn)點(diǎn)微調(diào)欺税,UINavigationButton左右兩邊與屏幕邊緣的距離都是15,右邊的兩個(gè)item間距大概為5揭璃,固定不可調(diào)整晚凿;

看到這里,你可能跟我一樣想吐槽了瘦馍,原來iOS 11以前的系統(tǒng)導(dǎo)航欄渲染是這么隨意歼秽,這里辣雞...哈哈哈,所以Stone哥得好好的拯救他一下情组。

現(xiàn)在問題的根源了解得差不多了燥筷,也基本有了解決思路,所以是時(shí)候進(jìn)入解決問題的第二步了院崇。

二. 解題思路

1. 針對(duì)老版本:

看到老版本中肆氓,系統(tǒng)從UIBarButtonItem到添加到UINavigationBar上的UINavigationButton的轉(zhuǎn)化如此糟糕,而且想要再對(duì)其進(jìn)行修改極其困難底瓣,決定要阻斷這一層轉(zhuǎn)換谢揪,全都創(chuàng)建自己的CustomView,并對(duì)其進(jìn)行對(duì)其設(shè)置捐凭,即添加到左邊則左對(duì)齊拨扶,右邊則右對(duì)齊,并且高度撐滿整個(gè)導(dǎo)航欄茁肠,總之就是這個(gè)customView要彌補(bǔ)前面提到的老版本的所有不足患民。

2. 針對(duì)新版本:

前面已經(jīng)提到,新版本通過改變約束來實(shí)現(xiàn)調(diào)整垦梆,但是具體在什么時(shí)候匹颤,在哪個(gè)地方來調(diào)整呢仅孩,首先我想到在[UINavigationBar layoutSubviews] 方法里遍歷subViews來設(shè)置約束,但是subViews里最終遍歷到UIStackView的時(shí)候惋嚎,并沒有position信息杠氢,即不知道這個(gè)視圖是被添加在了左邊還是右邊,所以很顯然也會(huì)需要CustomView另伍,并在其中包含位置信息鼻百,既然這樣,何不將改變約束的方法放在CustomView的layoutSubviews里呢摆尝,減少對(duì)一個(gè)系統(tǒng)類的修改應(yīng)該是降低風(fēng)險(xiǎn)降低復(fù)雜度的操作吧温艇,哈哈,要在CustomView里設(shè)置約束的話堕汞,那CustomView還需要包含另外一條信息勺爱,就是與它相鄰的另外一個(gè)CustomView,因?yàn)橐O(shè)置兩兩之間的間距讯检。

  • 到這里琐鲁,我們的主角CustomView類就有了兩個(gè)必須的屬性了,一個(gè)描述被添加的位置(左或右)的position人灼,一個(gè)指向前一個(gè)相鄰CustomView的屬性prevCustomView围段;
  • 另外要阻斷系統(tǒng)對(duì)UIBarButtonItem的轉(zhuǎn)換,CustomView還應(yīng)該增加幾個(gè)和UIBarButtonItem對(duì)應(yīng)的實(shí)例化方法投放,最終得到了CustomView類的聲明如下
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, ZGBarButtonItemPosition) {
    ZGBarButtonItemPositionLeft,
    ZGBarButtonItemPositionRight
};

typedef NS_ENUM(NSInteger, ZGBarButtonItemType) {
    ZGBarButtonItemTypeTitle,
    ZGBarButtonItemTypeImage,
    ZGBarButtonItemTypeCustomView
};

@interface ZGBarButtonItemCustomView : UIView

@property (nonatomic, assign) ZGBarButtonItemPosition position;
@property (nonatomic, weak) ZGBarButtonItemCustomView *prevCustomView;
@property (nonatomic, assign) ZGBarButtonItemType itemType;

- (instancetype)initWithTitle:(NSString *)title target:(id)target action:(SEL)action;
- (instancetype)initWithImage:(UIImage *)image target:(id)target action:(SEL)action;
- (instancetype)initWithCustomView:(UIView *)customView;

@end
  • 針對(duì)老版本奈泪,CustomView 設(shè)置對(duì)其方式,自適應(yīng)大芯姆肌涝桅;
  • 針對(duì)新版本,CustomView要設(shè)置約束烙样;
  • 因?yàn)樾枰砑禹憫?yīng)冯遂,所以CustomView上應(yīng)該添加一個(gè)button;
  • 另外由于CustomView上的button的圖片和文字正常情況下無法隨UINavigationBar的tintColor改變谒获,所以還得設(shè)置跟隨tintColor债蜜,圖片好說,本身的renderMode渲染模式就支持跟隨tintColor改變究反,但是titleLabel不能,所以還要設(shè)置KVO監(jiān)聽UINavigationBar.tintColor的改變儒洛,隨時(shí)更改titleLabel.textColor精耐。
    所以得到CustomView的實(shí)現(xiàn)代碼如下:
#import "ZGBarButtonItemCustomView.h"
#import "UIView+ZGLayoutConstraint.h"
#import "ZGNavBarItemSpceMacro.h"

@interface ZGBarButtonItemCustomView ()

@property (nonatomic, strong) UIButton *button;
@property (nonatomic, assign) BOOL fixed;
@property (nonatomic, assign) BOOL isLastItem;
@property (nonatomic, weak) UINavigationBar *navBar;

@end

@implementation ZGBarButtonItemCustomView

- (instancetype)initWithTitle:(NSString *)title target:(id)target action:(SEL)action {
    if (self = [super init]) {
        [self p_setUpButtonWithTitle:title
                               image:nil
                              target:target
                              action:action];
        [self p_init];
        self.itemType = ZGBarButtonItemTypeTitle;
    }
    return self;
}

- (instancetype)initWithImage:(UIImage *)image target:(id)target action:(SEL)action {
    if (self = [super init]) {
        [self p_setUpButtonWithTitle:nil
                               image:image
                              target:target
                              action:action];
        [self p_init];
        self.itemType = ZGBarButtonItemTypeImage;
    }
    return self;
}

- (instancetype)initWithCustomView:(UIView *)customView {
    if (self = [super init]) {
        [self addSubview:customView];
        [self setFrame:customView.bounds];
        [self setCenter:customView.center];
        
        [self p_init];
        self.itemType = ZGBarButtonItemTypeCustomView;
    }
    return self;
}

- (void)dealloc {
    [self.navBar removeObserver:self forKeyPath:@"tintColor"];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    if ([[UIDevice currentDevice] systemVersion].floatValue < 11) {
        [self p_setTitleFollowNavBarTintColorFromView:self];
        return;
    }
    
    if (self.fixed) {
        return;
    }
    
    UIView *adaptorView = [self p_getAdaptorViewFromView:self];
    UIView *prevAdaptorView = [self p_getAdaptorViewFromView:self.prevCustomView];
    [adaptorView zg_addSizeConstraintWithSize:self.frame.size];
    [adaptorView zg_addCenterYConstraint];
    CGFloat screenBorderGap = ZG_BAR_ITEM_SCREEN_BORDER_GAP;
    
    if (self.position == ZGBarButtonItemPositionLeft) {
        if (!prevAdaptorView) {
            [adaptorView zg_addLeftBorderGap:0];
        } else {
            [prevAdaptorView zg_addHorizontalGap:ZG_BAR_ITEM_GAP toView:adaptorView];
        }
        
        if (self.isLastItem) {
            UIStackView *stackView = [self p_getStackViewFromView:adaptorView];
            for (NSLayoutConstraint *constraint in stackView.superview.constraints) {
                if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                    constraint.firstAttribute == NSLayoutAttributeLeading) {
                    [stackView.superview removeConstraint:constraint];
                }
            }
            if (self.itemType == ZGBarButtonItemTypeImage) {
                screenBorderGap -= ZG_BAR_ITEM_LEFT_ICON_EDGE_INSETS;
            }
            [stackView zg_addLeftBorderGap:screenBorderGap];
        }
        
    } else if (self.position == ZGBarButtonItemPositionRight) {
        if (!prevAdaptorView) {
            [adaptorView zg_addRightBorderGap:0];
        } else {
            [adaptorView zg_addHorizontalGap:-ZG_BAR_ITEM_GAP toView:prevAdaptorView];
        }
        
        if (self.isLastItem) {
            UIStackView *stackView = [self p_getStackViewFromView:adaptorView];
            for (NSLayoutConstraint *constraint in stackView.superview.constraints) {
                if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                    constraint.firstAttribute == NSLayoutAttributeTrailing) {
                    [stackView.superview removeConstraint:constraint];
                }
            }
            if (self.itemType == ZGBarButtonItemTypeImage) {
                screenBorderGap -= ZG_BAR_ITEM_RIGHT_ICON_EDGE_INSETS;
            }
            [stackView zg_addRightBorderGap:-screenBorderGap];
        }
    }
    
    [self p_setTitleFollowNavBarTintColorFromView:adaptorView];
    self.fixed = YES;
}

#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    [self.button setTitleColor:self.navBar.tintColor forState:UIControlStateNormal];
}

#pragma mark - private
- (void)p_init {
    self.isLastItem = YES;
    self.fixed = NO;
    self.position = ZGBarButtonItemPositionLeft;
}

- (void)p_setUpButtonWithTitle:(NSString *)title image:(UIImage *)image target:(id)target action:(SEL)action {
    [self setButton:[[UIButton alloc] init]];
    [self addSubview:self.button];
    [self.button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
    [self.button setTintColor:[UIColor blueColor]];
    [self.button setTitle:title forState:UIControlStateNormal];
    [self.button.titleLabel setFont:ZG_BAR_ITEM_FONT];
    if (image.renderingMode == UIImageRenderingModeAutomatic) {
        image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    }
    [self.button setImage:image forState:UIControlStateNormal];
    [self.button sizeToFit];
    [self.button setFrame:CGRectMake(0, 0, MAX(self.button.frame.size.width, ZG_BAR_ITEM_MIN_WIDTH), 44)];
    [self.button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    [self setFrame:self.button.bounds];
}

- (UIView *)p_getAdaptorViewFromView:(UIView *)view {
    if (!view) {
        return nil;
    }
    UIView *tempView = view;
    while (![tempView isKindOfClass:NSClassFromString(@"_UITAMICAdaptorView")] && tempView.superview) {
        tempView = tempView.superview;
    }
    return tempView;
}

- (UIStackView *)p_getStackViewFromView:(UIView *)view {
    if (!view) {
        return nil;
    }
    UIView *tempView = view;
    while (![tempView isKindOfClass:UIStackView.class] && tempView.superview) {
        tempView = tempView.superview;
    }
    return (UIStackView *)tempView;
}

- (UINavigationBar *)p_getNavBarViewFromView:(UIView *)view {
    if (!view) {
        return nil;
    }
    UIView *tempView = view;
    while (![tempView isKindOfClass:UINavigationBar.class] && tempView.superview) {
        tempView = tempView.superview;
    }
    return (UINavigationBar *)tempView;
}

- (void)p_setTitleFollowNavBarTintColorFromView:(UIView *)view {
    if (self.itemType == ZGBarButtonItemTypeTitle) {
        self.navBar = [self p_getNavBarViewFromView:view];
        [self.button setTitleColor:self.navBar.tintColor forState:UIControlStateNormal];
        
        [self.navBar addObserver:self
                      forKeyPath:@"tintColor"
                         options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                         context:nil];
    }
}

#pragma mark - setter & getter
- (void)setPosition:(ZGBarButtonItemPosition)position {
    _position = position;
    
    if (self.position == ZGBarButtonItemPositionLeft) {
        [self.button setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
    } else {
        [self.button setContentHorizontalAlignment:UIControlContentHorizontalAlignmentRight];
    }
}

- (void)setPrevCustomView:(ZGBarButtonItemCustomView *)prevCustomView {
    _prevCustomView = prevCustomView;
    self.prevCustomView.isLastItem = NO;
}

@end

最后一步就是選擇在合適的創(chuàng)建CustomView,和給CustomView設(shè)置其他屬性了琅锻,這個(gè)很顯然要分別給UIBarButtonItem和UINavigationItem寫擴(kuò)展卦停,切面替換相關(guān)方法了向胡,直接上代碼:

UIBarButtonItem+ZGFixSpace.h

#import <UIKit/UIKit.h>
#import "ZGBarButtonItemCustomView.h"

@interface UIBarButtonItem (ZGFixSpace)

/*
 *  used before iOS 11
 */
+ (UIBarButtonItem *)zg_fixedSpaceWithWidth:(CGFloat)width;

/*
 *  the side the item be added in (left or right)
 *  used after iOS 11
 */
- (void)zg_setPosition:(ZGBarButtonItemPosition)position;

/*
 *  is the first itme at the current side
 *  used after iOS 11
 */
- (void)zg_setPrevCustomView:(ZGBarButtonItemCustomView *)prevCustomView;

@end

UIBarButtonItem+ZGFixSpace.m

#import "UIBarButtonItem+ZGFixSpace.h"
#import "NSObject+ZGRuntime.h"

@implementation UIBarButtonItem (ZGFixSpace)

+ (UIBarButtonItem *)zg_fixedSpaceWithWidth:(CGFloat)width {
    UIBarButtonItem *spaceBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                                                                                    target:nil
                                                                                    action:nil];
    spaceBarButton.width = width;
    return spaceBarButton;
}

+ (void)load {
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(initWithTitle:style:target:action:)
                                    swizzledSel:@selector(zg_initWithTitle:style:target:action:)];
    
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(initWithImage:style:target:action:)
                                    swizzledSel:@selector(zg_initWithImage:style:target:action:)];
    
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(initWithCustomView:)
                                    swizzledSel:@selector(zg_initWithCustomView:)];
}

- (void)zg_setPosition:(ZGBarButtonItemPosition)position {
    ZGBarButtonItemCustomView *zgCustomView = (ZGBarButtonItemCustomView *)self.customView;
    zgCustomView.position = position;
}

- (void)zg_setPrevCustomView:(ZGBarButtonItemCustomView *)prevCustomView {
    ZGBarButtonItemCustomView *zgCustomView = (ZGBarButtonItemCustomView *)self.customView;
    zgCustomView.prevCustomView = prevCustomView;
}

- (instancetype)zg_initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
    ZGBarButtonItemCustomView *zgCustomView = [[ZGBarButtonItemCustomView alloc] initWithTitle:title
                                                                                        target:target
                                                                                        action:action];
    return [self zg_initWithCustomView:zgCustomView];
}

- (instancetype)zg_initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action {
    ZGBarButtonItemCustomView *zgCustomView = [[ZGBarButtonItemCustomView alloc] initWithImage:image
                                                                                        target:target
                                                                                        action:action];
    return [self zg_initWithCustomView:zgCustomView];
}

- (instancetype)zg_initWithCustomView:(UIView *)customView {
    ZGBarButtonItemCustomView *zgCustomView = [[ZGBarButtonItemCustomView alloc] initWithCustomView:customView];
    return [self zg_initWithCustomView:zgCustomView];
}

@end

這個(gè)擴(kuò)展主要任務(wù)是實(shí)現(xiàn)前面說的,阻斷系統(tǒng)從UIBarButtonItem到UINavigationButton的轉(zhuǎn)換惊完,實(shí)現(xiàn)手段為替換掉
UIBarButtonItem的三個(gè)實(shí)例化方法僵芹,在這三個(gè)方法中均創(chuàng)建一個(gè)CustomView,然后調(diào)用原生的initWithCustomView:方法小槐,最終將這個(gè)CustomView渲染到UINavigationBar上拇派,這樣不會(huì)再有UINavigationButton的存在了。


接下來是 UINavigationItem+ZGFixSpace

這個(gè)擴(kuò)展是替換掉在UIViewController中凿跳,給viewController.navigationItem添加item的四個(gè)方法件豌,給每個(gè)item.customView完善前面講到的position和prevCustomView兩個(gè)屬性,并針對(duì)iOS 11以前的版本控嗜,在item前添加一個(gè)用于調(diào)整與屏幕邊緣間距的彈簧item茧彤,最終就能實(shí)現(xiàn)各個(gè)版本一樣的自適應(yīng)調(diào)整間距的效果。

#import "UINavigationItem+ZGFixSpace.h"
#import "NSObject+ZGRuntime.h"
#import "UIBarButtonItem+ZGFixSpace.h"
#import "ZGNavBarItemSpceMacro.h"

@implementation UINavigationItem (ZGFixSpace)

+ (void)load {
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(setLeftBarButtonItem:)
                                    swizzledSel:@selector(zg_setLeftBarButtonItem:)];
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(setLeftBarButtonItems:)
                                    swizzledSel:@selector(zg_setLeftBarButtonItems:)];

    [self zg_swizzleInstanceMethodWithOriginSel:@selector(setRightBarButtonItem:)
                                    swizzledSel:@selector(zg_setRightBarButtonItem:)];
    [self zg_swizzleInstanceMethodWithOriginSel:@selector(setRightBarButtonItems:)
                                    swizzledSel:@selector(zg_setRightBarButtonItems:)];
}

- (void)zg_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem {
    if (!leftBarButtonItem || [leftBarButtonItem isKindOfClass:[NSNull class]]) {
        [self zg_setLeftBarButtonItem:nil];
        return;
    }
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
        [leftBarButtonItem zg_setPosition:ZGBarButtonItemPositionLeft];
        [self zg_setLeftBarButtonItem:leftBarButtonItem];
    } else {
        [self setLeftBarButtonItems:@[leftBarButtonItem]];
    }
}

- (void)zg_setLeftBarButtonItems:(NSArray *)leftBarButtonItems {
    if (!leftBarButtonItems || [leftBarButtonItems isKindOfClass:[NSNull class]] || leftBarButtonItems.count == 0) {
        [self zg_setLeftBarButtonItems:nil];
        return;
    }
    
    NSMutableArray *items = [NSMutableArray array];
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 11) {
        ZGBarButtonItemCustomView *customView = (ZGBarButtonItemCustomView *)((UIBarButtonItem *)[leftBarButtonItems firstObject]).customView;
        CGFloat gap = ZG_BAR_ITEM_SCREEN_BORDER_GAP;
        if (customView.itemType == ZGBarButtonItemTypeImage) {
            gap -= ZG_BAR_ITEM_LEFT_ICON_EDGE_INSETS;
        }
        [items addObject:[UIBarButtonItem zg_fixedSpaceWithWidth:-(15 - gap)]];
    }
    ZGBarButtonItemCustomView *prevCustomeView = nil;
    for (NSInteger i=0; i<leftBarButtonItems.count; i++) {
        UIBarButtonItem *item = [leftBarButtonItems objectAtIndex:i];
        [item zg_setPosition:ZGBarButtonItemPositionLeft];
        [items addObject:item];
        [item zg_setPrevCustomView:prevCustomeView];
        prevCustomeView = (ZGBarButtonItemCustomView *)item.customView;
    }
    [self zg_setLeftBarButtonItems:items];
}

- (void)zg_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem {
    if (!rightBarButtonItem || [rightBarButtonItem isKindOfClass:[NSNull class]]) {
        [self zg_setRightBarButtonItem:nil];
        return;
    }
    
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
        [rightBarButtonItem zg_setPosition:ZGBarButtonItemPositionRight];
        [self zg_setRightBarButtonItem:rightBarButtonItem];
    } else {
        [self setRightBarButtonItems:@[rightBarButtonItem]];
    }
}

- (void)zg_setRightBarButtonItems:(NSArray *)rightBarButtonItems {
    if (!rightBarButtonItems || [rightBarButtonItems isKindOfClass:[NSNull class]] || rightBarButtonItems.count == 0) {
        [self zg_setRightBarButtonItems:nil];
        return;
    }
    
    NSMutableArray *items = [NSMutableArray array];
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 11) {
        ZGBarButtonItemCustomView *customView = (ZGBarButtonItemCustomView *)((UIBarButtonItem *)[rightBarButtonItems firstObject]).customView;
        CGFloat gap = ZG_BAR_ITEM_SCREEN_BORDER_GAP;
        if (customView.itemType == ZGBarButtonItemTypeImage) {
            gap -= ZG_BAR_ITEM_RIGHT_ICON_EDGE_INSETS;
        }
        [items addObject:[UIBarButtonItem zg_fixedSpaceWithWidth:-(15 - gap)]];
    }
    ZGBarButtonItemCustomView *prevCustomeView = nil;
    for (NSInteger i=0; i<rightBarButtonItems.count; i++) {
        UIBarButtonItem *item = [rightBarButtonItems objectAtIndex:i];
        [item zg_setPosition:ZGBarButtonItemPositionRight];
        [item zg_setPrevCustomView:prevCustomeView];
        prevCustomeView = (ZGBarButtonItemCustomView *)item.customView;
        [items addObject:item];
    }
    [self zg_setRightBarButtonItems:items];
}

@end

另外還有兩個(gè)工具類擴(kuò)展

第一個(gè) NSObject+ZGRuntime疆栏,主要是給實(shí)例對(duì)象添加了一個(gè)交換實(shí)例方法的API曾掂,前面的兩個(gè)擴(kuò)展都是在+ (void)load 方法里調(diào)用這個(gè)方法來替換掉原生API。

- (void)zg_swizzleInstanceMethodWithOriginSel:(SEL)originSel swizzledSel:(SEL)swizzledSel {
    Method m1 = class_getInstanceMethod([self class], originSel);
    Method m2 = class_getInstanceMethod([self class], swizzledSel);
    method_exchangeImplementations(m1, m2);
}

第二個(gè) UIView+ZGLayoutConstraint壁顶,這個(gè)擴(kuò)展主要是提供了給View添加 尺寸(size)珠洗,Y坐標(biāo)中心點(diǎn)(centerY),與另一個(gè)view的水平間距(horizontalGap)博助,與父視圖邊緣間距等的約束的API险污,在CustomView類的layoutSubviews方法里,iOS 11以后的導(dǎo)航欄就是調(diào)用這些方法來添加約束富岳。

#import "UIView+ZGLayoutConstraint.h"

@implementation UIView (ZGLayoutConstraint)

- (void)zg_addSizeConstraintWithSize:(CGSize)size {
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                     attribute:NSLayoutAttributeWidth
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:nil
                                                     attribute:NSLayoutAttributeNotAnAttribute
                                                    multiplier:1.0
                                                      constant:size.width]];
    
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                     attribute:NSLayoutAttributeHeight
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:nil
                                                     attribute:NSLayoutAttributeNotAnAttribute
                                                    multiplier:1.0
                                                      constant:size.height]];
    
}

- (void)zg_addCenterYConstraint {
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                               attribute:NSLayoutAttributeCenterY
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.superview
                                                               attribute:NSLayoutAttributeCenterY
                                                              multiplier:1.0
                                                                constant:0]];
}

- (void)zg_addHorizontalGap:(CGFloat)gap toView:(UIView *)view {
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                               attribute:NSLayoutAttributeRight
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:view
                                                               attribute:NSLayoutAttributeLeft
                                                              multiplier:1.0
                                                                constant:gap]];
}

- (void)zg_addLeftBorderGap:(CGFloat)gap {
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                               attribute:NSLayoutAttributeLeading
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.superview
                                                               attribute:NSLayoutAttributeLeading
                                                              multiplier:1.0
                                                                constant:gap]];
}

- (void)zg_addRightBorderGap:(CGFloat)gap {
    [self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                               attribute:NSLayoutAttributeTrailing
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.superview
                                                               attribute:NSLayoutAttributeTrailing
                                                              multiplier:1.0
                                                                constant:gap]];
}

@end

最后蛔糯,也是最重要的,是便于開發(fā)者設(shè)置間距的宏文件 ZGNavBarItemSpceMacro

#ifndef ZGNavBarItemSpceMacro_h
#define ZGNavBarItemSpceMacro_h

#define ZG_BAR_ITEM_SCREEN_BORDER_GAP           10  // item到屏幕邊緣的距離
#define ZG_BAR_ITEM_GAP                         5   // item之間的距離 ios11以后生效
#define ZG_BAR_ITEM_LEFT_ICON_EDGE_INSETS       6   // 左邊item圖標(biāo)圖片內(nèi)邊距
#define ZG_BAR_ITEM_RIGHT_ICON_EDGE_INSETS      2   // 右邊item圖標(biāo)圖片內(nèi)邊距
#define ZG_BAR_ITEM_MIN_WIDTH                   44  // item的最小寬度
#define ZG_BAR_ITEM_FONT                        [UIFont systemFontOfSize:15 weight:UIFontWeightLight] // item字體 ios11以后生效


#endif /* ZGNavBarItemSpceMacro_h */

好啦窖式,全部干活已出蚁飒,感謝閱讀,歡迎去GitHub下載并點(diǎn)星星萝喘,愛你喲;绰摺!阁簸!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爬早,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子启妹,更是在濱河造成了極大的恐慌筛严,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饶米,死亡現(xiàn)場(chǎng)離奇詭異桨啃,居然都是意外死亡车胡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門照瘾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匈棘,“玉大人,你說我怎么就攤上這事析命≈魑溃” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碳却,是天一觀的道長(zhǎng)队秩。 經(jīng)常有香客問我,道長(zhǎng)昼浦,這世上最難降的妖魔是什么馍资? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮关噪,結(jié)果婚禮上鸟蟹,老公的妹妹穿的比我還像新娘。我一直安慰自己使兔,他們只是感情好建钥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著虐沥,像睡著了一般熊经。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欲险,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天镐依,我揣著相機(jī)與錄音,去河邊找鬼天试。 笑死槐壳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喜每。 我是一名探鬼主播务唐,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼带兜!你這毒婦竟也來了枫笛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤刚照,失蹤者是張志新(化名)和其女友劉穎刑巧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡海诲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了檩互。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片特幔。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闸昨,靈堂內(nèi)的尸體忽然破棺而出蚯斯,到底是詐尸還是另有隱情,我是刑警寧澤饵较,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布拍嵌,位于F島的核電站,受9級(jí)特大地震影響循诉,放射性物質(zhì)發(fā)生泄漏横辆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一茄猫、第九天 我趴在偏房一處隱蔽的房頂上張望狈蚤。 院中可真熱鬧,春花似錦划纽、人聲如沸脆侮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靖避。三九已至,卻和暖如春比默,著一層夾襖步出監(jiān)牢的瞬間幻捏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工退敦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粘咖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓侈百,卻偏偏與公主長(zhǎng)得像瓮下,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钝域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)讽坏、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,093評(píng)論 4 62
  • iOS11 導(dǎo)航欄按鈕位置問題的解決 雖然現(xiàn)在iOS11正式版還沒有出來,但是作為開發(fā)人員,相信很多開發(fā)者都在使用...
    spicyShrimp閱讀 1,599評(píng)論 0 2
  • Swift版本點(diǎn)擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,365評(píng)論 7 249
  • 簡(jiǎn)書是一個(gè)朋友介紹給我的胀葱,我曾經(jīng)告訴她我喜歡寫作漠秋,成為作家是我一個(gè)長(zhǎng)久的夢(mèng)想。昨天我下載了這個(gè)軟件抵屿,大致有了個(gè)...
    無緒中閱讀 151評(píng)論 2 0
  • 我與他真的好久不見庆锦,久到八、九年轧葛,久到聽說我們的同班小學(xué)同學(xué)有的已經(jīng)當(dāng)上父母搂抒,久到我們?cè)僖不夭蝗ツ沁b遠(yuǎn)的小屁孩...
    阿拉米琪家閱讀 319評(píng)論 3 4