前言
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。
這裏兩個點需要解釋一下:
- 若我們使用自定義圖片作爲導航欄的背景,那麼UIViewController的view(下面稱爲視圖)就不會延伸到navigationBar的頂部,而是從它的底部開始——正如往常一樣。
- 若我們使用一張高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的頁面佈局。
上文如有什麼錯漏,歡迎指正交流~