iOS開發(fā)-LayoutGuide的前世今生(從top/bottom LayoutGuide到Safe Area)

前言

LayoutGuide這個概念在本人從事iOS開發(fā)過程中一直是比較陌生的肢扯。直至最近想要了解這個細(xì)碎的知識點褂乍,就隨手查了一下清钥,發(fā)現(xiàn)這個概念從iOS7的top/bottom LayoutGuide柒桑,到iOS9 UILayoutGuide類的引入,直至最近的iOS11涉及適配iPhone X仅政,引入了Safe Area概念,并且UIView增加了一個safeAreaLayoutGuide屬性扔茅。感覺有點傻傻分不清楚已旧。因此我要從iOS7開始好好研究一下此為何物。

iOS7 topLayoutGuide/bottomLayoutGuide

創(chuàng)建一個叫做LayoutGuideStudy的工程召娜,我們打開看一下Main.storyboard:


storyboard-top_bottom_layoutGuide.png

可以看到View Controller下面出現(xiàn)topLayoutGuide/bottomLayoutGuide這兩個東西运褪,并且和Controller的View處于同一層級。
并且在UIViewController頭文件里面,這兩個屬性是id類型遵守一個UILayoutSupport協(xié)議并且是只讀的屬性:

// These objects may be used as layout items in the NSLayoutConstraint API
@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide NS_AVAILABLE_IOS(7_0);
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide NS_AVAILABLE_IOS(7_0);

這就說明了這兩個LayoutGuide是系統(tǒng)自動創(chuàng)建并管理的玖瘸,這也解釋了剛剛我們創(chuàng)建的工程里面Main.storyboard為什么會自動出現(xiàn)topLayoutGuide/bottomLayoutGuide秸讹。

看看有什么用

我們拖拽一個紅色的view到Controller的view里,添加約束的時候雅倒,注意到是右下角的約束設(shè)定框璃诀,關(guān)于頂部約束的基準(zhǔn)view下拉選擇,xcode默認(rèn)勾選了Top Layout Guide:


autoLayout-useLayoutGuide.png

蔑匣,再添加完寬高約束劣欢,最后約束結(jié)果:


constraint_result.png

直接build看結(jié)果:
run-result.png

可以看出top約束基于系統(tǒng)提供的topLayoutGuide,系統(tǒng)會自動為這個view避開頂部狀態(tài)欄裁良。
我們在ViewController里面打印紅色view:

<UIView: 0x7ff10860fa90; frame = (0 20; 240 128); autoresize = RM+BM; layer = <CALayer: 0x60000003b5c0>>

看到紅色view的y值就是20.剛好是狀態(tài)欄的高度凿将。由此看出Top Layout Guide的作用就是在進行自動布局的時候,幫助開發(fā)者隔離出狀態(tài)欄的空間价脾。那么我們再看看導(dǎo)航控制器(頂部出現(xiàn)導(dǎo)航欄)的情況:


navagation-result.png

build看結(jié)果:


run_nav_result.png

Top Layout Guide同樣自動幫助隔離出狀態(tài)欄+導(dǎo)航欄牧抵。
在ViewController里面打印黃色view:
<UIView: 0x7fb04fe08040; frame = (0 64; 240 128); autoresize = RM+BM; layer = <CALayer: 0x61800003ef60>>

看到黃色view的y值就是64.剛好是狀態(tài)欄+導(dǎo)航欄的高度。

同理侨把,bottomLayoutGuide就是用于在TabbarController里面隔離底部的tabbar:


tabbar-bottomGuide.png

扒一扒topLayoutGuide/bottomLayoutGuide對象:

在UIViewController的viewDidLayoutSubviews方法打印

- (void)viewDidLayoutSubviews
 {
    [super viewDidLayoutSubviews];
    NSLog(@"topLayoutGuide-%@",self.topLayoutGuide);
    NSLog(@"bottomLayoutGuide-%@",self.bottomLayoutGuide);
}
打印結(jié)果:
topLayoutGuide-
<_UILayoutGuide: 0x7fd7cce0c350; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x61000003f2c0>>

bottomLayoutGuide-
<_UILayoutGuide: 0x7fd7cce0d6b0; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x610000221620>>

這個是_UILayoutGuide類型的私有對象犀变,看起來里面有frame,hidden秋柄,layer屬性获枝,感覺十分像UIView啊,那我們就驗證一下:

if ([self.topLayoutGuide isKindOfClass:[UIView class]]) {
    NSLog(@"topLayoutGuide is an UIView");
}
 if ([self.bottomLayoutGuide isKindOfClass:[UIView class]]) {
    NSLog(@"bottomLayoutGuide is an UIView");
}
打印結(jié)果:
topLayoutGuide is an UIView
bottomLayoutGuide is an UIView

