在《iOS 7 UI Transition Guide》中有在《iOS 7 UI Transition Guide》的Bar and Bar Buttons一節(jié)中有這么一段話
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.
翻譯過來(lái):
在iOS7中,狀態(tài)欄是完全透明的,而其他bar,即navigation bars, tab bars, toolbars, search bars和scope bars都是半透明的。開發(fā)者需要保證頁(yè)面內(nèi)容能覆蓋到這些bar的后面。
事實(shí)上,iOS7中的狀態(tài)欄不僅變完全透明了,而且完全不占空間。
有碼有真相 —— 新建一個(gè)UIViewController,再viewDidLoad里面輸入以下代碼,作為rootViewController啟動(dòng)應(yīng)用:
- (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];
}
應(yīng)用效果:

可以看到的是label和status bar悲催地重疊了。
我們?cè)偬滓粋€(gè)UINavigationController,可以看到更悲催的事情:

label活生生地被navigationBar蓋住了。
可以說(shuō),蘋果這次在iOS7上的redesign對(duì)開發(fā)者來(lái)說(shuō)是慘絕人寰的。
不過蘋果還是有節(jié)操的,在iOS7上運(yùn)行iOS7 SDK以下開發(fā)的應(yīng)用時(shí),保留了原先的頁(yè)面結(jié)構(gòu)布局,并且做了不少向下兼容策略。
而且,iOS7 SDK提供了一系列接口和策略方案,下文將會(huì)一一介紹并順帶剖析一下iOS7上的頁(yè)面結(jié)構(gòu)框架。
Realtime Debug Protal
首先介紹一個(gè)小工具,可以方便我們進(jìn)行學(xué)習(xí)。它的小名叫RDP,是一個(gè)類似Web Inspector的工具,把這個(gè)工具引入我們的項(xiàng)目工程,并做一些簡(jiǎn)單的配置,然后運(yùn)行真機(jī)或者模擬器。應(yīng)用啟動(dòng)后,在瀏覽器輸入手機(jī)的IP地址,就可以看到UIView的樹狀結(jié)構(gòu)和Log信息,還可以在瀏覽器中對(duì)View進(jìn)行移動(dòng),隱藏,選中高亮等操作。

狀態(tài)欄
在iOS7中,狀態(tài)欄是透明的,就是說(shuō),狀態(tài)欄只有文字沒有背景。
而變透明之后就很容易和后面的內(nèi)容混淆,雖說(shuō)一般應(yīng)用不會(huì)把內(nèi)容和狀態(tài)欄疊合在一起,但是至少,現(xiàn)在的情況是,默認(rèn)是會(huì)疊合的,開發(fā)需要從20px像素以下開始布局頁(yè)面元素才能避免。
蘋果為了讓深色淺色背景均能讓狀態(tài)欄內(nèi)容清晰顯示,提供兩種狀態(tài)欄樣式:
UIStatusBarStyleDefault = 0 黑色文字,淺色背景時(shí)使用
UIStatusBarStyleLightContent = 1 白色文字,深色背景時(shí)使用
而以下兩個(gè)舊狀態(tài)欄樣式將被廢棄:
UIStatusBarStyleBlackTranslucent = 1
UIStatusBarStyleLightContent = 2
還有,iOS7中我們通過ViewController重載方法返回枚舉值的方法來(lái)控制狀態(tài)欄的隱藏和樣式。
首先,需要在Info.plist配置文件中,增加鍵:UIViewControllerBasedStatusBarAppearance,并設(shè)置為YES;
然后,在UIViewController子類中實(shí)現(xiàn)以下兩個(gè)方法:
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (BOOL)prefersStatusBarHidden
{
return NO;
}
最后,在需要刷新狀態(tài)欄樣式的時(shí)候,調(diào)用[self setNeedsStatusBarAppearanceUpdate]方法即可刷新,若果需要以動(dòng)畫形式切換狀態(tài)欄樣式,則用以下方式調(diào)用即可:
[UIView animateWithDuration:0. animations:^{
[self setNeedsStatusBarAppearanceUpdate];
}];
導(dǎo)航欄
在iOS7,由于狀態(tài)欄背景透明,那么,導(dǎo)航欄背景就可能要兼職充當(dāng)狀態(tài)欄背景了。
iOS7默認(rèn)導(dǎo)航欄樣式就是這么做的,見下圖:

雖然用戶看來(lái),iOS7默認(rèn)樣式的狀態(tài)欄和導(dǎo)航欄時(shí)連在一起的,但是實(shí)際上導(dǎo)航欄的位置和大小是和之前系統(tǒng)版本一樣的,依然是貼在狀態(tài)欄下面, 依然是高44px;之所以用戶看來(lái)它們是連在一起,這是因?yàn)閁INavigationBar里面的_UINavigationBarBackground 定位在y方向-20px的位置,然后高度增加到64px,這樣就可以同時(shí)充當(dāng)了兩者的背景。
關(guān)于這些定位,蘋果做了很多工作,后面也會(huì)談到不少。不關(guān)心的同學(xué)可以略過,其實(shí)這些細(xì)節(jié),個(gè)人覺得,即使對(duì)于開發(fā)者來(lái)說(shuō),也不是必需知道的,我們只需要知道怎么調(diào)用相關(guān)API就足夠了。
實(shí)際情況下,我們會(huì)自定義導(dǎo)航欄背景,過去,我們也許會(huì)使用如下代碼把一張高44像素(retina/88像素)的圖片來(lái)平鋪?zhàn)鳛閷?dǎo)航欄背景。
[navCtrl.navigationBar setBackgroundImage:[UIImage imageNamed:@"nav_background"] forBarMetrics:UIBarMetricsDefault];
啟動(dòng)應(yīng)用,出現(xiàn)了意想不到的效果和久違的界面 —— 黑底白字的狀態(tài)欄,不再被navigationBar蓋住的label。

