safe area-- iphone x

iOS 7 之后蘋(píng)果給 UIViewController 引入了 topLayoutGuide 和 bottomLayoutGuide 兩個(gè)屬性來(lái)描述不希望被透明的狀態(tài)欄或者導(dǎo)航欄遮擋的最高位置(status bar, navigation bar, toolbar, tab bar 等)挑庶。這個(gè)屬性的值是一個(gè) length 屬性( topLayoutGuide.length)颁独。 這個(gè)值可能由當(dāng)前的 ViewController 或者 NavigationController 或者 TabbarController 決定臣樱。

  • 一個(gè)獨(dú)立的ViewController朵逝,不包含于任何其他的ViewController。如果狀態(tài)欄可見(jiàn),topLayoutGuide表示狀態(tài)欄的底部,否則表示這個(gè)ViewController的上邊緣舰褪。

  • 包含于其他ViewController的ViewController不對(duì)這個(gè)屬性起決定作用,而是由容器ViewController決定這個(gè)屬性的含義:

  • 如果導(dǎo)航欄(Navigation Bar)可見(jiàn)疏橄,topLayoutGuide表示導(dǎo)航欄的底部占拍。

  • 如果狀態(tài)欄可見(jiàn),topLayoutGuide表示狀態(tài)欄的底部捎迫。

  • 如果都不可見(jiàn)晃酒,表示ViewController的上邊緣。

    這部分還比較好理解窄绒,總之是屏幕上方任何遮擋內(nèi)容的欄的最底部贝次。

iOS 11 開(kāi)始棄用了這兩個(gè)屬性, 并且引入了 Safe Area 這個(gè)概念颗祝。蘋(píng)果建議: 不要把 Control 放在 Safe Area 之外的地方

// These objects may be used as layout items in the NSLayoutConstraint API

@available(iOS, introduced: 7.0, deprecated: 11.0)

open var topLayoutGuide: UILayoutSupport { get }

@available(iOS, introduced: 7.0, deprecated: 11.0)

open var bottomLayoutGuide: UILayoutSupport { get }

今天, 來(lái)研究一下 iOS 11 中新引入的這個(gè) API浊闪。

UIView 中的 safe area

iOS 11 中 UIViewController 的 topLayoutGuide 和 bottonLayoutGuide 兩個(gè)屬性被 UIView 中的 safe area 替代了。

@available(iOS 11.0, *)

open var safeAreaInsets: UIEdgeInsets { get }

@available(iOS 11.0, *)

open func safeAreaInsetsDidChange()

safeAreaInsets

這個(gè)屬性表示相對(duì)于屏幕四個(gè)邊的間距螺戳, 而不僅僅是頂部還有底部。這么說(shuō)好像沒(méi)有什么感覺(jué), 我們來(lái)看一看這個(gè)東西分別在 iPhone X 和 iPhone 8 中是什么樣的吧折汞!

什么都沒(méi)有做, 只是新建了一個(gè)工程然后在

Main.storyboard

中的 UIViewController 中拖了一個(gè)橙色的 View 并且設(shè)置約束為:

image

ViewController.swift

viewDidLoad

中打印

override func viewDidLoad() {

    super.viewDidLoad()

    print(view.safeAreaInsets)

}

// 無(wú)論是iPhone 8 還是 iPhone X 輸出結(jié)果均為

// UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

image

iPhone 8 VS iPhone X Safe Area (豎屏)

image

iPhone 8 VS iPhone X Safe Area (橫屏)

這樣對(duì)比可以看出, iPhone X 同時(shí)具有上下, 還有左右的 Safe Area倔幼。

**再來(lái)看這個(gè)例子: ** 拖兩個(gè)自定義的 View, 這個(gè) View 上有一個(gè) 顯示很多字的Label。然后設(shè)置這兩個(gè) View 的約束分別是:

let view1 = MyView()

let view2 = MyView()

view.addSubview(view1)

view.addSubview(view2)

let screenW = UIScreen.main.bounds.size.width

let screenH = UIScreen.main.bounds.size.height

view1.frame = CGRect(

x: 0,

y: 0,

width:screenW,

height: 200)