得到結(jié)論就是topLayoutGuide/bottomLayoutGuide其實是一個UIView類型的對象骇笔。
我們再打印一下UIViewController的view的subviews:

- (void)viewDidLayoutSubviews
 {
    [super viewDidLayoutSubviews];
     NSLog(@"viewController view subViews %@",self.view.subviews);
}
打印結(jié)果:
viewController view subViews (
    "<UIView: 0x7ffc774035b0; frame = (0 64; 240 128); autoresize = RM+BM; layer = <CALayer: 0x60800002c720>>",
    "<_UILayoutGuide: 0x7ffc7740ae10; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x60800002c480>>",
    "<_UILayoutGuide: 0x7ffc7740b1e0; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x60800002b820>>"
)

這樣就明了了省店!
總結(jié)一下:
topLayoutGuide/bottomLayoutGuide其實是作為虛擬的占坑view机隙,用于在自動布局的時候幫助開發(fā)者避開頂部的狀態(tài)欄,導(dǎo)航欄以及底部的tabbar等萨西。

iOS9 UILayoutGuide

iOS9開始有鹿,蘋果新增加了一個UILayoutGuide的類,看看蘋果官方對它的解釋:

The UILayoutGuide class defines a rectangular area that can interact with Auto Layout. 
Use layout guides to replace the dummy views you may have created to represent
inter-view spaces or encapsulation in your user interface

大概意思是UILayoutGuide用于提供一個矩形區(qū)域可以用Auto Layout來定制一些約束特性谎脯,作為一個虛擬的view使用葱跋。
我想大概是蘋果的工程師覺得以前的topLayoutGuide/bottomLayoutGuide提供虛擬占坑view,隔離導(dǎo)航欄/tabber的思想不錯源梭,進而有了啟發(fā)娱俺,能不能讓整個LayoutGuide變得更靈活,讓開發(fā)者能夠自由定制废麻,于是這個UILayoutGuide類就設(shè)計出來了荠卷。。

那么如何自由定制一個UILayoutGuide烛愧,我們看看這個類的幾個屬性:

@property(readonly, strong) NSLayoutXAxisAnchor *leadingAnchor;
@property(readonly, strong) NSLayoutXAxisAnchor *trailingAnchor;
@property(readonly, strong) NSLayoutXAxisAnchor *leftAnchor;
@property(readonly, strong) NSLayoutXAxisAnchor *rightAnchor;
@property(readonly, strong) NSLayoutYAxisAnchor *topAnchor;
@property(readonly, strong) NSLayoutYAxisAnchor *bottomAnchor;
@property(readonly, strong) NSLayoutDimension *widthAnchor;
@property(readonly, strong) NSLayoutDimension *heightAnchor;
@property(readonly, strong) NSLayoutXAxisAnchor *centerXAnchor;
@property(readonly, strong) NSLayoutYAxisAnchor *centerYAnchor;

NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension這幾個類也是跟隨UILayoutGuide在
iOS9以后新增的油宜,即便很陌生,但我們看上面UILayoutGuide的幾個屬性里面leading怜姿,trailing慎冤,top,bottom沧卢,center等熟悉的字眼蚁堤,就能明白這些屬性就是用于給UILayoutGuide對象增加布局約束的。

我們在看UIView里面新增的一個分類:

@interface UIView (UIViewLayoutConstraintCreation)

@property(readonly, strong) NSLayoutXAxisAnchor *leadingAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutXAxisAnchor *trailingAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutXAxisAnchor *leftAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutXAxisAnchor *rightAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutYAxisAnchor *topAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutYAxisAnchor *bottomAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutDimension *widthAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutDimension *heightAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutXAxisAnchor *centerXAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutYAxisAnchor *centerYAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutYAxisAnchor *firstBaselineAnchor NS_AVAILABLE_IOS(9_0);
@property(readonly, strong) NSLayoutYAxisAnchor *lastBaselineAnchor NS_AVAILABLE_IOS(9_0);

@end

也是跟UILayoutGuide一樣的提供了一致的屬性但狭。這就說明了UILayoutGuide是可以跟UIView進行Auto Layout的約束交互的披诗。

我們用一個例子說明:

創(chuàng)建一個UILayoutGuide,約束它距離控制器view的頂部64立磁,左邊0呈队,寬250,高200息罗,于是在viewDidLoad方法里面的代碼:

// 創(chuàng)建
UILayoutGuide *layoutGuide = [[UILayoutGuide alloc] init];
// 需要使用UIView的addLayoutGuide方法添加新建的layoutGuide
[self.view addLayoutGuide:layoutGuide];
// 正式的約束代碼
[layoutGuide.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:64].active = YES;
[layoutGuide.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[layoutGuide.widthAnchor constraintEqualToConstant:250].active = YES;
[layoutGuide.heightAnchor constraintEqualToConstant:200].active = YES;

這樣約束代碼明顯比使用NSLayoutConstraint簡潔多了掂咒。

接著才沧,我們再創(chuàng)建一個紫色view迈喉,基于這個創(chuàng)建的layoutGuide進行約束,紫色view頂部距離上述layoutGuide底部20温圆,和layoutGuide左對齊挨摸,寬和高和layoutGuide保持一致

UIView *viewBaseLayoutGuide = [[UIView alloc] init];
viewBaseLayoutGuide.translatesAutoresizingMaskIntoConstraints = NO;
viewBaseLayoutGuide.backgroundColor = [UIColor purpleColor];
[self.view addSubview:viewBaseLayoutGuide];

[viewBaseLayoutGuide.topAnchor constraintEqualToAnchor:layoutGuide.bottomAnchor constant:20].active = YES;
[viewBaseLayoutGuide.leadingAnchor constraintEqualToAnchor:layoutGuide.leadingAnchor].active = YES;
[viewBaseLayoutGuide.widthAnchor constraintEqualToAnchor:layoutGuide.widthAnchor].active = YES;
[viewBaseLayoutGuide.heightAnchor constraintEqualToAnchor:layoutGuide.heightAnchor].active = YES;

運行程序的結(jié)果:


layoutGuide-test.png

iOS11 Safe Area / safeAreaLayoutGuide

iOS11又引入了一個Safe Area(安全區(qū)域)的概念,蘋果建議在這個安全區(qū)域內(nèi)放置UI控件岁歉。這個安全區(qū)域的范圍其實就是整個屏幕隔離出狀態(tài)欄得运,導(dǎo)航欄膝蜈,tabar,以及iPhone X頂部劉海熔掺,底部虛擬home手勢區(qū)域的范圍饱搏。
從這個介紹可以看得出,所謂的Safe Area其實也就是升級版本的topLayoutGuide/bottomLayoutGuide置逻,以前只能限制top/bottom的Layout推沸,現(xiàn)在更加強大了。
再看一下UIViewController頭文件:(用xcode9以上版本打開):

@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));

蘋果提示topLayoutGuide/bottomLayoutGuide這兩個屬性在iOS11已經(jīng)過期券坞,推薦使用UIView 的safeAreaLayoutGuide屬性(safeAreaLayoutGuide稍后會介紹)鬓催。

另外用xcode9以上版本創(chuàng)建工程的時候,Main.storyboard會默認(rèn)選擇Use Safe Area Layout Guides恨锚,控制器view下面會出現(xiàn)safe area:

xcode9_safeArea.png

驗證使用safeArea的效果:

如上圖所示脚粟,我們基于storyboard提供的控制器view的safeArea區(qū)域?qū)t色的view進行約束:頂部距離
安全區(qū)域0放前,左邊距離安全區(qū)域0,寬240,高180:

constraint-safeArea.png

在iPhone 8上運行結(jié)果:
run-safeArea.png

為了驗證Safe Area在豎屏iPhone X底部起到的隔離作用正歼,又增加了一個棕色的view:左邊距離安全區(qū)域0,底部距離安全區(qū)域0萍膛,寬240血崭,高180:


iPhone X-bottom.png

在iPhone X上運行結(jié)果:

iPhone X-SafeArea.png

利用安全區(qū)域進行Auto Layout布局,分別在iPhone 8雇盖,iPhone X上以及避開了狀態(tài)欄/劉海/底部的home虛擬手勢區(qū)域忿等,使得開發(fā)者不用關(guān)心狀態(tài)欄以及適配iPhone X避開劉海的高度,只需要安安心心的蘋果指定的這個安全區(qū)域放置子控件崔挖,布局就可以了贸街。

UIView 的safeAreaLayoutGuide屬性

查看UIView在iOS11上關(guān)于Safe Area新增的兩個屬性:

@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
@property(nonatomic,readonly,strong) UILayoutGuide *safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));

很明顯這個只讀的safeAreaLayoutGuide屬性是系統(tǒng)自動創(chuàng)建的,可以讓開發(fā)者用代碼進行基于安全區(qū)域進行自動布局狸相。
點擊控制器的view觸發(fā)touchesBegan進行打印驗證:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"safeAreaInsets %@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));
    NSLog(@"safeAreaGuide %@",self.view.safeAreaLayoutGuide);
}
打印結(jié)果:
safeAreaInsets {44, 0, 34, 0}
safeAreaGuide <UILayoutGuide: 0x6080009a3c60 - "UIViewSafeAreaLayoutGuide",
layoutFrame = {{0, 44}, {375, 734}}, owningView = <UIView: 0x7f888240c3b0; frame = (0 0; 375 812); 
autoresize = W+H; layer = <CALayer: 0x608000431ec0>>>

