Redesign Your App for iOS 7 之 頁面佈局

前言

iOS7是目前iOS史上最顛覆的一次改版。
它的出現令人興奮,因爲它將會帶我們進入一個全新的時代;
它的到來也讓我們憂心,因爲它顛覆了一切,包括我們過去做過的很多努力。
但是,相信大家樂意爲這個全新時代做更多的努力,因爲,它值得期待。
 
最近打算寫一系列針對iOS7新特性的文章,今天就從最基本和簡單的頁面佈局開始吧。
 
 

從頭說起

當我看見iOS7那個半透明設計的navigationBar,已經有種不詳的預感。
因爲要透過navigationBar看到底下滾動的內容,那麼底下的內容必須從頂部開始佈局,並且需要設定相應的內邊距以讓初始內容顯示在合適的位置上。
要對應用適配這種兇殘設計,其工作量絕比適配iPhone5屏幕還要多許多。
 
我馬上翻了一下iOS7相關的文檔,在《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啓動應用:
01 - (void)viewDidLoad
02 {
03     [super viewDidLoad];
04      
05     self.view.backgroundColor = [UIColor whiteColor];
06      
07     UILabel *label = [[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 20)]autorelease];
08     label.text = @"I am a label";
09     [self.view addSubview:label];
10 }
 
應用效果:
可以看到的是label和status bar悲催地重疊了。
我們再套一個UINavigationController,可以看到更悲催的事情:
 
label活生生地被navigationBar蓋住了。
 
可以說,蘋果這次在iOS7上的redesign對開發者來說是慘絕人寰的。
不過蘋果還是有節操的,在iOS7上運行iOS7 SDK以下開發的應用時,保留了原先的頁面結構佈局,並且做了不少向下兼容策略。
而且,iOS7 SDK提供了一系列接口和策略方案,下文將會一一介紹並順帶剖析一下iOS7上的頁面結構框架。
 
 

Realtime Debug Protal

在討論如何應對兇殘的iOS7之前,我首先介紹一個自己之前搗騰的小工具,可以方便我們進行學習。
它的小名叫RDP,是一個類似Web Inspector的工具,把這個工具引入我們的項目工程,並做一些簡單的配置,然後運行真機或者模擬器。
應用啓動後,在瀏覽器輸入手機的IP地址,就可以看到UIView的樹狀結構和Log信息,還可以在瀏覽器中對View進行移動,隱藏,選中高亮等操作。
 
 
這是iOS7上面的頁面結構,對比一下iOS6和iOS5的,就這樣已經可以看出區別來了。
 
 
 
不過今天的重點不是橫向對比,而是在iOS7上的縱向對比,請看下文。
 
 

狀態欄

在iOS7中,狀態欄是透明的,就是說,狀態欄只有文字沒有背景。
這個改動讓我頗爲意外,因爲一直印象中蘋果很care狀態欄的,之前也曾聽說過有應用因爲遮擋了狀態欄而被Appstore拒絕。
而且蘋果之前狀態欄提供的三種樣式都是以深色底白色字呈現,保證了狀態欄的內容清晰易讀。
 
而變透明之後就很容易和後面的內容混淆,雖說一般應用不會把內容和狀態欄疊合在一起,但是至少,現在的情況是,默認是會疊合的,開發需要從20px像素以下開始佈局頁面元素才能避免。
 
蘋果爲了讓深色淺色背景均能讓狀態欄內容清晰顯示,提供兩種狀態欄樣式:
 
UIStatusBarStyleDefault = 0 黑色文字,淺色背景時使用
UIStatusBarStyleLightContent = 1 白色文字,深色背景時使用
 
而以下兩個舊狀態欄樣式將被廢棄:
UIStatusBarStyleBlackTranslucent = 1
UIStatusBarStyleLightContent = 2
 
還有,iOS7中我們通過ViewController重載方法返回枚舉值的方法來控制狀態欄的隱藏和樣式。
首先,需要在Info.plist配置文件中,增加鍵:UIViewControllerBasedStatusBarAppearance,並設置爲YES;
然後,在UIViewController子類中實現以下兩個方法:
1 - (UIStatusBarStyle)preferredStatusBarStyle
2 {
3     return UIStatusBarStyleLightContent;
4 }
5  
6 - (BOOL)prefersStatusBarHidden
7 {
8     return NO;
9 }
 
最後,在需要刷新狀態欄樣式的時候,調用[self setNeedsStatusBarAppearanceUpdate]方法即可刷新,若果需要以動畫形式切換狀態欄樣式,則用以下方式調用即可:
 
