iOS開發-LayoutGuide(從top/bottom LayoutGuide到Safe Area)

来源:http://www.cnblogs.com/lurenq/archive/2017/11/17/7849813.html
-Advertisement-
Play Games

iOS7 topLayoutGuide/bottomLayoutGuide 創建一個叫做LayoutGuideStudy的工程,我們打開看一下Main.storyboard: storyboard-top_bottom_layoutGuide.png 可以看到View Controller下麵出現t ...


iOS7 topLayoutGuide/bottomLayoutGuide

創建一個叫做LayoutGuideStudy的工程,我們打開看一下Main.storyboard:


storyboard-top_bottom_layoutGuide.png
storyboard-top_bottom_layoutGuide.png

可以看到View Controller下麵出現topLayoutGuide/bottomLayoutGuide這兩個東西,並且和Controller的View處於同一層級。
並且在UIViewController頭文件裡面,這兩個屬性是id類型遵守一個UILayoutSupport協議並且是只讀的屬性:

// 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是系統自動創建並管理的,這也解釋了剛剛我們創建的工程裡面Main.storyboard為什麼會自動出現topLayoutGuide/bottomLayoutGuide。

看看有什麼用

我們拖拽一個紅色的view到Controller的view里,添加約束的時候,註意到是右下角的約束設定框,關於頂部約束的基準view下拉選擇,xcode預設勾選了Top Layout Guide:


autoLayout-useLayoutGuide.png
autoLayout-useLayoutGuide.png

,再添加完寬高約束,最後約束結果:


constraint_result.png
constraint_result.png
直接build看結果:
run-result.png
run-result.png

可以看出top約束基於系統提供的topLayoutGuide,系統會自動為這個view避開頂部狀態欄。
我們在ViewController裡面列印紅色view:

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

看到紅色view的y值就是20.剛好是狀態欄的高度。由此看出Top Layout Guide的作用就是在進行自動佈局的時候,幫助開發者隔離出狀態欄的空間。那麼我們再看看導航控制器(頂部出現導航欄)的情況:


navagation-result.png
navagation-result.png

build看結果:


run_nav_result.png
run_nav_result.png
Top Layout Guide同樣自動幫助隔離出狀態欄+導航欄。
在ViewController裡面列印黃色view:
<UIView: 0x7fb04fe08040; frame = (0 64; 240 128); autoresize = RM+BM; layer = <CALayer: 0x61800003ef60>>

看到黃色view的y值就是64.剛好是狀態欄+導航欄的高度。

同理,bottomLayoutGuide就是用於在TabbarController裡面隔離底部的tabbar:


tabbar-bottomGuide.png
tabbar-bottomGuide.png

扒一扒topLayoutGuide/bottomLayoutGuide對象:

在UIViewController的viewDidLayoutSubviews方法列印

- (void)viewDidLayoutSubviews
 {
    [super viewDidLayoutSubviews];
    NSLog(@"topLayoutGuide-%@",self.topLayoutGuide);
    NSLog(@"bottomLayoutGuide-%@",self.bottomLayoutGuide);
}
列印結果:
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");
}
列印結果:
topLayoutGuide is an UIView
bottomLayoutGuide is an UIView

得到結論就是topLayoutGuide/bottomLayoutGuide其實是一個UIView類型的對象。
我們再列印一下UIViewController的view的subviews:

- (void)viewDidLayoutSubviews
 {
    [super viewDidLayoutSubviews];
     NSLog(@"viewController view subViews %@",self.view.subviews);
}
列印結果:
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>>"
)

這樣就明瞭了!
總結一下:
topLayoutGuide/bottomLayoutGuide其實是作為虛擬的占坑view,用於在自動佈局的時候幫助開發者避開頂部的狀態欄,導航欄以及底部的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用於提供一個矩形區域可以用Auto Layout來定製一些約束特性,作為一個虛擬的view使用。
我想大概是蘋果的工程師覺得以前的topLayoutGuide/bottomLayoutGuide提供虛擬占坑view,隔離導航欄/tabber的思想不錯,進而有了啟發,能不能讓整個LayoutGuide變得更靈活,讓開發者能夠自由定製,於是這個UILayoutGuide類就設計出來了。。