view2.frame = CGRect(

x: 0,

y: screenH - 200,

width:screenW,

height: 200)
image

可以看出來(lái)爽待, 子視圖被頂部的劉海以及底部的 home 指示區(qū)擋住了损同。我們可以使用 frame 布局或者 auto layout 來(lái)優(yōu)化這個(gè)地方:

let insets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? UIEdgeInsets.zero

view1.frame = CGRect(

x: insets.left,

y: insets.top,

width:view.bounds.width - insets.left - insets.right,

height: 200)

view2.frame = CGRect(

x: insets.left,

y: screenH - insets.bottom - 200,

width:view.bounds.width - insets.left - insets.right,

height: 200)
image

這樣起來(lái)好多了, 還有另外一個(gè)更好的辦法是直接在自定義的 View 中修改 Label 的布局:

override func layoutSubviews() {

super.layoutSubviews()

if #available(iOS 11.0, *) {

    label.frame = safeAreaLayoutGuide.layoutFrame

}

}

image

這樣, 不僅僅是在 ViewController 中能夠使用 safe area 了翩腐。

UIViewController 中的 safe area

在 iOS 11 中 UIViewController 有一個(gè)新的屬性

@available(iOS 11.0, *)

open var additionalSafeAreaInsets: UIEdgeInsets

當(dāng) view controller 的子視圖覆蓋了嵌入的子 view controller 的視圖的時(shí)候。比如說(shuō)膏燃, 當(dāng) UINavigationController 和 UITabbarController 中的 bar 是半透明(translucent) 狀態(tài)的時(shí)候, 就有

additionalSafeAreaInsets

image

自定義的 View 上面的 label 布局兼容了 safe area

// UIView

@available(iOS 11.0, *)

open func safeAreaInsetsDidChange()

//UIViewController

@available(iOS 11.0, *)

open func viewSafeAreaInsetsDidChange()

這兩個(gè)方法分別是 UIView 和 UIViewController 的 safe area insets 發(fā)生改變時(shí)調(diào)用的方法茂卦,如果需要做一些處理,可以重寫(xiě)這個(gè)方法组哩。有點(diǎn)類(lèi)似于 KVO 的意思等龙。

模擬 iPhone X 的 safe area

額外的 safe area insets 也能用來(lái)測(cè)試你的 app 是否支持 iPhone X。在沒(méi)有 iPhone X 也不方便使用模擬器的時(shí)候伶贰, 這個(gè)還是很有用的蛛砰。

//豎屏

additionalSafeAreaInsets.top = 24.0

additionalSafeAreaInsets.bottom = 34.0

//豎屏, status bar 隱藏

additionalSafeAreaInsets.top = 44.0

additionalSafeAreaInsets.bottom = 34.0

//橫屏

additionalSafeAreaInsets.left = 44.0

additionalSafeAreaInsets.bottom = 21.0

additionalSafeAreaInsets.right = 44.0

UIScrollView 中的 safe area

在 scroll view 上加一個(gè) label。設(shè)置scroll 的約束為:

scrollView.snp.makeConstraints { (make) in

        make.edges.equalToSuperview()

    }
image

iOS 7 中引入 UIViewController 的 automaticallyAdjustsScrollViewInsets 屬性在 iOS11 中被廢棄掉了黍衙。取而代之的是 UIScrollView 的 contentInsetAdjustmentBehavior

@available(iOS 11.0, *)

public enum UIScrollViewContentInsetAdjustmentBehavior : Int {

case automatic          //default value

case scrollableAxes

case never

case always

}

@available(iOS 11.0, *)

open var contentInsetAdjustmentBehavior: UIScrollViewContentInsetAdjustmentBehavior

Content Insets Adjustment Behavior

never 不做調(diào)整泥畅。

scrollableAxes content insets 只會(huì)針對(duì) scrollview 滾動(dòng)方向做調(diào)整。

always content insets 會(huì)針對(duì)兩個(gè)方向都做調(diào)整琅翻。

