Cisco Spark適配iPhone X

iPhone X已經發(fā)布有一段時間了姆打,最近我負責把公司的產品Cisco Spark適配iPhone X良姆。雖然還沒有采購到iPhone X,但是借助模擬器幔戏,勉強把代碼改好玛追,讓Spark適配iPhone X。我們在產品中闲延,除了啟動頁面痊剖,所有的UI都是用代碼來實現的。所以我下面提到的所有內容都是在代碼中如何適配iPhone X垒玲,不涉及Interface Builder陆馁。下面是一些總結,可能無法做到面面俱到合愈,但是相信能解決大部分的iPhone X適配問題叮贩。

什么是Safe Area?

iOS11為了解決iPhone X上異形屏的問題佛析,新引入了安全區(qū)(Safe Area)益老。在代碼中安全區(qū)是通過safeAreaLayoutGuide來表示的。來看看蘋果官方怎么定義safeAreaLayoutGuide寸莫。

safeAreaLayoutGuide

這里面有些單詞不好理解捺萌,比如accommodates是什么意思。很容易把人搞混淆膘茎。不過如果我們對照這safeAreaInsets的定義來看桃纯,就能大概搞清楚safe area是什么來。

safeAreaInsets

簡單的總結一下什么是安全區(qū):

1. safeAreaLayoutGuide是UIView的一個屬性披坏,類型是UILayoutGuide慈参。UILayoutGuide有一個layoutFrame的屬性。說明它是一塊方形區(qū)域(CGRect)刮萌。該方形區(qū)域對應的就是Safe Area驮配。那么該方形區(qū)域有多大呢?

2. Safe Area避開了導航欄,狀態(tài)欄壮锻,工具欄以及可能遮擋View顯示的父View琐旁。

????2.1. 對于一個控制器的root view,safe area等于該view的Frame減去各種bar以及additionalSafeAreaInsets剩余的部分猜绣。

????2.2. 對于視圖層級上除此之外的其他view灰殴,safe area對應沒有被其他內容(各種bar,additionalSafeAreaInsets)遮擋的部分掰邢。比如牺陶,如果一個view完全在superview的safe area中,這個view的safe area就是view本身。

3. 根據上面一條疮方,我們在編碼的時候划栓,只要針對safe area編程,就不會被各種bar遮擋狮鸭。

4. 是不是所有view都需要針對safe area編程。其實也不是的多搀,只要控制器View第一層級的subviews是針對safe area編程就可以了歧蕉。后面層級的subview無需針對safe area編程。因為這種情況下康铭,safe area的大小就是view frame的大小惯退。

5. 如果view不顯示或者不在顯示層級中,該view的safe area的大小就等于view frame大小从藤。

6. safeAreaLayoutGuide = view.frame - safeAreaInsets蒸痹。 我們可以通過調整additionalSafeAreaInsets來控制safe area的大小。

如何適配iPhone X

現在已經搞清楚了safe area呛哟。那么我們在代碼中需要做哪些修改來適配iPhone X呢叠荠?我們所有的UI都是用代碼來實現的,并且基本上都是通過constraint來實現Auto layout扫责¢欢Γ基本用到3種添加約束的方式。

1. 用的最多的就是VFL(visual format language)鳖孤, 這種方式一行代碼可以寫出多個約束者娱,所有用的最多。

?customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[redView]-|", options: [], metrics: nil, views: views))

2. Layout Anchor苏揣。這種方式用的也不少黄鳍。我們主要用來約束view相對其他view的X,Y中心位置.

customConstraints.append(bindToLyraSpaceButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)) ? ? ? ? customConstraints.append(bindRoomNameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor))

3. 這種又臭又長的創(chuàng)建NSLayoutConstraint方式,基本上用的比較少平匈。

customConstraints.append(NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal, toItem: view attribute: .top, multiplier: 1, constant: 0)

下面來逐個分析這幾種添加越蘇的方式需要怎么修改框沟。

VFL的適配

比如藏古,一個全屏的view(上下左右都不留margin的那種),我們一般用這種方式來寫:

