iOS11縮小介面導航欄與標籤欄異常的問題

___發表於2018-01-29

到底是在什麼情況下出現怎麼樣的異常?

幾個月之前自己嘗試封裝了一個仿QQ抽屜效果的輪子,相比較目前常見的抽屜框架優勢還是比較明顯的,用的人也慢慢的多起了來,現在基本已經穩定了。如果有興趣可以開啟?一行程式碼整合0耦合側滑抽屜一看。這個問題就是在這基礎上發現的(找遍了百度,谷歌沒有找到對應的解決方法),異常如圖所示:

iOS11縮小介面導航欄與標籤欄異常的問題
當縮小整個tabbarController的View時,導航欄上方會出現一個黑條。有人會說,哎呀我又不用你的框架,和我沒關係,不看了。?我還是建議你看一看,遇到這種網上找不到答案的問題,我們應該如何的去解決他。

如何解決問題

1、定位問題

遇到這種情況首先當然是定位問題出現在哪裡,如果問題都無法定位那麼連谷歌都不知道從哪個方面谷歌。

  • 首先我認真的檢視了我寫的程式碼,看看是否是因為我在動畫的過程中做了什麼缺心眼的操作。
  • 然後在一些關鍵的可疑地方註釋掉自己覺得可能會導致這種情況的程式碼執行看看是否會有所改善。

經過一系列排查,發現程式碼沒啥問題,並且這個異常只會在iOS11下且縮放的時候才表現出來,為了確認自己的想法,我新建了一個測試工程,單單對介面進行縮放,分別在iOS10和iOS11下進行測試(為了證明這是蘋果自己的問題),測試程式非常簡單,就是在touchBeagn時用動畫將介面縮小一點點,然後得到的測試結果如下:

iOS11縮小介面導航欄與標籤欄異常的問題
前面兩個是iOS11,第三個是iOS10,非常的明顯,兩個iOS11的程式的導航欄的頭部都出現了一段空的異常。

2、自己思考解決方案+谷歌\百度

當我們知道這個問題是大概是出現在什麼場景之後,可以先自己思考一下有沒有好的解決方式,然後再到網上找一下相關的解決方式。除非你已經有經驗或者特別自信,不然就算你知道怎麼解決了,我其實也建議你到網上搜一下別人的解決辦法,說不定更好也能讓你更清晰。

然後我就是想不到什麼好的解決方法,谷歌百度了一個下午沒有找到對應的案例,出現這樣的情況的實在太少了,最終我甚至在stackflow上搜尋 iOS11 navigation、iOS11 tabbarController,把所有的搜到問題都過了一遍都沒這個相關的問題。最終我放棄了,決定自己嘗試去解決它。

3、自己動手kill he

說到自己解決這個問題,你會怎麼做呢?說一下我的方式

3.1 掌握一些基本的知識

既然是隻在iOS11出現,我首先會想到了解iOS11對導航欄到底做了怎麼樣的修改,於是我在網上翻閱了一些優秀的關於iOS11更新的文章去理解iOS11到底對導航欄做了什麼修改?(雖然裡面沒有我們問題的答案,但是我們非常有必要的先把基礎的東西弄明白再來解決問題,這將使你少走很多彎路)具體做了哪些調整這裡就不多說了,搜一下一大把~

3.2 定位問題並開始進行嘗試

在掌握了一些基本的知識之後,我開始嘗試著想解決辦法,首先對介面層級進行分析,介面層級如下圖,淡藍色為我滑鼠選中的view:

iOS11縮小介面導航欄與標籤欄異常的問題
看到這個就發現navigationBar以及他所有的subview高度只有44,然後因為少了狀態列(電池欄)20的高度就把後面介面的背景色給漏出來了,於是我嘗試把後面的view的背景設為與導航欄同樣的顏色,異想天開的覺得反正你看到後面了,那我把後面弄成一模一樣的顏色你就看不出來了吧,發現沒鳥用一點變化都沒,然後在此基礎上我又嘗試將translucent設定為NO(想到啥就做啥多試試沒毛病),發現有點效果,但是顏色有明顯的偏差(如下圖)。而且這樣做就算顏色沒偏差我們也不能這麼做,因為修改translucent會導致介面佈局的原點發生改變,說不定就要做更多的工作了。
iOS11縮小介面導航欄與標籤欄異常的問題

3.2 仔細確認問題出現在哪!

果然邪門歪道總是靠不住的,我們還是正兒八經的去解決它吧,我們先比較一下iOS11,在正常情況下導航欄的subview的高度情況以及在縮放後導航欄的高度情況,然後再比較縮放後在iOS10下導航欄的subview的佈局情況,再決定到底要修改什麼地方。首先研究一下iOS11下導航欄的subview的層級關係:

iOS11縮小介面導航欄與標籤欄異常的問題
從UINavigationBar開始往下就是我們能看見的上面64高度(X另算)的導航欄。 然後研究一下iOS11正常的情況下導航欄的佈局:
iOS11縮小介面導航欄與標籤欄異常的問題
這層選中的橘色為_UIBarBackgroud,起始點為-20,高度為64,從前面的層級關係圖中可以瞭解到他是放在UINavigationBar上的。然後我們再來看下上面這張圖,非常明顯,只有橘色的這一層有64高,其他的包括他的父檢視都只有44高,於是我們可以明白, 在正常情況下,狀態列的20個畫素背後我們看到的就是_UIBarBackgroud這層東西,這個非常關鍵。

再來研究縮放後會異常的主介面佈局:

iOS11縮小介面導航欄與標籤欄異常的問題
於是我們發現_UIBarBackgroud這個東西起始位置是0且高度只有44了,和正常情況比較有明顯的偏差,所以導致我們無法從狀態列的位置再看到_UIBarBackgroud這個東西,所以介面顯示異常了

雖然我們可以知道估計就是這個問題影響的,但是我們更保險的得再確認一下iOS10縮放之後這層的高度是否真的是64,畢竟縮放後iOS10是正常的,它具有比較權威的解釋。

iOS11縮小介面導航欄與標籤欄異常的問題
然後我們發現,在iOS10下,_UIBarBackgroud的起始位置為-20,高度為64,這是我們想要的結果,於是我們可以肯定的說:哎,蘋果爸爸你iOS11的毛病還挺多的

3.3 定位之後,動手解決它!

到了這裡,我們的目標就非常明確了,我們只要想辦法把_UIBarBackgroud這個玩意的高度和起始位置做對應的調整就OJBK了,但是~

iOS11縮小介面導航欄與標籤欄異常的問題
不過我們們不管可達鴨眉頭皺沒皺,先搞下去再說。於是我們在導航控制器的viewDidLayoutSubviews寫下以下程式碼來修改_UIBarBackgroud的高度

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11, *)) { // xcode9新特性 可以這樣判斷,xcode9以下只能用UIDevice systemVersion 來判斷
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            // 通過遍歷獲取到_UIBarBackground圖層,修改其frame
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                CGRect frame = view.frame;
                frame.size.height = 44 + statusH;
                frame.origin.y = -statusH;
                view.frame = frame;
            }
        }
    }
}
複製程式碼

寫好啦,皆大歡喜歡喜的執行起我們的測試程式,發現還是一個卵樣。。壓根兒沒用,於是。。。回憶剛開始的翻閱stackflow的問題時幫助了我,我清晰的記得有一個問題是說自定義的導航欄高度沒法修改,裡面的解決方案提到了要用約束做修改,於是我選擇用約束去改變他,我在上面的程式碼里加上了約束的程式碼。當然我們不可能再去用masonry這種框架,只能用蘋果自帶的程式碼約束咯。

NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:44 + statusH];
[view addConstraint:heightConstraint];
複製程式碼

然後。執行。發現不僅沒鳥用,還給我報這個約束的警告:

iOS11縮小介面導航欄與標籤欄異常的問題
從控制檯列印大概看出來是說有一個NSAutoresizingMaskLayoutConstraint這個鬼約束設定了高度是44,然後我新加的高度是64就約束衝突了。。。。。excuse me??前面那個是個啥鬼東西?沒見過,趕緊去網上查一下。最後發現這個東西大概就是蘋果自動幫我們佈局時做的一些約束,這個東西可以通過UIView的translatesAutoresizingMaskIntoConstraints這個屬性設定為NO關掉,我嘗試過關掉,關掉之後佈局就完全亂了,除非什麼都自己去拉約束佈局,這樣當然不是一種好的解決方式;於是我們修了一下我們自己新增的約束的優先順序來消除警告,

// 這個有4個等級的優先順序,這裡就不做詳細介紹了,可以自行在網上搜一下
heightConstraint.priority = UILayoutPriorityDefaultHigh;
複製程式碼

然後⚠️警告就沒有了,但是還是沒鳥用。。。什麼情況?我明明按照所有的要求都做了且保證程式碼都有執行。。連個高度都改變不了嗎??於是我瘋了!在viewWillLayoutSubviews,ViewWillAppear,ViewDidAppear,ViewDidLoad方法內全部給貼上了這個程式碼。執行之後。。神奇了,TM居然正常了?? 再來一百個excuse me??表達一下我的心情

