iOS之頁面布局
原文請點擊
在《iOS 7 UI Transition Guide》中有在《iOS 7 UI Transition Guide》的Bar and Bar Buttons一節中有這么一段話
In iOS 7, the status bar is transparent, and other bars—that is, navigation bars, tab bars, toolbars, search bars, and scope bars—are translucent. As a general rule, you want to make sure that content fills the area behind the bars in your app.
翻譯過來:
在iOS7中,狀態欄是完全透明的,而其他bar,即navigation bars, tab bars, toolbars, search bars和scope bars都是半透明的。開發者需要保證頁面內容能覆蓋到這些bar的后面。
事實上,iOS7中的狀態欄不僅變完全透明了,而且完全不占空間。
有碼有真相 —— 新建一個UIViewController,再viewDidLoad里面輸入以下代碼,作為rootViewController啟動應用:
- (void)viewDidLoad
{ [super viewDidLoad];self.view.backgroundColor = [UIColor whiteColor];UILabel *label = [[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 20)]autorelease];label.text = @"I am a label";[self.view addSubview:label];
}
應用效果:
Paste_Image.png
可以看到的是label和status bar悲催地重疊了。
我們再套一個UINavigationController,可以看到更悲催的事情:
Paste_Image.png
label活生生地被navigationBar蓋住了。
可以說,蘋果這次在iOS7上的redesign對開發者來說是慘絕人寰的。
不過蘋果還是有節操的,在iOS7上運行iOS7 SDK以下開發的應用時,保留了原先的頁面結構布局,并且做了不少向下兼容策略。
而且,iOS7 SDK提供了一系列接口和策略方案,下文將會一一介紹并順帶剖析一下iOS7上的頁面結構框架。
Realtime Debug Protal
首先介紹一個小工具,可以方便我們進行學習。它的小名叫RDP,是一個類似Web Inspector的工具,把這個工具引入我們的項目工程,并做一些簡單的配置,然后運行真機或者模擬器。應用啟動后,在瀏覽器輸入手機的IP地址,就可以看到UIView的樹狀結構和Log信息,還可以在瀏覽器中對View進行移動,隱藏,選中高亮等操作。
Paste_Image.png
狀態欄
在iOS7中,狀態欄是透明的,就是說,狀態欄只有文字沒有背景。
而變透明之后就很容易和后面的內容混淆,雖說一般應用不會把內容和狀態欄疊合在一起,但是至少,現在的情況是,默認是會疊合的,開發需要從20px像素以下開始布局頁面元素才能避免。
蘋果為了讓深色淺色背景均能讓狀態欄內容清晰顯示,提供兩種狀態欄樣式:
UIStatusBarStyleDefault = 0 黑色文字,淺色背景時使用
UIStatusBarStyleLightContent = 1 白色文字,深色背景時使用
而以下兩個舊狀態欄樣式將被廢棄:
UIStatusBarStyleBlackTranslucent = 1
UIStatusBarStyleLightContent = 2
還有,iOS7中我們通過ViewController重載方法返回枚舉值的方法來控制狀態欄的隱藏和樣式。
首先,需要在Info.plist配置文件中,增加鍵:UIViewControllerBasedStatusBarAppearance,并設置為YES;
然后,在UIViewController子類中實現以下兩個方法:
- (UIStatusBarStyle)preferredStatusBarStyle
{ return UIStatusBarStyleLightContent;
}- (BOOL)prefersStatusBarHidden
{return NO;
}
最后,在需要刷新狀態欄樣式的時候,調用[self setNeedsStatusBarAppearanceUpdate]方法即可刷新,若果需要以動畫形式切換狀態欄樣式,則用以下方式調用即可:
[UIView animateWithDuration:0. animations:^{[self setNeedsStatusBarAppearanceUpdate];
}];
導航欄
在iOS7,由于狀態欄背景透明,那么,導航欄背景就可能要兼職充當狀態欄背景了。
iOS7默認導航欄樣式就是這么做的,見下圖:
Paste_Image.png
雖然用戶看來,iOS7默認樣式的狀態欄和導航欄時連在一起的,但是實際上導航欄的位置和大小是和之前系統版本一樣的,依然是貼在狀態欄下面, 依然是高44px;之所以用戶看來它們是連在一起,這是因為UINavigationBar里面的_UINavigationBarBackground 定位在y方向-20px的位置,然后高度增加到64px,這樣就可以同時充當了兩者的背景。
關于這些定位,蘋果做了很多工作,后面也會談到不少。不關心的同學可以略過,其實這些細節,個人覺得,即使對于開發者來說,也不是必需知道的,我們只需要知道怎么調用相關API就足夠了。
實際情況下,我們會自定義導航欄背景,過去,我們也許會使用如下代碼把一張高44像素(retina/88像素)的圖片來平鋪作為導航欄背景。
[navCtrl.navigationBar setBackgroundImage:[UIImage imageNamed:@"nav_background"] forBarMetrics:UIBarMetricsDefault];
啟動應用,出現了意想不到的效果和久違的界面 —— 黑底白字的狀態欄,不再被navigationBar蓋住的label。
Paste_Image.png
這里兩個點需要解釋一下:
- 若我們使用自定義圖片作為導航欄的背景,那么UIViewController的view(下面稱為視圖)就不會延伸到navigationBar的頂部,而是從它的底部開始——正如往常一樣。
- 若我們使用一張高44像素(retina/88像素)的圖片作為導航欄背景,那么狀態欄就會保持黑色,圖片只會在導航欄區域平鋪。
另外,iOS7 SDK中新增了一個設置背景圖片的方法(setBackgroundImage:forBarPosition:barMetrics:),比原有的方法多了一個UIBarPosition枚舉參數,用于設置背景圖片拉伸的策略。
針對不同的拉伸設置和背景圖片尺寸,在《iOS 7 UI Transition Guide》的Bar and Bar Buttons一節中
中有詳細說明:
Paste_Image.png
?
頁面布局
在 《iOS 7 UI Transition Guide》的Layout and Appearance?一節中也提到 —— 在iOS7中,view controllers使用全屏布局 (In iOS 7, view controllers use full-screen layout)。
通過上面的討論我們也知道,除非導航欄設置了自定義的背景圖片,否則每個視圖都會延伸到屏幕一樣大小的。
所以,像上面第二張圖片中出現導航欄遮蓋label的情況也是正常的現象。
如果我們要讓label從導航欄以下位置顯示,可以通過修改UIViewController的edgesForExtendedLayout這個屬性來實現。
edgesForExtendedLayout是一個類型為UIExtendedEdge的屬性,指定邊緣要延伸的方向。
因為iOS7鼓勵全屏布局,它的默認值很自然地是UIRectEdgeAll,四周邊緣均延伸,就是說,如果即使視圖中上有navigationBar,下有tabBar,那么視圖仍會延伸覆蓋到四周的區域。
如果把視圖做如下設置,那么視圖就不會延伸到這些bar的后面了,于是label又出來了。
self.edgesForExtendedLayout = UIExtendedEdgeNone;
Paste_Image.png
也許,這時候你會想,那為什么不把UIExtendedEdgeNone作為默認態呢?
iOS7以后鼓勵全屏,它希望用戶在使用可滾動視圖的時候可以透過半透明的bar還可以看到一些模模糊糊的內容。
為了保持設計的優雅,同時避免給開發者太多的困擾,iOS7在Conttoller中新增了這個屬性:automaticallyAdjustsScrollViewInsets,當設置為YES時(默認YES),如果視圖里面存在唯一一個UIScrollView或其子類View,那么它會自動設置相應的內邊距,這樣可以讓scroll占據整個視圖,又不會讓導航欄遮蓋,如以下例子:
Paste_Image.png
要注意的是,這個例子中我們沒有設置edgesForExtendedLayout,即視圖是延伸至全屏的。
我們可以從UIView樹狀圖看到,tableview的bounds值中有64像素的偏移值,它作為一個內邊距來保持內容顯示在導航欄以下,而滾動時仍可以透過半透明的導航欄看到模糊的內容。
最后一個介紹的新屬性是extendedLayoutIncludesOpaqueBars,這個屬性指定了當Bar使用了不透明圖片時,視圖是否延伸至Bar所在區域,默認值時NO。
所以我們如果自定義了導航欄的背景圖片,那么視圖會從導航欄以下開始,不會延伸到導航欄區域。
如果把這個屬性設置為YES,那么視圖將會延伸至導航欄區域,即使我們把導航欄設置成了自定義背景,如下圖:
Paste_Image.png
視圖延伸之后,label又被導航欄覆蓋住了,正如我們意料。