這里兩個(gè)點(diǎn)需要解釋一下:
- 若我們使用自定義圖片作為導(dǎo)航欄的背景,那么UIViewController的view(下面稱為視圖)就不會(huì)延伸到navigationBar的頂部,而是從它的底部開始——正如往常一樣。
- 若我們使用一張高44像素(retina/88像素)的圖片作為導(dǎo)航欄背景,那么狀態(tài)欄就會(huì)保持黑色,圖片只會(huì)在導(dǎo)航欄區(qū)域平鋪。
另外,iOS7 SDK中新增了一個(gè)設(shè)置背景圖片的方法(setBackgroundImage:forBarPosition:barMetrics:),比原有的方法多了一個(gè)UIBarPosition枚舉參數(shù),用于設(shè)置背景圖片拉伸的策略。
針對(duì)不同的拉伸設(shè)置和背景圖片尺寸,在《iOS 7 UI Transition Guide》的Bar and Bar Buttons一節(jié)中
中有詳細(xì)說(shuō)明:

頁(yè)面布局
在 《iOS 7 UI Transition Guide》的Layout and Appearance 一節(jié)中也提到 —— 在iOS7中,view controllers使用全屏布局 (In iOS 7, view controllers use full-screen layout)。
通過上面的討論我們也知道,除非導(dǎo)航欄設(shè)置了自定義的背景圖片,否則每個(gè)視圖都會(huì)延伸到屏幕一樣大小的。
所以,像上面第二張圖片中出現(xiàn)導(dǎo)航欄遮蓋label的情況也是正常的現(xiàn)象。
如果我們要讓label從導(dǎo)航欄以下位置顯示,可以通過修改UIViewController的edgesForExtendedLayout這個(gè)屬性來(lái)實(shí)現(xiàn)。
edgesForExtendedLayout是一個(gè)類型為UIExtendedEdge的屬性,指定邊緣要延伸的方向。
因?yàn)閕OS7鼓勵(lì)全屏布局,它的默認(rèn)值很自然地是UIRectEdgeAll,四周邊緣均延伸,就是說(shuō),如果即使視圖中上有navigationBar,下有tabBar,那么視圖仍會(huì)延伸覆蓋到四周的區(qū)域。
如果把視圖做如下設(shè)置,那么視圖就不會(huì)延伸到這些bar的后面了,于是label又出來(lái)了。
self.edgesForExtendedLayout = UIExtendedEdgeNone;

也許,這時(shí)候你會(huì)想,那為什么不把UIExtendedEdgeNone作為默認(rèn)態(tài)呢?
iOS7以后鼓勵(lì)全屏,它希望用戶在使用可滾動(dòng)視圖的時(shí)候可以透過半透明的bar還可以看到一些模模糊糊的內(nèi)容。
為了保持設(shè)計(jì)的優(yōu)雅,同時(shí)避免給開發(fā)者太多的困擾,iOS7在Conttoller中新增了這個(gè)屬性:automaticallyAdjustsScrollViewInsets,當(dāng)設(shè)置為YES時(shí)(默認(rèn)YES),如果視圖里面存在唯一一個(gè)UIScrollView或其子類View,那么它會(huì)自動(dòng)設(shè)置相應(yīng)的內(nèi)邊距,這樣可以讓scroll占據(jù)整個(gè)視圖,又不會(huì)讓導(dǎo)航欄遮蓋,如以下例子:

要注意的是,這個(gè)例子中我們沒有設(shè)置edgesForExtendedLayout,即視圖是延伸至全屏的。
我們可以從UIView樹狀圖看到,tableview的bounds值中有64像素的偏移值,它作為一個(gè)內(nèi)邊距來(lái)保持內(nèi)容顯示在導(dǎo)航欄以下,而滾動(dòng)時(shí)仍可以透過半透明的導(dǎo)航欄看到模糊的內(nèi)容。
最后一個(gè)介紹的新屬性是extendedLayoutIncludesOpaqueBars,這個(gè)屬性指定了當(dāng)Bar使用了不透明圖片時(shí),視圖是否延伸至Bar所在區(qū)域,默認(rèn)值時(shí)NO。
所以我們?nèi)绻远x了導(dǎo)航欄的背景圖片,那么視圖會(huì)從導(dǎo)航欄以下開始,不會(huì)延伸到導(dǎo)航欄區(qū)域。
如果把這個(gè)屬性設(shè)置為YES,那么視圖將會(huì)延伸至導(dǎo)航欄區(qū)域,即使我們把導(dǎo)航欄設(shè)置成了自定義背景,如下圖:

視圖延伸之后,label又被導(dǎo)航欄覆蓋住了,正如我們意料。