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)解釋:
- 在iOS 11中正卧,UINavigationBar上有個(gè)contentView,contentView的兩邊各有個(gè)stackView跪解,分別用于渲染左右兩邊添加的UIBarButtonItem炉旷;
- 從一個(gè)頁(yè)面push到另一個(gè)頁(yè)面完成時(shí),前一個(gè)頁(yè)面叉讥,contentView上兩邊的stackView都會(huì)被移除并銷毀窘行;
- 頁(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!!!
這間距可就大得有點(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導(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)好簡(jiǎn)單很多侍芝,而且沒有使用autoLayout。所有的視圖都堆在UINavigationBar上埋同,對(duì)比起來州叠,老版本確實(shí)不是很合理的樣子,心里默默為這個(gè)細(xì)小的優(yōu)化點(diǎn)個(gè)贊凶赁!
新版本的調(diào)整方向已經(jīng)確定了咧栗,那能不能讓老版本也統(tǒng)一呢逆甜,也給UINavigationButton也加上約束條件,拋棄fixedSpace類型的UIBarButtonItem致板。然鵝交煞,這種操作是被禁止的,程序會(huì)無情的crash
蘋果不允許開發(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)星星萝喘,愛你喲;绰摺!阁簸!