那麼如何自由定製一個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的約束交互的。

我們用一個例子說明:

創建一個UILayoutGuide,約束它距離控制器view的頂部64,左邊0,寬250,高200,於是在viewDidLoad方法裡面的代碼:

// 創建
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簡潔多了。

接著,我們再創建一個紫色view,基於這個創建的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;

運行程式的結果:


layoutGuide-test.png
layoutGuide-test.png

iOS11 Safe Area / safeAreaLayoutGuide

iOS11又引入了一個Safe Area(安全區域)的概念,蘋果建議在這個安全區域內放置UI控制項。這個安全區域的範圍其實就是整個屏幕隔離出狀態欄,導航欄,tabar,以及iPhone X頂部劉海,底部虛擬home手勢區域的範圍。
從這個介紹可以看得出,所謂的Safe Area其實也就是升級版本的topLayoutGuide/bottomLayoutGuide,以前只能限制top/bottom的Layout,現在更加強大了。
再看一下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已經過期,推薦使用UIView 的safeAreaLayoutGuide屬性(safeAreaLayoutGuide稍後會介紹)。

另外用xcode9以上版本創建工程的時候,Main.storyboard會預設選擇Use Safe Area Layout Guides,控制器view下麵會出現safe area:

xcode9_safeArea.png
xcode9_safeArea.png

驗證使用safeArea的效果:

如上圖所示,我們基於storyboard提供的控制器view的safeArea區域對紅色的view進行約束:頂部距離
安全區域0,左邊距離安全區域0,寬240,高180:

 

constraint-safeArea.png
constraint-safeArea.png
在iPhone 8上運行結果:
run-safeArea.png
run-safeArea.png

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


iPhone X-bottom.png
iPhone X-bottom.png

在iPhone X上運行結果:

iPhone X-SafeArea.png
iPhone X-SafeArea.png

利用安全區域進行Auto Layout佈局,分別在iPhone 8,iPhone X上以及避開了狀態欄/劉海/底部的home虛擬手勢區域,使得開發者不用關心狀態欄以及適配iPhone X避開劉海的高度,只需要安安心心的蘋果指定的這個安全區域放置子控制項,佈局就可以了。

UIView 的safeAreaLayoutGuide屬性

查看UIView在iOS11上關於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屬性是系統自動創建的,可以讓開發者用代碼進行基於安全區域進行自動佈局。
點擊控制器的view觸發touchesBegan進行列印驗證:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"safeAreaInsets %@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));
    NSLog(@"safeAreaGuide %@",self.view.safeAreaLayoutGuide);
}
列印結果:
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>>>

根據列印結果safeAreaInsets.top=44,剛好是蘋果規定的適配iPhone X要避開的劉海的距離,
safeAreaInsets.bottom=34,剛好是底部的home虛擬手勢區域的高度。

橫屏旋轉測試:

進行橫屏切換後:


lascape-1.png
lascape-1.png

再次點擊控制器的view觸發touchesBegan進行列印驗證,列印結果:

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>>>

旋轉之後,safeAreaInsets.left距離劉海隔離區域依然是44,底部的home虛擬手勢區域變成了21。
由此證明,系統也把屏幕旋轉的情況也自動計算好了。

 

 

  • iOS 11.0之後系統新增安全區域變化方法

1 2 3 4 UIViewController中新增: - (void)viewSafeAreaInsetsDidChange; UIView中新增: - (void)viewSafeAreaInsetsDidChange;
  • 通過安全區域變化來改變視圖的位置