let views: [String: AnyObject] = ["redView" : redView] ??

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|[redView]|", options: [], metrics: nil, views: views))

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|[redView]|", options: [], metrics: nil, views: views))

NSLayoutConstraint.activate(customConstraints)

對于這種方式創(chuàng)建出來的view忍燥,很明顯拧晕,在iPhone X上是會被劉海遮擋住的。因為VFH添加的約束是針對View而不是上面提到的safe area梅垄。這種代碼改起來稍微有點痛苦厂捞,比如以上的代碼需要改成這樣,來針對safe area添加約束队丝。? ? ? ? ? ? ?

let safeArea = view.safeAreaLayoutGuide? ??

redView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true ? ? ? ? redView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true ? ? ? ? redView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true ? ? ? ? redView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true

還有一種情況靡馁,全屏的view上下左右留有一定的margin。我們一般這么寫:

let views: [String: AnyObject] = ["redView" : redView] ??

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-16-[redView]-16-|", options: [], metrics: nil, views: views))

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-16-[redView]-16-|", options: [], metrics: nil, views: views))

NSLayoutConstraint.activate(customConstraints)

這種情況跟上面的改法一樣机久,只需要指定一下constant值就行了

let safeArea = view.safeAreaLayoutGuide ?? ? ? ? ? ? ? ?

redView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 16).isActive = true? ? ?

redView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: 16).isActive = true ? ? ? ?

redView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 16).isActive = true ? ? ? ?

redView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: 16).isActive = true

如果恰好走狗屎運臭墨,你的產品設計的margin值等于系統的默認margin 8,你一般會這么寫吞加。反正我們代碼里基本margin都是16。這種情況尽狠,你無需修改任何代碼就可以適配iPhone X衔憨。

let views: [String: AnyObject] = ["redView" : redView] ??

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-[redView]-|", options: [], metrics: nil, views: views))

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-[redView]-|", options: [], metrics: nil, views: views))

NSLayoutConstraint.activate(customConstraints)

因為這種情況,你沒有修改系統默認的margins袄膏,系統會自動給你添加layoutMargins践图。而layoutMargins已經默認對safe area處理過了。以上代碼跟下面的代碼是一樣的:

let margin = view.layoutMarginsGuide ? ? ? ?

view.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)? ? ? ? ? ? ? ?

redView.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true ? ? ? ?

redView.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true ? ? ? ?

redView.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true ? ? ? ?

redView.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true

因為layoutMargins已經針對safe area處理過了沉馆,所以我們其實可以直接跳過safe area码党,針對layoutMarginGuide編程來適配iPhone X。比如斥黑,如果你的margins是(16揖盘,16,16锌奴,16)兽狭,你只需要修改layoutMargins這個屬性,其余的代碼還跟原來一樣用VFL:

let views: [String: AnyObject] = ["redView" : redView] ?? ? ? ? ? ? ? ? customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-[redView]-|", options: [], metrics: nil, views: views)) ? ? ? ?

customConstraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-[redView]-|", options: [], metrics: nil, views: views)) ?? ? ? ? ? ? ? ?

view.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) ? ? ? ? NSLayoutConstraint.activate(customConstraints)

所以對于VFL鹿蜀,有兩種辦法來修改你的代碼去適配iPhone X箕慧。一種是直接針對safe area編程,一種是直接跳過safe area針對layoutMarginsGuide編程茴恰。使用前一種颠焦,需要寫if/else來處理不同的iOS版本,這一點后面會提到往枣。使用后一種伐庭,可能代碼看起來會有點雜亂粉渠。我們用的是前一種。

Layout Anchor的適配

layout Anchor的適配代碼比較簡單似忧,只需要把view.xxxAnchor改成view.safeAreaLayoutGuide就可以了渣叛。

customConstraints.append(bindToLyraSpaceButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)) ? ? ? ? customConstraints.append(bindRoomNameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor))

// 改成這樣:view ------> view..safeAreaLayoutGuide

customConstraints.append(bindToLyraSpaceButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor)) ? ? ? ? customConstraints.append(bindRoomNameLabel.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor))

NSLayoutConstraint方式的適配