automatic 這是默認(rèn)值位仁。當(dāng)下面的條件滿足時(shí), 它跟 always 是一個(gè)意思

  • 能夠水平滾動(dòng)方椎,不能垂直滾動(dòng)

  • scroll view 是 當(dāng)前 view controller 的第一個(gè)視圖

  • 這個(gè)controller 是被navigation controller 或者 tab bar controller 管理的

  • automaticallyAdjustsScrollViewInsets 為 true

在其他情況下 automoatc 跟 scrollableAxes 一樣

Adjusted Content Insets

iOS 11 中 UIScrollView 新加了一個(gè)屬性: adjustedContentInset

@available(iOS 11.0, *)

open var adjustedContentInset: UIEdgeInsets { get }

adjustedContentInset 和 contentInset 之間有什么區(qū)別呢吼畏?

在同時(shí)有 navigation 和 tab bar 的 view controller 中添加一個(gè) scrollview 然后分別打印兩個(gè)值:

//iOS 10

//contentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)

//iOS 11

//contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

//adjustedContentInset = UIEdgeInsets(top: 64.0, left: 0.0, bottom: 49.0, right: 0.0)

然后再設(shè)置:

// 給 scroll view 的四個(gè)方向都加 10 的間距

scrollView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

打印:

//iOS 10

//contentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)

//iOS 11

//contentInset = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)

//adjustedContentInset = UIEdgeInsets(top: 74.0, left: 10.0, bottom: 59.0, right: 10.0)

由此可見(jiàn)洞翩,在 iOS 11 中 scroll view 實(shí)際的 content inset 可以通過(guò) adjustedContentInset 獲取。這就是說(shuō)如果你要適配 iOS 10 的話。這一部分的邏輯是不一樣的贬养。

系統(tǒng)還提供了兩個(gè)方法來(lái)監(jiān)聽(tīng)這個(gè)屬性的改變

//UIScrollView

@available(iOS 11.0, *)

open func adjustedContentInsetDidChange()

//UIScrollViewDelegate

@available(iOS 11.0, *)

optional public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView)

UITableView 中的 safe area

我們現(xiàn)在再來(lái)看一下 UITableView 中 safe area 的情況。我們先添加一個(gè)有自定義 header 以及自定義 cell 的 tableview挣输。設(shè)置邊框?yàn)?self.view 的邊框刊驴。也就是

tableView.snp.makeConstraints { (make) in

make.edges.equalToSuperview()

}

或者

tableView.frame = view.bounds

image

自定義的 header 上面有一個(gè) lable,自定義的 cell 上面也有一個(gè) label胸墙。將屏幕橫屏之后會(huì)發(fā)現(xiàn)我注,cell 以及 header 的布局均自動(dòng)留出了 safe area 以外的距離。cell 還是那么大迟隅,只是 cell 的 contnt view 留出了相應(yīng)的距離但骨。這其實(shí)是 UITableView 中新引入的屬性管理的:

@available(iOS 11.0, *)

open var insetsContentViewsToSafeArea: Bool

insetsContentViewsToSafeArea 的默認(rèn)值是 true, 將其設(shè)置成 no 之后:

image

可以看出來(lái) footer 和 cell 的 content view 的大小跟 cell 的大小相同了智袭。這就是說(shuō):在 iOS 11 下, 并不需要改變 header/footer/cell 的布局奔缠, 系統(tǒng)會(huì)自動(dòng)區(qū)適配 safe area

需要注意的是, Xcode 9 中使用 IB 拖出來(lái)的 TableView 默認(rèn)的邊框是 safe area 的。所以實(shí)際運(yùn)行起來(lái) tableview 都是在 safe area 之內(nèi)的吼野。

image

UICollectionView 中的 safe area

我們?cè)谧鲆粋€(gè)相同的 collection view 來(lái)看一下 collection view 中是什么情況:

image

這是一個(gè)使用了

UICollectionViewFlowLayout

的 collection view校哎。 滑動(dòng)方向是豎向的。cell 透明, cell 的 content view 是白色的闷哆。這些都跟上面 table view 一樣腰奋。header(UICollectionReusableView) 沒(méi)有 content view 的概念, 所以給其自身設(shè)置了紅色的背景。

