在引入了導航控制器UINavigationController和分欄控制器UITabBarController之后,我們在設置控件的frame的時候就需要注意避開導航欄UINavigationBar 44+電源欄UIStatusBar 20的高度,和底部分欄UITabBar 44的高度。底部分欄並沒有太多需要處理的,我們只需要在計算高度的時候避開這44就可以了。而導航欄因為包含透明/半透明、第一個控件是否是UIScrollView或其子類等造成frame.origin.y的起點不同。我們現在來分析一下。
1.edgesForExtendedLayout:UIRectEdge 擴展布局的邊緣
在iOS7以后 UIViewController 開始使用全屏布局,而且是默認的屬性。通常涉及到布局,就離不開這個屬性 edgesForExtendedLayout,它是一個類型為UIExtendedEdge的屬性,指定UIViewController上的根視圖self.view邊緣要延伸的方向。由於iOS7鼓勵全屏布局,所以它的默認值是UIRectEdgeAll,四周邊緣均延伸,就是說,如果即使視圖中上有UINavigationBar,下有UITabBar,那么視圖仍會延伸覆蓋到四周的區域。
(1)UIRectEdgeAll(Defalut)
圖1
這里放置了一個frame為(0, 0, 100, 100)的view,backgroundColor設置為redColor,就是這個樣子。也就是說,此時的self.view是從屏幕頂到屏幕底的。此時我們計算控件frame的y的時候,如果想把控件在導航欄底部開始,那么y就是64。
(2)UIRectEdgeNone
因此,我們為了不讓我們的控件UIView延伸到UINavigationBar下面,我們可以將該屬性設置為UIRectEdgeNone。代碼如下:
1 [self setEdgesForExtendedLayout:UIRectEdgeNone];
ps:鑒於之前代碼OC與Swift都有,可能帶來的閱讀不習慣,以后貼代碼盡量使用OC,Swift獨有的那就沒辦法了。
圖2
很清晰的可以看出來在設置為UIRectEdgeNone之后,self.view的是從導航欄底部到底部分欄頂部的。此時我們計算控件frame的y的時候,如果想把控件在導航欄底部開始,那么y就是0。
(3)UIRectEdge
1 typedef NS_OPTIONS(NSUInteger, UIRectEdge) { 2 3 UIRectEdgeNone = 0, 4 5 UIRectEdgeTop = 1 << 0, 6 7 UIRectEdgeLeft = 1 << 1, 8 9 UIRectEdgeBottom = 1 << 2, 10 11 UIRectEdgeRight = 1 << 3, 12 13 UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight 14 15 } NS_ENUM_AVAILABLE_IOS(7_0);
很明顯,UIRectEdge是個枚舉類型。我們已經分析完了UIRectEdgeNone和UIRectEdgeAll,那么對UIRectEdgeTop和UIRectEdgeBottom也應該有所了解了。UIRectEdgeLeft/UIRectEdgeRight也不難猜測,就是對左右的擴展。目前沒有遇到過左右兩邊系統控件可能覆蓋掉自定義控件的情況,但遇到了我想大家也應該心里有數了吧。
2.translucent:Bool 半透明的
這個是self.navigationController.navigationBar的屬性,設置導航條UINavigationBar是否半透明。默認是YES,也就是半透明,看起來比較高大上,與圖1效果相同。
當設置為不透明NO的時候,就涉及到下面第三條屬性了,不過在第三條默認的情況下,則被下壓,與圖2效果相同。
1 [self.navigationController.navigationBar setTranslucent:NO];
圖3
圖中空出的部分,即為導航條UINavigationBar。此時計算frame應從導航欄下部為0開始計算,或者說控件會被下壓64。
不過我們一般不會使用到這一條,因為半透明效果比較好看啊~
3.extendedLayoutIncludesOpaqueBars:Bool 不透明的條下是否可以擴展
很明顯,這條需要與上一條translucent搭配使用的,默認為NO,也就是不可以擴展。當translucent設置為NO,即導航欄UINavigationBar不透明的時候,默認不能擴展。若我們設置為YES,則會出現這種情況:
圖4
我並沒有改變紅色view的frame,而是被不透明的導航條蓋住看不到了。很明顯此時計算frame又是從屏幕最上方作為0開始計算了。
為了計算究竟是在電源條下方還是屏幕最上方作為y的0點,改變了紅色view的height為64和65重復試驗,結果如下
圖5:64開始
圖6:65開始
結論是從屏幕上方作為y的0開始計算。
不過第2條都很少用,第3條使用的幾率...
4.automaticallyAdjustsScrollViewInsets:Bool 自動校准滾動視圖的嵌入視圖
這個其實就很簡單了,也是我們最常使用的一個吧。automaticallyAdjustsScrollViewInsets是一個bool類型,默認為YES,也就是會自動校准滾動視圖的嵌入視圖。不過這個只針對於UIScrollView及其子視圖,所以我們是用UITextView做一下測試。
創建了一個frame為(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)占據整個屏幕的UITextView,並放置了足夠多的文字。在默認automaticallyAdjustsScrollViewInsets為YES的前提下,我們測試下可能出現的結果。
圖7
似乎什么毛病都沒有啊很正常啊。但是不要忘記我設置的frame的y是0啊。那大家可能就好奇了,難道UIScrollView的滾動視圖的frame是從導航欄下邊界算起的?
不忙,我們設置automaticallyAdjustsScrollViewInsets為NO再試試。
1 [self setAutomaticallyAdjustsScrollViewInsets:NO];
圖8
這個是一啟動就出現的界面,我並沒有向上滑動它的內容區域,也就是說,似乎此時的frame又是從屏幕下方開始的了。
不要慌,我們改一下設置試試看。
我將UITextView的frame設置為(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height),也就是y改成了64;然后改變了它的背景顏色為紅色redColor;最后將[self setAutomaticallyAdjustsScrollViewInsets:NO];這行代碼注掉,讓它默認為YES。
看一下效果。
圖9
注意:導航欄是半透明的,所以不存在被壓到紅色看不到的問題,也就是說,UITextView的frame的y的64的位置,是導航欄下方;那么0自然就是在屏幕下方開始計算了。
很明顯,automaticallyAdjustsScrollViewInsets改變的並不是UIScrollView的frame,而是它的內容區域contentView的可滾動區域,也就是scrollIndicatorInsets。這是我們能滾到的最大范圍。相當於scrollIndicatorInsets這個屬性UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)的值從UIEdgeInsetsMake(0, 0, 0, 0)變為了UIEdgeInsetsMake(64, 0, 0, 0)。這就是automaticallyAdjustsScrollViewInsets這個屬性的作用。
另外再說一點,這個automaticallyAdjustsScrollViewInsets的屬性,只對加在self.view上的第一個子視圖起作用,所以我們可以在將UIScrollView加載到self.view上之前先加載一個其他控件,這樣也能達到同樣的效果。
1 [self.view addSubView:[[UIView alloc] init]];
涉及導航欄下壓的情況大致就有這些,如遇到其他問題,會繼續補充。