這種方式的適配跟上一種一樣,也是把view改成safeAreaLayoutGuide就可以了

customConstraints.append(NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0))

// 改成這樣:view ------> view..safeAreaLayoutGuide

customConstraints.append(NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .bottom, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .leading, multiplier: 1, constant: 0)) ? ? ? ? customConstraints.append(NSLayoutConstraint(item: redView, attribute: .trailing, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .trailing, multiplier: 1, constant: 0))

兼容iOS 10 和 iOS 11

safeAreaLayoutGuide和safeAreaInsets這兩個屬性都是iOS 11新加入的盯捌。所以如果你的App需要兼容iOS 9和iOS 10淳衙,要寫很多這樣的判斷代碼

if #available(iOS 11.0, *) { ? ? ? ? ? ? return safeAreaLayoutGuide ? ? ? ? }

為了避免在代碼中到處寫這種判斷代碼,我們對對UIView進行了擴展饺著。具體方式如下:

首先定義一個UILayoutGuideProtocol箫攀,讓UIView和UILayoutGuide都實現這個protocol。


UILayoutGuideProtocol

然后擴展UIView幼衰,給它添加一個safeLayoutGuide的屬性靴跛,這個屬性是這樣實現的:


UIView extension

在使用的地方,需要調用view.safeAreaLayoutGuide的地方渡嚣,改為調用view.safeLayoutGuide梢睛。這樣就不需要寫if/else了。

UITableView和UICollectionView

UITableView比較特殊识椰,系統已經幫你出里過safe area绝葡。切記,在你的UITableViewCell中腹鹉,所以的view都要添加到contentView藏畅。否則你的內容會被遮蓋。

override init(style: UITableViewCellStyle, reuseIdentifier: String?) { ? ? ? ?

????super.init(style: style, reuseIdentifier: reuseIdentifier) ?? ? ? ? ? ? ? ?

????contentView.addSubview(label)

????//addSubview(label)? ?//這種方式是有問題的

}

很不幸功咒,UICollectionView不是自適應的愉阎,系統沒有幫你處理iPhone X。即使你所有的view都添加到contenView中力奋,你的內容依然會被遮蓋榜旦。所有,對于CollectionView景殷, 你必須像處理Label章办,button那樣,用前面提到的的方式針對safe area編程滨彻。

(第一次發(fā)技術文章藕届,水平有限,如果大家看了有問題亭饵,請指出來休偶。)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辜羊,隨后出現的幾起案子踏兜,更是在濱河造成了極大的恐慌词顾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碱妆,死亡現場離奇詭異肉盹,居然都是意外死亡,警方通過查閱死者的電腦和手機疹尾,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門上忍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纳本,你說我怎么就攤上這事窍蓝。” “怎么了繁成?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵吓笙,是天一觀的道長。 經常有香客問我巾腕,道長面睛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任尊搬,我火速辦了婚禮叁鉴,結果婚禮上,老公的妹妹穿的比我還像新娘毁嗦。我一直安慰自己亲茅,他們只是感情好回铛,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布狗准。 她就那樣靜靜地躺著,像睡著了一般茵肃。 火紅的嫁衣襯著肌膚如雪腔长。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天验残,我揣著相機與錄音捞附,去河邊找鬼。 笑死您没,一個胖子當著我的面吹牛鸟召,可吹牛的內容都是我干的。 我是一名探鬼主播氨鹏,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼欧募,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仆抵?” 一聲冷哼從身側響起跟继,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤种冬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舔糖,有當地人在樹林里發(fā)現了一具尸體娱两,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年金吗,在試婚紗的時候發(fā)現自己被綠了十兢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡辽聊,死狀恐怖纪挎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情跟匆,我是刑警寧澤异袄,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站玛臂,受9級特大地震影響烤蜕,放射性物質發(fā)生泄漏。R本人自食惡果不足惜迹冤,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一讽营、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泡徙,春花似錦橱鹏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至礁竞,卻和暖如春糖荒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背模捂。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工捶朵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狂男。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓综看,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岖食。 傳聞我的和親對象是個殘疾皇子红碑,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容