從截圖上可以看出來(lái)抱怔, collection view 并沒(méi)有默認(rèn)給 header cell footer 添加safe area 的間距劣坊。能夠?qū)⒉季终{(diào)整到合適的情況的方法只有將 header/ footer / cell 的子視圖跟其 safe area 關(guān)聯(lián)起來(lái)。跟 IB 中拖 table view 一個(gè)道理屈留。

image.gif

現(xiàn)在我們?cè)僭囋嚢巡季终{(diào)整成更像 collection view 那樣:

image

截圖上可以看出來(lái)橫屏下, 左右兩邊的 cell 都被劉海擋住了局冰。這種情況下, 我們可以通過(guò)修改 section insets 來(lái)適配 safe area 來(lái)解決這個(gè)問(wèn)題。但是再 iOS 11 中绕沈, UICollectionViewFlowLayout 提供了一個(gè)新的屬性 sectionInsetReference 來(lái)幫你做這件事情锐想。

@available(iOS 11.0, *)

public enum UICollectionViewFlowLayoutSectionInsetReference : Int {

case fromContentInset

case fromSafeArea

case fromLayoutMargins

}

/// The reference boundary that the section insets will be defined as relative to. Defaults to .fromContentInset.

/// NOTE: Content inset will always be respected at a minimum. For example, if the sectionInsetReference equals .fromSafeArea, but the adjusted content inset is greater that the combination of the safe area and section insets, then section content will be aligned with the content inset instead.

@available(iOS 11.0, *)

open var sectionInsetReference: UICollectionViewFlowLayoutSectionInsetReference

可以看出來(lái),系統(tǒng)默認(rèn)是使用

.fromContentInset

我們?cè)俜謩e修改, 看具體會(huì)是什么樣子的乍狐。

image

fromSafeArea

這種情況下 section content insets 等于原來(lái)的大小加上 safe area insets 的大小赠摇。

跟使用

.fromLayoutMargins

相似使用這個(gè)屬性 colection view 的 layout margins 會(huì)被添加到 section content insets 上面。

image

IB 中的 Safe Area

前面的例子都說(shuō)的是用代碼布局要實(shí)現(xiàn)的部分浅蚪。但是很多人都還是習(xí)慣用 Interface Builder 來(lái)寫(xiě) UI 界面藕帜。蘋(píng)果在 WWDC 2107 Session 412 中提到:Storyboards 中的 safe area 是向下兼容的

也就是說(shuō), 即使在 iOS10 及以下的 target 中,也可以使用 safe area 來(lái)做布局惜傲。唯一需要做的就是給每個(gè) stroyboard 勾選 Use Safe Area Layout Guide洽故。實(shí)際測(cè)試看,應(yīng)該是 iOS9 以后都只需要這么做盗誊。

知識(shí)點(diǎn)

: 在使用 IB 設(shè)置約束之后时甚, 注意看相對(duì)的是 superview 還是 topLayoutGuide/bottomLayoutGuide, 包括在 Xcode 9 中勾選了 Use Safe Area Layout Guide 之后哈踱,默認(rèn)應(yīng)該是相對(duì)于 safe area 了荒适。