iOS11縮小介面導航欄與標籤欄異常的問題
於是我開始做苦力活,把一些不必要程式碼刪掉,一邊刪一邊執行,最後保留了NavigationController類裡買呢兩個方法內的部分程式碼,這樣就可以保證顯示正常了。程式碼如下:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
//    return;
    if (@available(iOS 11, *)) { // xcode9新特性 可以這樣判斷,xcode9以下只能用UIDevice systemVersion 來判斷
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:44 + statusH];
                heightConstraint.priority = UILayoutPriorityDefaultHigh;
                [view addConstraint:heightConstraint];
            }
        }
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (@available(iOS 11, *)) { // xcode9新特性 可以這樣判斷,xcode9以下只能用UIDevice systemVersion 來判斷
        UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
        CGFloat statusH = CGRectGetHeight(statusBar.frame);
        for (UIView *view in self.navigationBar.subviews) {
            // 通過遍歷獲取到_UIBarBackground圖層
            if ([NSStringFromClass([view class]) isEqualToString:@"_UIBarBackground"]) {
                CGRect frame = view.frame;
                frame.size.height = 44 + statusH;
                frame.origin.y = -statusH;
                view.frame = frame;
            }
        }
    }
}
複製程式碼

分別在viewWillLayoutSubviews、viewDidLayoutSubviews新增約束以及改變frame。注意:他們的方法互換或者將程式碼寫到一起都試過沒用,只有這樣才有用!為啥?不知道,試出來的。。

3.4 解決之後的結果!

iOS11縮小介面導航欄與標籤欄異常的問題

3.5 其他問題

還有我們發現在介面縮小的時候iPhoneX的tabbar的高度也會變小,於是我們使用同樣的思路調整了一下tabbar的高度,在自定義的tabbarController內,程式碼如下:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    if (!iPhoneX) return;
    CGRect frame = self.tabBar.frame;
    CGFloat h = 83 - frame.size.height;
    frame.size.height += h;
    frame.origin.y -= h;
    self.tabBar.frame = frame;
}
複製程式碼

4、使用我們的框架時如何解決這個問題?

第4點這一段才是向我提issue的同學最關心的吧,畢竟前面巴拉巴拉廢話一大堆沒啥用的。因為我們的demo與新建的測試工程有一點點差別,所以我在框架的demo工程裡面稍微整合了一下,解決方案如下:

  • 解決navigationbar上面20畫素的黑條的方法在NavigationController的viewDidAppear方法內,如果整個導航欄顏色偏黑可以嘗試將tabbarController.view.backgroundColor = [UIColor whiteColor];背景設定為白色。
  • 解決iPhoneX的tabbar高度變動的問題,在TabbarController類的viewDidAppear、viewDidLayoutSubviews這兩個方法內。

最後再提供一種解決縮放時navigationbar上面20畫素的黑條的問題,這個是我無意中測試發現的,也是最最最最簡單的。就是給navigationBar設定一個背景圖片BackgroundImage,程式碼如下:

UINavigationBar *naviBar = [UINavigationBar appearance];
[naviBar setBackgroundImage:[UIImage imageNamed:@"nav_bg"] forBarMetrics:UIBarMetricsDefault];
複製程式碼

demo的導航欄為白色,nav_bg也為一張白色的圖。只要設定了背景圖片,縮放時就不會有問題了。你可以執行我的demo兩種方式都嘗試一下。。當然最後一種是最簡單的。

總結

通過上面的案例,我們知道要解決一個問題,如果有正常和異常的兩種情況,最好就是比較兩個的差別在哪。就像平時開發中,上一個版本是正常的而新版本異常,在排查問題時也可以將兩者比較來獲取解決方式,以及遮蔽掉一些可疑的程式碼再執行看是否是這段可疑程式碼導致的異常,這些都是幫助我們定位問題的好辦法

當然大部分的開發者都是這樣做的,我在這裡說這些。。主要是因為有這個文章所說的的問題會遇到的人太少了,不說點東西大家看完之後可能會說,臥槽,看了白看,反正我這輩子可能都不會出現這種情況。。哈哈

最後如果你想問我,解決上面的問題為啥要在這個方法裡寫不能在這個方法裡?那麼我會告訴你:我瞎JB試,試出來的?......所以沒事多試試,試試又不會懷孕。。

最後一波廣告,如果你的專案需要抽屜側滑的功能,歡迎看看我的輪子

一行程式碼實現0耦合側滑抽屜

畢竟像我這樣,因為某個童鞋的一個issue而寫了一篇文章來解決的負責男人來說,你還是值得去看一下的?。ps:其實和負責卵關係沒有,因為每一個issue都是我提升的機會,所以你助我提升,我助你輕鬆,相互合作?各取所需?這是這個社會生存的真理。。

相關文章