在 iOS 11 以後,Apple 在導航欄中啓用了自動佈局的相關特性,這使得導航欄的使用方式發生了一些變化,今天咱們着重說說導航欄中 UIBarButtonItem 在 iOS 11 中的幾點變化。bash
在 WWDC 2018 的 Updating Your App for iOS 11中,咱們能夠知道 UINavigationBar 開始支持 Auto Layout 了。app
這對於 UIBarButtonItem 來說,意味着什麼呢?經過 view debug 工具咱們能夠發現,全部的 item 會被一個內置的 stack view 所管理。ide
當 Custom View 正確的實現了 sizeThatFits 或者 intrinsicContentSize 時,UI 的展示將不會出現問題。工具
在 iOS 11 中,爲了充分發揮 Auto Layout 特性,難免須要將 UIBarButtonItem 裏 Custom View 的 translatesAutoresizingMaskIntoConstraints 屬性設置爲 no,這就可能會形成它在 iOS 11 如下的系統中發生佈局錯亂,所以咱們須要在相應的地方寫上以下代碼。佈局
UIView *view = [UIView new];
if(@available(iOS 11, *)){
view.translatesAutoresizingMaskIntoConstraints = NO;
}
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:view];
self.navigationItem.rightBarButtonItem = item;
複製代碼
咱們以 Custom View 的方式建立兩個 UIBarButtonItem,經過 view debug 工具能夠查看 Custom View 的真實大小ui
在 iOS 10 以前的版本中,它的點擊區域如紅色區域所示:this
對於這個問題,能夠有兩種解決方案:atom
一種方式是經過重寫 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
方法來修改控件的點擊區域,保證其範圍控制在 44 * 44 pt 以上。例以下面的示例代碼:spa
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
CGSize acturalSize = self.frame.size;
CGSize minimumSize = kBarButtonMinimumTapAreaSize; // 44 * 44 pt
CGFloat verticalMargin = acturalSize.height - minimumSize.height >= 0 ? 0 : ((minimumSize.height - acturalSize.height ) / 2);
CGFloat horizontalMargin = acturalSize.width - minimumSize.width >= 0 ? 0 : ((minimumSize.width - acturalSize.width ) / 2);
CGRect newArea = CGRectMake(self.bounds.origin.x - horizontalMargin, self.bounds.origin.y - verticalMargin, self.bounds.size.width + 2 * horizontalMargin, self.bounds.size.height + 2 * verticalMargin);
return CGRectContainsPoint(newArea, point);
}
複製代碼
另外一種方式是建立一箇中間層視圖,保證其視圖的大小不小於 44 * 44 便可,例以下面的代碼:debug
@interface WrapperView : UIView
@property (nonatomic, assign) CGSize minimumSize;
@property (nonatomic, strong) UIView *underlyingView;
@end
@implementation WrapperView
- (instancetype)initWithUnderlyingView:(UIView *)underlyingView {
self = [super initWithFrame:underlyingView.bounds];
if (self) {
_underlyingView = underlyingView;
_minimumSize = CGSizeMake(44.0f, 44.0f);
underlyingView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:underlyingView];
NSLayoutConstraint *leadingConstraint = [underlyingView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor];
NSLayoutConstraint *trailingConstraint = [underlyingView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor];
NSLayoutConstraint *topConstraint = [underlyingView.topAnchor constraintEqualToAnchor:self.topAnchor];
NSLayoutConstraint *bottomConstraint = [underlyingView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor];
NSLayoutConstraint *heightConstraint = [self.heightAnchor constraintGreaterThanOrEqualToConstant:self.minimumSize.height];
NSLayoutConstraint *widthConstraint = [self.widthAnchor constraintGreaterThanOrEqualToConstant:self.minimumSize.width];
[NSLayoutConstraint activateConstraints:@[leadingConstraint, trailingConstraint, topConstraint, bottomConstraint, heightConstraint, widthConstraint]];
}
return self;
}
@end
複製代碼
固然最簡單的方法就是讓你的圖片變大一些,好比增大留白……
在 iOS 11 以前,咱們有兩種方式來修改 item 與屏幕的間距
當咱們使用 Custom View 類型的 item 時,item 與屏幕的邊距默認爲 16 或者 20 pt(PS:當屏幕爲5.5寸屏時,邊距爲 20 pt),下圖以 16 pt 爲例:
UIBarButtonItem *spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacer.width = -8;
self.navigationItem.rightBarButtonItems = @[spacer, item1, item2];
複製代碼
這個方案在 iOS 10 及如下的系統是有效的,效果以下圖所示:
可是一樣的代碼在 iOS 11 上就是失效的……
仔細研究 iOS 11 中的視圖佈局能夠發現,fixed space 類型的 item 在 Stack View 的 leading 和 trailling 時的行爲與其在 item 之間的行爲保持一致,它的最小寬度都爲 8pt,這也就說明了設置負數爲何會不生效了。
咱們能夠重寫某個視圖控件的 alignmentRectInsets 屬性,例以下面的代碼:
- (UIEdgeInsets)alignmentRectInsets {
return self.position == right ? UIEdgeInsetsMake(0, -8, 0, 8) : UIEdgeInsetsMake(0, 8, 0, -8);
}
複製代碼
當咱們設置 UIEdgeInsetsMake(0, -8, 0, 8) ,並將這段代碼用到前面的例子時,你會看到這樣的結果:
乍一看,感受這個方法彷佛解決了全部的問題,但仔細觀察,你就會發現這個方案也存在不完美的地方,例如超出 stack view 的部分將沒法響應點擊事件
方案三和方案二比較類似,經過修改 XXXEdgeInsets 屬性確實能讓控件在視覺上達到預期,但因爲 stack View 的存在,即便修改了 Custom View 的 hitTest 區域,也會存在沒法響應點擊事件的問題,因此這個方案也不是一個完美的解決方案:
雖然剛纔提到的三種解決方案在 iOS 11 中都沒法完美解決問題,但咱們仍是發現了一個有意思的現象
下圖是使用寬度爲負值,類型爲 Fixed Space 的 item 時的視圖佈局,雖然 fixed space 的存在 讓它在視覺上看起來離屏幕邊距仍是有點遠,但 item 控件自身與屏幕的邊距確實變小了。
這樣說可能讓人有點摸不着頭腦,咱們不妨來看看這中間的區別,下圖是使用了非 customView 建立的 UIBarButtonItem,與屏幕的間距爲 8 pt(當屏幕爲 5.5 寸時,爲 12 pt)
UIButton *customButton = [UIButton buttonWithType:UIButtonTypeCustom];
customButton.overrideAlignmentRectInsets = UIEdgeInsetsMake(0, x, 0, -x); // you should do this in your own custom class
customButton.translatesAutoresizingMaskIntoConstraints = NO;
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:customButton]
UIBarButtonItem *positiveSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
positiveSeparator.width = 8;
self.navigationItem.leftBarButtonItems = @{positiveSeparator, item, ...}
複製代碼
UIBarButtonItem 在 iOS 11 上的變化讓很多開發者都頭疼不已,咱們在 Apple Developer Forums上也能夠看到很多開發者的解決方案,大致都是去對 NavigationBar 作修改,好比修改約束,本身實現一個導航欄基類等,但對於 咱們現有的工程來講,這些方案的實現代價都太大了。
這篇文章提出的解決方案不須要對導航欄自己作任何修改,只須要在 UIBarButtonItem 上作一些處理便可,對比社區裏現有的方案,該適配方案對歷史包袱比較沉重的項目來講是值得嘗試的。