根據(jù)打印結(jié)果safeAreaInsets.top=44薛匪,剛好是蘋果規(guī)定的適配iPhone X要避開的劉海的距離,
safeAreaInsets.bottom=34脓鹃,剛好是底部的home虛擬手勢區(qū)域的高度逸尖。

橫屏旋轉(zhuǎn)測試:

進行橫屏切換后:


lascape-1.png

再次點擊控制器的view觸發(fā)touchesBegan進行打印驗證,打印結(jié)果:

safeAreaInsets {0, 44, 21, 44}
safeAreaGuide <UILayoutGuide: 0x6080009a3c60 - "UIViewSafeAreaLayoutGuide", layoutFrame =
{{44, 0}, {724, 354}}, owningView = <UIView: 0x7f888240c3b0; frame = (0 0; 812 375); autoresize =
W+H; layer = <CALayer: 0x608000431ec0>>>

旋轉(zhuǎn)之后瘸右,safeAreaInsets.left距離劉海隔離區(qū)域依然是44娇跟,底部的home虛擬手勢區(qū)域變成了21。
由此證明太颤,系統(tǒng)也把屏幕旋轉(zhuǎn)的情況也自動計算好了苞俘。

總結(jié)

這次為了適配iPhone X,個人從一開始看到iOS11的Safe Area這個概念龄章,追溯到iOS7 topLayoutGuide/bottomLayoutGuide吃谣,從頭開始學(xué)習(xí)乞封,受益匪淺。也體會到了蘋果工程師針對UI適配岗憋,面向開發(fā)者進行的一系列探索肃晚,以及優(yōu)化的心路歷程。也看到了他們?nèi)绾螌⒁粋€好的思路仔戈,面對當(dāng)前的需求變化陷揪,進行合理的擴展,設(shè)計出的靈活可擴展的API:
1.iOS7: topLayoutGuide/bottomLayoutGuide杂穷,利用一個虛擬的view初步解決導(dǎo)航欄悍缠,tabbar的隔離問題。

2.iOS9:有了虛擬view的思路,又考慮能不能去除top/bottom概念的局限性耐量,讓開發(fā)者都可以靈活自定義這個隔離區(qū)域飞蚓,又提供一些更方便簡潔易懂的API方便進行代碼自動布局,于是有了UILayoutGuide這個類廊蜒。趴拧。

3.兩年后的iOS11,有了iPhone X,蘋果工程師順理成章的將他們在iOS9的探索成果利用起來山叮,他們自定義了一個UILayoutGuide著榴,給開發(fā)者提供了一個只讀屬性的safeAreaLayoutGuide,并且提出安全區(qū)域的概念屁倔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脑又,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锐借,更是在濱河造成了極大的恐慌问麸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞翔,死亡現(xiàn)場離奇詭異严卖,居然都是意外死亡,警方通過查閱死者的電腦和手機布轿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門哮笆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汰扭,你說我怎么就攤上這事稠肘。” “怎么了东且?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵启具,是天一觀的道長本讥。 經(jīng)常有香客問我珊泳,道長鲁冯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任色查,我火速辦了婚禮薯演,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秧了。我一直安慰自己跨扮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布验毡。 她就那樣靜靜地躺著衡创,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晶通。 梳的紋絲不亂的頭發(fā)上璃氢,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音狮辽,去河邊找鬼一也。 笑死,一個胖子當(dāng)著我的面吹牛喉脖,可吹牛的內(nèi)容都是我干的椰苟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼树叽,長吁一口氣:“原來是場噩夢啊……” “哼舆蝴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起题诵,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤须误,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仇轻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體京痢,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年篷店,在試婚紗的時候發(fā)現(xiàn)自己被綠了祭椰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡疲陕,死狀恐怖方淤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹄殃,我是刑警寧澤携茂,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站诅岩,受9級特大地震影響讳苦,放射性物質(zhì)發(fā)生泄漏带膜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鸳谜、第九天 我趴在偏房一處隱蔽的房頂上張望膝藕。 院中可真熱鬧,春花似錦咐扭、人聲如沸芭挽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袜爪。三九已至,卻和暖如春薛闪,著一層夾襖步出監(jiān)牢的瞬間饿敲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工逛绵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怀各,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓术浪,卻偏偏與公主長得像瓢对,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胰苏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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