iOS7 topLayoutGuide/bottomLayoutGuide

创建一个叫做LayoutGuideStudy的工程,我们打开看一下Main.storyboard:

storyboard-top_bottom_layoutGuide.png

可以看到View Controller下面出现topLayoutGuide/bottomLayoutGuide这两个东西,并且和Controller的View处于同一层级。
并且在UIViewController头文件里面,这两个属性是id类型遵守一个UILayoutSupport协议并且是只读的属性:

  1. // These objects may be used as layout items in the NSLayoutConstraint API
  2. @property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide NS_AVAILABLE_IOS(7_0);
  3. @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

,再添加完宽高约束,最后约束结果:

constraint_result.png

直接build看结果:

run-result.png

可以看出top约束基于系统提供的topLayoutGuide,系统会自动为这个view避开顶部状态栏。
我们在ViewController里面打印红色view:

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

看到红色view的y值就是20.刚好是状态栏的高度。由此看出Top Layout Guide的作用就是在进行自动布局的时候,帮助开发者隔离出状态栏的空间。那么我们再看看导航控制器(顶部出现导航栏)的情况:

navagation-result.png

build看结果:

run_nav_result.png

Top Layout Guide同样自动帮助隔离出状态栏+导航栏。
在ViewController里面打印黄色view:

  1. <UIView: 0x7fb04fe08040; frame = (0 64; 240 128); autoresize = RM+BM; layer = <CALayer: 0x61800003ef60>>

看到黄色view的y值就是64.刚好是状态栏+导航栏的高度。

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

tabbar-bottomGuide.png

扒一扒topLayoutGuide/bottomLayoutGuide对象:

在UIViewController的viewDidLayoutSubviews方法打印

  1. - (void)viewDidLayoutSubviews
  2. {
  3. [super viewDidLayoutSubviews];
  4. NSLog(@"topLayoutGuide-%@",self.topLayoutGuide);
  5. NSLog(@"bottomLayoutGuide-%@",self.bottomLayoutGuide);
  6. }
  7. 打印结果:
  8. topLayoutGuide-
  9. <_UILayoutGuide: 0x7fd7cce0c350; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x61000003f2c0>>
  10. bottomLayoutGuide-
  11. <_UILayoutGuide: 0x7fd7cce0d6b0; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x610000221620>>

这个是_UILayoutGuide类型的私有对象,看起来里面有frame,hidden,layer属性,感觉十分像UIView啊,那我们就验证一下:

  1. if ([self.topLayoutGuide isKindOfClass:[UIView class]]) {
  2. NSLog(@"topLayoutGuide is an UIView");
  3. }
  4. if ([self.bottomLayoutGuide isKindOfClass:[UIView class]]) {
  5. NSLog(@"bottomLayoutGuide is an UIView");
  6. }
  7. 打印结果:
  8. topLayoutGuide is an UIView
  9. bottomLayoutGuide is an UIView

得到结论就是topLayoutGuide/bottomLayoutGuide其实是一个UIView类型的对象。
我们再打印一下UIViewController的view的subviews:

  1. - (void)viewDidLayoutSubviews
  2. {
  3. [super viewDidLayoutSubviews];
  4. NSLog(@"viewController view subViews %@",self.view.subviews);
  5. }
  6. 打印结果:
  7. viewController view subViews (
  8. "<UIView: 0x7ffc774035b0; frame = (0 64; 240 128); autoresize = RM+BM; layer = <CALayer: 0x60800002c720>>",
  9. "<_UILayoutGuide: 0x7ffc7740ae10; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x60800002c480>>",
  10. "<_UILayoutGuide: 0x7ffc7740b1e0; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x60800002b820>>"
  11. )

这样就明了了!
总结一下:
topLayoutGuide/bottomLayoutGuide其实是作为虚拟的占坑view,用于在自动布局的时候帮助开发者避开顶部的状态栏,导航栏以及底部的tabbar等

iOS9 UILayoutGuide

iOS9开始,苹果新增加了一个UILayoutGuide的类,看看苹果官方对它的解释:

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

大概意思是UILayoutGuide用于提供一个矩形区域可以用Auto Layout来定制一些约束特性,作为一个虚拟的view使用。
我想大概是苹果的工程师觉得以前的topLayoutGuide/bottomLayoutGuide提供虚拟占坑view,隔离导航栏/tabber的思想不错,进而有了启发,能不能让整个LayoutGuide变得更灵活,让开发者能够自由定制,于是这个UILayoutGuide类就设计出来了。。

