[貝聊科技]iOS 11 怎樣爲導航條上的 UIBarButtonItem 設置間距

做者:陳浩  貝聊科技移動開發部  iOS 工程師ios

本文已發表在我的博客git

之前咱們經常使用 fixedSpace 的方式爲 UINavigationBar 上的 UIBarButtonItem 設置間距。然而在 iOS 11 下 UIBarButtonItem width 屬性不但失效了,UIBarButtonItem 也開始使用 auto layout 佈局,對此咱們須要設置 UIBarButtonItem 子 view 的約束。除此以外,蘋果還修改了 UINavigationBar 的實現。直到 iOS 10 UINavigationBar 都是採用手動佈局,全部的子 view 都是直接加在 UINavigationBar 上。可是,從 iOS 11 開始, UINavigationBar 使用了 auto layout 來佈局它的內容子 view,而且子 view 加在了 _UINavigationBarContentView 上。github

iOS 11以前的作法:bash

UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace 
                                                                                target:nil 
                                                                                action:nil];

negativeSpacer.width = -8;

[self.navigationItem setLeftBarButtonItems:@[negativeSpacer, button] animated:NO];
複製代碼

先來看看 iOS 11 下 UINavigationBar 的視圖層級:ide

UINavigationBar
      | _UIBarBackground
      |    | UIImageView
      |    | UIImageView
      | _UINavigationBarLargeTitleView
      |    | UILabel
      | _UINavigationBarContentView 
      |    | _UIButtonBarStackView
      |    |    | _UIButtonBarButton
      |    |    |    | _UIModernBarButton
      |    |    |    |    | UIButtonLabel
      | _UINavigationBarContentView
      |    | _UIButtonBarStackView
      |    |    | _UITAMICAdaptorView // customView
      |    |    |    | UIBarButtonItem
      |    |    |    |    | UIImageView
      |    |    |    |    | UIButtonLabel
複製代碼

經過 View Debug 工具可知,原來是 stackView 限制了 customView 的寬度以及引發了偏移:工具

contentView |<-fullWidth----------->|
stackView     |<-stackViewWidth->|
customView    |<-reducedWidth--->|
複製代碼

在這次深挖以前,貝聊客戶端的開發小哥們因爲項目工期緊以及適配 iOS 11 工做量大,暫時是經過設置 UIButtonsetContentEdgeInsets: 來實現的,這在當時看來是以最小的改動完成了適配,直到 iOS 11.2 這個版本的推出,咱們發現當側滑返回時,導航條的返回按鈕會被切掉一點角。(這個方法還有個小缺點是點擊區域過小了)佈局

img1

碰巧的是,個人 leader 剛好發現了釘釘也有相似的問題。ui

iOS 11 雖然已經推出好幾個月了,這個問題可能還在困擾着部分同行,接下來就講講貝聊是如何解決這個問題的。atom

因爲你們知道 fixed space 失效是系統換成了 auto layout 來實現,因此網上的大部分文章也都是修改 constraint。遺憾的是,我谷歌到挺多使用這種方式去修改要獲取到 UINavigationBar 的私有子 view,譬如 contentViewbarStackView,再爲私有子 view 添加 leading 和 trailing 的約束等。spa

我並無嘗試這種方法的可行性,由於始終以爲獲取私有子 view 的作法比較脆弱,蘋果一旦更換實現,程序的健壯性很差保障。但能夠肯定的是,解決這個問題的思路大體是修改約束,設法擺脫 stackView 的限制。

在 auto layout 中,約束是使用 alignment rectangle 來肯定視圖的大小和位置。先看看 alignment rectangle 的做用是怎樣,下圖摘自《iOS Auto Layout Demystified》:

img2

img3

書中對此的說明是,假如設計師給了你張帶角標的氣泡圖片,程序只指望對氣泡進行居中,而圖片的 frame 卻包含了角標部分,這時能夠 override alignmentRectForFrame:frameForAlignmentRectUIView 也給出了相對簡便的屬性 alignmentRectInsets,須要注意的是,一旦設置了 alignmentRectInsets,view 的 frame 就會根據 alignment rectangle 和 alignmentRectInsets 計算出來。

有了以上的概念後,貝聊的修復方法是子類化一個 UIBarButtonItem 的 customView:

@interface BLNavigationItemCustomView: UIView

@property (nonatomic) UIEdgeInsets alignmentRectInsetsOverride;

@end

@implementation BLNavigationItemCustomView

- (UIEdgeInsets)alignmentRectInsets {
    if (UIEdgeInsetsEqualToEdgeInsets(self.alignmentRectInsetsOverride, UIEdgeInsetsZero)) {
        return super.alignmentRectInsets;
    } else {
        return self.alignmentRectInsetsOverride;
    }
    
}

@end
複製代碼

再就是建立 customView 時針對 iOS 11 作特殊處理,以返回按鈕爲例:

if (@available(iOS 11.0, *)) {
    button.alignmentRectInsetsOverride = UIEdgeInsetsMake(0, offset, 0, -(offset));
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [button.widthAnchor constraintEqualToConstant:buttonWidth].active = YES;
    [button.heightAnchor constraintEqualToConstant:44].active = YES;
                    
}
複製代碼

之因此設置 widthAnchor、heightAnchor 是前文提到的須要對 UIBarButtonItem 子 view 設置約束,我在實現時就遇到了怎麼修改 frame 都沒法撐大 customView 的狀況,後來發現是沒設置 widthAnchor。咱們接着用 View Debug 來看看實現的效果:

img4

這兒有個問題就是 customView 有小部分超出了 stackView 的 bounds,致使了超出部分沒法接收點擊。有趣的是,使用 iOS 11 以前 fixed space 添加間距的作法能夠減小 stackView 的 margin。

UIBarButtonItem *spacer = [UIBarButtonItem bl_barButtonItemSpacerWithWidth:-(offset)];
self.navigationItem.leftBarButtonItems = @[spacer, barButtonItem];
複製代碼

結合上 fixed space 和 alignmentRectInsets,customView 將再也不超出它的父視圖:

img5

總之,咱們需繼承複寫 alignmentRectInsetsBLNavigationItemCustomView,而後繼續保持以前版本 fixed space 的處理,只針對 iOS 11 爲 customView 新增約束,就可以使間距問題在新舊系統得以解決。

總結

不客氣的說,iOS 11 真的是一個挺難適配的版本,期間我都差點放棄對導航條間隔的適配了,好在最後仍是順利解決了。若是你有更好的方式解決,歡迎賜教。

相關文章
相關標籤/搜索