1 [UIView animateWithDuration:0. animations:^{
2     [self setNeedsStatusBarAppearanceUpdate];
3 }];
 
 

導航欄

在iOS7,由於狀態欄背景透明,那麼,導航欄背景就可能要兼職充當狀態欄背景了。
iOS7默認導航欄樣式就是這麼做的,見下圖:
 
 
雖然用戶看來,iOS7默認樣式的狀態欄和導航欄時連在一起的,但是實際上導航欄的位置和大小是和之前系統版本一樣的,依然是貼在狀態欄下面,依然是高44px;之所以用戶看來它們是連在一起,這是因爲UINavigationBar裏面的_UINavigationBarBackground定位在y方向-20px的位置,然後高度增加到64px,這樣就可以同時充當了兩者的背景。
 
關於這些定位,蘋果做了很多工作,後面也會談到不少。不關心的同學可以略過,其實這些細節,個人覺得,即使對於開發者來說,也不是必需知道的,我們只需要知道怎麼調用相關API就足夠了。
實際情況下,我們會自定義導航欄背景,過去,我們也許會使用如下代碼把一張高44像素(retina/88像素)的圖片來平鋪作爲導航欄背景。
 
1 [navCtrl.navigationBar setBackgroundImage:[UIImage imageNamed:@"nav_background"] forBarMetrics:UIBarMetricsDefault];
 
啓動應用,出現了意想不到的效果和久違的界面 —— 黑底白字的狀態欄,不再被navigationBar蓋住的label。
 
 
這裏兩個點需要解釋一下:
 
  1. 若我們使用自定義圖片作爲導航欄的背景,那麼UIViewController的view(下面稱爲視圖)就不會延伸到navigationBar的頂部,而是從它的底部開始——正如往常一樣。
  2. 若我們使用一張高44像素(retina/88像素)的圖片作爲導航欄背景,那麼狀態欄就會保持黑色,圖片只會在導航欄區域平鋪。
 
另外,iOS7 SDK中新增了一個設置背景圖片的方法(setBackgroundImage:forBarPosition:barMetrics:),比原有的方法多了一個UIBarPosition枚舉參數,用於設置背景圖片拉伸的策略。
針對不同的拉伸設置和背景圖片尺寸,在《iOS 7 UI Transition Guide》的Bar and Bar Buttons一節中
中有詳細說明:
 
 
PS:原文中的"resize"翻譯爲“調整”,表述比較含糊,根據實際操作結果看,水平方向調整一般是平鋪,垂直方向調整一般是局部拉伸。
 
 

頁面佈局

在 《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又出來了。
1 self.edgesForExtendedLayout = UIExtendedEdgeNone;
 
 
 
也許,這時候你會想,那爲什麼不把UIExtendedEdgeNone作爲默認態呢?
iOS7鼓勵全屏,它希望用戶在使用可滾動視圖的時候可以透過半透明的bar還可以看到一些模模糊糊的內容。
 
爲了保持設計的優雅,同時避免給開發者太多的困擾,iOS7在Conttoller中新增了這個屬性:automaticallyAdjustsScrollViewInsets,當設置爲YES時(默認YES),如果視圖裏面存在唯一一個UIScrollView或其子類View,那麼它會自動設置相應的內邊距,這樣可以讓scroll佔據整個視圖,又不會讓導航欄遮蓋,如以下例子:
 
 
 
要注意的是,這個例子中我們沒有設置edgesForExtendedLayout,即視圖是延伸至全屏的。
我們可以從UIView樹狀圖看到,tableview的bounds值中有64像素的偏移值,它作爲一個內邊距來保持內容顯示在導航欄以下,而滾動時仍可以透過半透明的導航欄看到模糊的內容。
 
最後一個介紹的新屬性是extendedLayoutIncludesOpaqueBars,這個屬性指定了當Bar使用了不透明圖片時,視圖是否延伸至Bar所在區域,默認值時NO。
所以我們如果自定義了導航欄的背景圖片,那麼視圖會從導航欄以下開始,不會延伸到導航欄區域。
如果把這個屬性設置爲YES,那麼視圖將會延伸至導航欄區域,即使我們把導航欄設置成了自定義背景,如下圖:
 
 
 
視圖延伸之後,label又被導航欄覆蓋住了,正如我們意料。
 
好了,這次就介紹到這裏了。
下次我們討論一下如何寫兼容iOS7和iOS5、6的頁面佈局。
 
上文如有什麼錯漏,歡迎指正交流~
 

參考資料

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章