那么如何自由定制一个UILayoutGuide,我们看看这个类的几个属性:

  1. @property(readonly, strong) NSLayoutXAxisAnchor *leadingAnchor;
  2. @property(readonly, strong) NSLayoutXAxisAnchor *trailingAnchor;
  3. @property(readonly, strong) NSLayoutXAxisAnchor *leftAnchor;
  4. @property(readonly, strong) NSLayoutXAxisAnchor *rightAnchor;
  5. @property(readonly, strong) NSLayoutYAxisAnchor *topAnchor;
  6. @property(readonly, strong) NSLayoutYAxisAnchor *bottomAnchor;
  7. @property(readonly, strong) NSLayoutDimension *widthAnchor;
  8. @property(readonly, strong) NSLayoutDimension *heightAnchor;
  9. @property(readonly, strong) NSLayoutXAxisAnchor *centerXAnchor;
  10. @property(readonly, strong) NSLayoutYAxisAnchor *centerYAnchor;

NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension这几个类也是跟随UILayoutGuide在
iOS9以后新增的,即便很陌生,但我们看上面UILayoutGuide的几个属性里面leading,trailing,top,bottom,center等熟悉的字眼,就能明白这些属性就是用于给UILayoutGuide对象增加布局约束的。

我们在看UIView里面新增的一个分类:

  1. @interface UIView (UIViewLayoutConstraintCreation)
  2. @property(readonly, strong) NSLayoutXAxisAnchor *leadingAnchor NS_AVAILABLE_IOS(9_0);
  3. @property(readonly, strong) NSLayoutXAxisAnchor *trailingAnchor NS_AVAILABLE_IOS(9_0);
  4. @property(readonly, strong) NSLayoutXAxisAnchor *leftAnchor NS_AVAILABLE_IOS(9_0);
  5. @property(readonly, strong) NSLayoutXAxisAnchor *rightAnchor NS_AVAILABLE_IOS(9_0);
  6. @property(readonly, strong) NSLayoutYAxisAnchor *topAnchor NS_AVAILABLE_IOS(9_0);
  7. @property(readonly, strong) NSLayoutYAxisAnchor *bottomAnchor NS_AVAILABLE_IOS(9_0);
  8. @property(readonly, strong) NSLayoutDimension *widthAnchor NS_AVAILABLE_IOS(9_0);
  9. @property(readonly, strong) NSLayoutDimension *heightAnchor NS_AVAILABLE_IOS(9_0);
  10. @property(readonly, strong) NSLayoutXAxisAnchor *centerXAnchor NS_AVAILABLE_IOS(9_0);
  11. @property(readonly, strong) NSLayoutYAxisAnchor *centerYAnchor NS_AVAILABLE_IOS(9_0);
  12. @property(readonly, strong) NSLayoutYAxisAnchor *firstBaselineAnchor NS_AVAILABLE_IOS(9_0);
  13. @property(readonly, strong) NSLayoutYAxisAnchor *lastBaselineAnchor NS_AVAILABLE_IOS(9_0);
  14. @end

也是跟UILayoutGuide一样的提供了一致的属性。这就说明了UILayoutGuide是可以跟UIView进行Auto Layout的约束交互的。

我们用一个例子说明:

创建一个UILayoutGuide,约束它距离控制器view的顶部64,左边0,宽250,高200,于是在viewDidLoad方法里面的代码:

  1. // 创建
  2. UILayoutGuide *layoutGuide = [[UILayoutGuide alloc] init];
  3. // 需要使用UIView的addLayoutGuide方法添加新建的layoutGuide
  4. [self.view addLayoutGuide:layoutGuide];
  5. // 正式的约束代码
  6. [layoutGuide.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:64].active = YES;
  7. [layoutGuide.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  8. [layoutGuide.widthAnchor constraintEqualToConstant:250].active = YES;
  9. [layoutGuide.heightAnchor constraintEqualToConstant:200].active = YES;

这样约束代码明显比使用NSLayoutConstraint简洁多了。

接着,我们再创建一个紫色view,基于这个创建的layoutGuide进行约束,紫色view顶部距离上述layoutGuide底部20,和layoutGuide左对齐,宽和高和layoutGuide保持一致

  1. UIView *viewBaseLayoutGuide = [[UIView alloc] init];
  2. viewBaseLayoutGuide.translatesAutoresizingMaskIntoConstraints = NO;
  3. viewBaseLayoutGuide.backgroundColor = [UIColor purpleColor];
  4. [self.view addSubview:viewBaseLayoutGuide];
  5. [viewBaseLayoutGuide.topAnchor constraintEqualToAnchor:layoutGuide.bottomAnchor constant:20].active = YES;
  6. [viewBaseLayoutGuide.leadingAnchor constraintEqualToAnchor:layoutGuide.leadingAnchor].active = YES;
  7. [viewBaseLayoutGuide.widthAnchor constraintEqualToAnchor:layoutGuide.widthAnchor].active = YES;
  8. [viewBaseLayoutGuide.heightAnchor constraintEqualToAnchor:layoutGuide.heightAnchor].active = YES;

运行程序的结果:

layoutGuide-test.png

iOS11 Safe Area / safeAreaLayoutGuide

iOS11又引入了一个Safe Area(安全区域)的概念,苹果建议在这个安全区域内放置UI控件。这个安全区域的范围其实就是整个屏幕隔离出状态栏,导航栏,tabar,以及iPhone X顶部刘海,底部虚拟home手势区域的范围。
从这个介绍可以看得出,所谓的Safe Area其实也就是升级版本的topLayoutGuide/bottomLayoutGuide,以前只能限制top/bottom的Layout,现在更加强大了。
再看一下UIViewController头文件:(用xcode9以上版本打开):

  1. @property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));
  2. @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

验证使用safeArea的效果:

如上图所示,我们基于storyboard提供的控制器view的safeArea区域对红色的view进行约束:顶部距离
安全区域0,左边距离安全区域0,宽240,高180:

constraint-safeArea.png

在iPhone 8上运行结果:

run-safeArea.png

为了验证Safe Area在竖屏iPhone X底部起到的隔离作用,又增加了一个棕色的view:左边距离安全区域0,底部距离安全区域0,宽240,高180:

iPhone X-bottom.png

在iPhone X上运行结果:

iPhone X-SafeArea.png

利用安全区域进行Auto Layout布局,分别在iPhone 8,iPhone X上以及避开了状态栏/刘海/底部的home虚拟手势区域,使得开发者不用关心状态栏以及适配iPhone X避开刘海的高度,只需要安安心心的苹果指定的这个安全区域放置子控件,布局就可以了。

UIView 的safeAreaLayoutGuide属性

查看UIView在iOS11上关于Safe Area新增的两个属性:

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

很明显这个只读的safeAreaLayoutGuide属性是系统自动创建的,可以让开发者用代码进行基于安全区域进行自动布局。
点击控制器的view触发touchesBegan进行打印验证:

  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  2. {
  3. NSLog(@"safeAreaInsets %@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));
  4. NSLog(@"safeAreaGuide %@",self.view.safeAreaLayoutGuide);
  5. }
  6. 打印结果:
  7. safeAreaInsets {44, 0, 34, 0}
  8. safeAreaGuide <UILayoutGuide: 0x6080009a3c60 - "UIViewSafeAreaLayoutGuide",
  9. layoutFrame = {{0, 44}, {375, 734}}, owningView = <UIView: 0x7f888240c3b0; frame = (0 0; 375 812);
  10. autoresize = W+H; layer = <CALayer: 0x608000431ec0>>>

根据打印结果safeAreaInsets.top=44,刚好是苹果规定的适配iPhone X要避开的刘海的距离,
safeAreaInsets.bottom=34,刚好是底部的home虚拟手势区域的高度。

横屏旋转测试:

进行横屏切换后:

lascape-1.png

再次点击控制器的view触发touchesBegan进行打印验证,打印结果:

  1. safeAreaInsets {0, 44, 21, 44}
  2. safeAreaGuide <UILayoutGuide: 0x6080009a3c60 - "UIViewSafeAreaLayoutGuide", layoutFrame =
  3. {{44, 0}, {724, 354}}, owningView = <UIView: 0x7f888240c3b0; frame = (0 0; 812 375); autoresize =
  4. 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
    }
}

总结

这次为了适配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,并且提出安全区域的概念。

 
参考链接:
  1.  