總結(jié)

  • 在適配 iPhone X 的時(shí)候首先是要理解 safe area 是怎么回事。盲目的 if iPhoneX{} 只會(huì)給之后的工作代碼更多的麻煩开镣。

  • 如果只需要適配到 iOS9 之前的 storyboard 都只需要做一件事情刀诬。

  • Xcode9 用 IB 可以看得出來(lái), safe area 到處都是了。理解起來(lái)很簡(jiǎn)單邪财。就是系統(tǒng)對(duì)每個(gè) View 都添加了 safe area陕壹, 這個(gè)區(qū)域的大小,是否跟 view 的大小相同是系統(tǒng)來(lái)決定的树埠。在這個(gè) View 上的布局只需要相對(duì)于 safe area 就可以了糠馆。每個(gè) View 的 safe area 都可以通過(guò) iOS 11 新增的 API

    safeAreaInsets

    或者

    safeAreaLayoutGuide

    獲取。

  • 對(duì)與 UIViewController 來(lái)說(shuō)新增了

    additionalSafeAreaInsets

    這個(gè)屬性, 用來(lái)管理有 tabbar 或者 navigation bar 的情況下額外的情況弥奸。

  • 對(duì)于 UIScrollView榨惠, UITableView, UICollectionView 這三個(gè)控件來(lái)說(shuō)盛霎,系統(tǒng)以及做了大多數(shù)的事情赠橙。

  • scrollView 只需要設(shè)置 contentInsetAdjustmentBehavior 就可以很容易的適配帶 iPhoneX

  • tableView 只需要在 cell header footer 等設(shè)置約束的時(shí)候相對(duì)于 safe area 來(lái)做

  • 對(duì) collection view 來(lái)說(shuō)修改 sectionInsetReference 為 .safeArea 就可以做大多數(shù)的事情了。

  • 總的來(lái)說(shuō)愤炸, safe area 可以看作是系統(tǒng)在所有的 view 上加了一個(gè)虛擬的 view期揪, 這個(gè)虛擬的 view 的大小等都是跟 view 的位置等有關(guān)的(當(dāng)然是在 iPhoneX上才有值)

    以后在寫(xiě)代碼的時(shí)候,自定義的控件都盡量針對(duì) safe area 這個(gè)虛擬的 view 進(jìn)行布局规个。

原文:https://mp.weixin.qq.com/s?__biz=MzA3NzM0NzkxMQ==&mid=2655359233&idx=1&sn=4eb8ad2ccd6a2705037bfce2394b8566&chksm=84e25c29b395d53ffe2d9f17aa96580f8c53cec0f5bdabc71814a5dfcfad37b9e5bf4129b9c0&mpshare=1&scene=24&srcid=1104dyU7McC2zWLzbj3WKugM&key=5dba587a8b06ccca244188734caa87801c04b5fac2cec798fdecfd3379d3775b9d1023e378e3b55061eec4e61977618af724c1f58e8d8765ae59e0f9b954f6b4b5c8a39ee67c6e767516436b0c4ba6a5&ascene=0&uin=MTU4NDc4MTMwMQ%3D%3D&devicetype=iMac16%2C2+OSX+OSX+10.12.5+build(16F73)&version=12020810&nettype=WIFI&fontScale=100&pass_ticket=%2FbflYGhQOmSeptirRLkQ5YI7B%2F0OTRjnd10kdPdarRJtSCIUZKjcut%2F0h6ukHNF6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凤薛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诞仓,更是在濱河造成了極大的恐慌缤苫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墅拭,死亡現(xiàn)場(chǎng)離奇詭異活玲,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谍婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)舒憾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人穗熬,你說(shuō)我怎么就攤上這事镀迂。” “怎么了唤蔗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵探遵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我妓柜,道長(zhǎng)箱季,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任领虹,我火速辦了婚禮规哪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘塌衰。我一直安慰自己诉稍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布最疆。 她就那樣靜靜地躺著杯巨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪努酸。 梳的紋絲不亂的頭發(fā)上服爷,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼仍源。 笑死心褐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笼踩。 我是一名探鬼主播逗爹,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嚎于!你這毒婦竟也來(lái)了掘而?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤于购,失蹤者是張志新(化名)和其女友劉穎袍睡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體肋僧,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斑胜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了色瘩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伪窖。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖居兆,靈堂內(nèi)的尸體忽然破棺而出覆山,到底是詐尸還是另有隱情,我是刑警寧澤泥栖,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布簇宽,位于F島的核電站,受9級(jí)特大地震影響吧享,放射性物質(zhì)發(fā)生泄漏魏割。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一钢颂、第九天 我趴在偏房一處隱蔽的房頂上張望钞它。 院中可真熱鬧,春花似錦殊鞭、人聲如沸遭垛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锯仪。三九已至,卻和暖如春趾盐,著一層夾襖步出監(jiān)牢的瞬間庶喜,已是汗流浹背小腊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留久窟,地道東北人秩冈。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瘸羡,于是被迫代替她去往敵國(guó)和親漩仙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搓茬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容