如果屏幕旋轉,相應的安全區域也會變化,所以不比擔心。![safearea.gif](http://upload-

1 2 3 4 5 6 7 8 images.jianshu.io/upload_images/1186277-ab32b1be56378531.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - (void)viewSafeAreaInsetsDidChange {     [super viewSafeAreaInsetsDidChange];           NSLog(@"viewSafeAreaInsetsDidChange-%@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));           [self updateOrientation]; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /**  更新屏幕safearea frame  */ - (void)updateOrientation {     if (@available(iOS 11.0, *)) {         CGRect frame = self.customerView.frame;         frame.origin.x = self.view.safeAreaInsets.left;         frame.size.width = self.view.frame.size.width - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right;         frame.size.height = self.view.frame.size.height - self.view.safeAreaInsets.bottom;         self.customerView.frame = frame;     else {         // Fallback on earlier versions     } }

1186277-ec4ce8180ff0b6ce.gif

 

 

總結

這次為了適配iPhone X,個人從一開始看到iOS11的Safe Area這個概念,追溯到iOS7 topLayoutGuide/bottomLayoutGuide,從頭開始學習,受益匪淺。也體會到了蘋果工程師針對UI適配,面向開發者進行的一系列探索,以及優化的心路歷程。也看到了他們如何將一個好的思路,面對當前的需求變化,進行合理的擴展,設計出的靈活可擴展的API:
1.iOS7: topLayoutGuide/bottomLayoutGuide,利用一個虛擬的view初步解決導航欄,tabbar的隔離問題。

2.iOS9:有了虛擬view的思路,又考慮能不能去除top/bottom概念的局限性,讓開發者都可以靈活自定義這個隔離區域,又提供一些更方便簡潔易懂的API方便進行代碼自動佈局,於是有了UILayoutGuide這個類。。

3.兩年後的iOS11,有了iPhone X,蘋果工程師順理成章的將他們在iOS9的探索成果利用起來,他們自定義了一個UILayoutGuide,給開發者提供了一個只讀屬性的safeAreaLayoutGuide,並且提出安全區域的概念。

  參考鏈接: https://stackoverflow.com/questions/46184197/ios-11-safe-area-layout-guide-backwards-compatibility


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 列表一直是展示數據的一個重要方式,在手機端的列表展示又和PC端展示不同,畢竟手機端主要靠滑。之前手機端之前一直使用的 ,但是 本身其實有很多相容性 ,想改動一下需求也很不容易,可以看我之前寫的這一文章 "IScroll那些事——內容不足時下拉刷新" (這裡並不是說 不好,裡面對手機、瀏覽器 ...
  • 代碼如下: 原因:跨頁面操作涉及域的概念(origin),錯誤的意思是:未捕獲的安全錯誤:阻止了一個域為null的frame頁面訪問另一個域為null的頁面。代碼運行時在本地直接用瀏覽器打開的,地址欄是file:///的頁面,只需改為localhost訪問就行。 ...
  • ...
  • 要成為高產、高效的Web開發者,這需要我們做很多工作,來提高我們的工作方式,以及改善我們的勞動成果。 下麵是10個提高效率的步驟,雖然不能保證解決你在開發中的所有問題,但至少是非常實用的,可以簡化你的Web開發流程,使開發的每一個環節快速、流暢。還可以幫助你節省大量的時間,以便開發更多、更好的項目。 ...
  • 本文以一個小例子簡單介紹微信小程式開發的相關內容,希望共同學習進步。 ...
  • 今天寫登錄註冊頁面,點擊登錄頁面的“註冊”按鈕後軟體突然崩潰,直接閃退,因為是新手,只能去網上搜。雖然網上解決方法眾多,但也沒找到可行的。想起來可以看Logcat,馬上重新運行應用,查看崩潰時的日誌,發現日誌比較多,還都不認識,只好又複製日誌上網搜索,無果。突發奇想,把註冊頁面有關Toolbar的內 ...
  • 1.ViewController .m 頭部代理 代理方法 2.KKViewController(目標ViewController) 新建一個KKViewController .h .m 頭部代理 手勢代理方法 效果圖 ...
  • RecyclerView做的一個輪播效果,適配器有視圖緩存,避免了一些記憶體問題 首先是藉助 PagerSnapHelper 讓RecyclerView每次只滑動一個,然後添加一個指示器,這裡指示器是動態生成的,自己做了個簡單的view 很簡單的一個效果,直接上代碼 public class Imag ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...