iOS开发-LayoutGuide(从top/bottom LayoutGuide到Safe Area)的更多相关文章

  1. IOS开发基础知识--碎片40

    1:Masonry快速查看报错小技巧 self.statusLabel = [UILabel new]; [self.contentView addSubview:self.statusLabel]; ...

  2. iOS开发-Masonry简易教程

    关于iOS布局自动iPhone6之后就是AutoLayOut,AutoLayOut固然非常好用,不过有时候我们需要在页面手动进行页面布局,VFL算是一种选择,如果对VFL不是很熟悉可以参考iOS开发- ...

  3. iOS开发UI篇—UIScrollView控件介绍

    iOS开发UI篇—UIScrollView控件介绍 一.知识点简单介绍 1.UIScrollView控件是什么? (1)移动设备的屏幕⼤大⼩小是极其有限的,因此直接展⽰示在⽤用户眼前的内容也相当有限 ...

  4. iOS开发UIScrollView的底层实现

    起始 做开发也有一段时间了,经历了第一次完成项目的激动,也经历了天天调用系统的API的枯燥,于是就有了探索底层实现的想法. 关于scrollView的思考 在iOS开发中我们会大量用到scrollVi ...

  5. IOS开发-几种截屏方法

    IOS开发-几种截屏方法 1.        UIGraphicsBeginImageContextWithOptions(pageView.page.bounds.size, YES, zoomSc ...

  6. iOS开发——高级UI&带你玩转UITableView

    带你玩装UITableView 在实际iOS开发中UITableView是使用最多,也是最重要的一个控件,如果你不会用它,那别说什么大神了,菜鸟都不如. 其实关于UItableView事非常简单的,实 ...

  7. iOS开发通过代码方式使用AutoLayout (NSLayoutConstraint + Masonry)

    iOS开发通过代码方式使用AutoLayout (NSLayoutConstraint + Masonry) 随着iPhone6/6+设备的上市,如何让手头上的APP适配多种机型多种屏幕尺寸变得尤为迫 ...

  8. iOS开发tips-神奇的UITableView

    概述 UITableView是iOS开发中使用频率最高的UI控件,在前面的文章中对于UITableView的具体用法有详细的描述,今天主要看一些UITableView开发中的常见一些坑,这些坑或许不深 ...

  9. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

随机推荐

  1. 【前端】Vue2全家桶案例《看漫画》之四、漫画页

    转载请注明出处:http://www.cnblogs.com/shamoyuu/p/vue_vux_app_4.html 项目github地址:https://github.com/shamoyuu/ ...

  2. arm-linux-gcc: Command not found 问题解析 .

    问题: sudo tar jxvf cross-2.95.3.tar.bz2 export PATH=$PATH:/usr/local/arm/2.95.3/bin 使用arm-linux-gcc – ...

  3. [javascript]一段焦点图的js代码

    <html> <head> <meta name="name" content="content"charset="ut ...

  4. 基于LVDS/M-LVDS的数据通信

    现在有两种方案:一种基于 M-LVDS (基于总线的多节点通信) ,有其 特定的电气要求:另外一种是基于 LVDS 的点到点通信,具体说明如 下: 基于 M-LVDS 的总线通信: 基于 M-LVDS ...

  5. hi3531的pcie控制器使能

    1. 关闭PCIe 控制器: 通过向系统控制寄存器PERIPHCTRL30[pcie0_app_ltssm_enabl]写入0 关闭PCIe0 控制 器. 通过向系统控制寄存器PERIPHCTRL77 ...

  6. Linux显示指定区块大小为1024字节

    Linux显示指定区块大小为1024字节 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ df -k 文件系统 1K-blocks 已用 可用 已用% 挂载点 ...

  7. Unity3d开发中与oc交互之类型转换

    对于非科班出身的程序来说,在没有学过C和OC的情况,用unity开发iOS相关的功能,是非常痛苦的.简单写一下自己遇到的,并且没有百度到的坑. 1.C#给OC传递字典 一般流程是,C#调用C,C调用O ...

  8. MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决 转

    MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决   在多线程设计中,许多人为了省事,会将对话框类或其它类的指针传给工作线程,而在工作线程中调用该类的成员函数或成员变量等等. ...

  9. MyEclipse开发平台下如何将新建的JSP页面的默认编码格式设置为UTF-8--JSP

    新建的JSP页面原始的编码格式是ISO-8859-1(测试的MyEclipse版本为2014),它是不支持中文,在预览JSP页面时会出现乱码的现象.当然自己手动改一下编码格式就好了,但是那太过麻烦,每 ...

  10. Python基础__函数

    本节将进入函数的介绍,函数是Python基础中最精彩的部分之一,接下来将对函数做详细介绍.函数 函数就是对代码进行一个封装.把实现某一功能的代码进行封装到一起.下次需要使用时不需要进行编写代码直接调用 ...