怎样创建"ios top layout guidee"的顶级约束

iOS开发之Auto Layout入门
随着iPhone6与iOS8的临近,适配的问题讲更加复杂,最近学习了一下Auto Layout的使用,与大家分享。
什么是Auto Layout?
Auto Layout是iOS6发布后引入的一个全新的布局特性,其目的是弥补以往Autoresizing在布局方面的不足之处,以及未来面对更多尺寸适配时界面布局可以更好的适应。
为什么要用Auto Layout?
Autolayout能解决不同屏幕(iPhone4,iPhone5,iPad...)之间的适配问题。
在iPhone4时代开发者只需要适配一种屏幕尺寸,相比与阵营的相对布局,iOS开发者们最长用的做法是使用绝对布局,坐标和大小只要写死就ok了。随后iPhone5出了,对于两种屏幕尺寸,就需要考虑一个新的问题,屏幕适配。苹果其实很早就考虑到了这一点Autoresizing技术,诚然Autoresizing有所不足,苹果在iOS6发布后引入了Autolayout特性,适应更广泛场景下的布局需求。当然了iPhone5由于和iPhone4在屏幕宽度上一致,即便不用上这些技术适配起来也不麻烦(笔者再之前也只用到了Autoresizing),不过在iPhone6即将推出,即将面临更复杂的屏幕适配时,Auto
Layout能帮助我们很好地解决这个问题,此外也能解决横屏竖屏切换,iPad的适配问题。
下面是本文事例代码在横竖屏切换下的效果:
如何使用Auto Layout?
Auto Layout的基本概念
Auto Layout的核心是约束(constraint),通过对view的约束(view的大小,view与view之间的关系)使得view能够自己计算出尺寸和坐标。
Visual Format Language,在Auto Layout中使用的形象描述约束的一种语言规则。
Auto Layout的使用还是比较复杂的,一开始用可能会感觉云里雾里的,用的多了习惯VFL的写法之后就会感觉到它的方便了。
在代码中使用Auto Layout
- (void)viewDidLoad
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
UIView *viewTopLeft = [[UIView alloc] init];
UIView *viewTopRight = [[UIView alloc] init];
UIView *viewBottom = [[UIView alloc] init];
[viewTopLeft setBackgroundColor:[UIColor blueColor]];
[viewTopRight setBackgroundColor:[UIColor redColor]];
[viewBottom setBackgroundColor:[UIColor blackColor]];
//添加约束之前必须讲view添加到superview里
[self.view addSubview:viewTopRight];
[self.view addSubview:viewTopLeft];
[self.view addSubview:viewBottom];
//对于要使用Auto Layout的控件需要关闭Autoresizing
[viewTopLeft setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewTopRight setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewBottom setTranslatesAutoresizingMaskIntoConstraints:NO];
//dict和metrics相当于vfl中的名称与对象和数值的映射
NSDictionary *dict = NSDictionaryOfVariableBindings(viewTopLeft, viewTopRight, viewBottom);
//相当于这么写 NSDictionary *dict = @[@"viewTopLeft":viewTopLeft, @"viewTopRight":viewTopRight, @"viewBottom",viewBottom];不一定名称要与对象名一致
NSDictionary *metrics = @{@"pad":@10};
//水平关系(H:,可省略如vfl1),"|"相当与superview,"-"是连接符,表示两者间的间距也可以没有表示无间距
//转化正自然语言的描述就是:superview的左边界间隔pad距离是viewTopLeft(宽度与viewTopRight相等)再间隔默认距离是viewTopRight再间隔10的距离是superview的右边界。
NSString *vfl0 = @"H:|-pad-[viewTopLeft(==viewTopRight)]-[viewTopRight]-10-|";
NSString *vfl1 = @"|[viewBottom]|";
//垂直关系(V:)
NSString *vfl2 = @"V:|-[viewTopLeft(==viewBottom)]-[viewBottom]-pad-|";
NSString *vfl3 = @"V:|-[viewTopRight]-[viewBottom]-pad-|";
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl0 options:0 metrics:metrics views:dict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl1 options:0 metrics:metrics views:dict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl2 options:0 metrics:metrics views:dict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vfl3 options:0 metrics:metrics views:dict]];
//不使用VFL
//viewTopLeft的leading与其superview的leading(左侧)对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeLeading multiplier:1 constant:-10]];
//viewTopLeft的top与其superview的top对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeTop multiplier:1 constant:-10]];
//viewTopRight的top与viewTopLeft的top对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewTopRight attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
//viewTopRight的leading与viewTopLeft的trailing(右侧)对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewTopRight attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeTrailing multiplier:1 constant:10]];
//viewTopRight的trailing与其superview的右侧对其
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewTopRight attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:-10]];
//viewTopRight的宽与viewTopLeft宽相等
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewTopRight attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
//viewTopRight的高与viewTopLeft高相等
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewTopLeft attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:viewTopRight attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
//viewBottom的top与viewTopRight的bottom对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewBottom attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:viewTopRight attribute:NSLayoutAttributeBottom multiplier:1 constant:10]];
//viewBottom的bottom与superview的bottom对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewBottom attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:-10]];
//viewBottom的leading与viewTopLeft的leading对齐
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewBottom attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeLeading multiplier:1 constant:0]];
//viewBottom的高与viewTopLeft的高相等
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewBottom attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:viewTopLeft attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
//viewBottom的宽与其superview的高相等
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:viewBottom attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:-20]];
//更新约束
[self.view setNeedsUpdateConstraints];
[self.view updateConstraintsIfNeeded];
1.约束必须有给定的size然后通过约束计算出各个view的size和位置,上面self.view的size是给定的,如果superview的size不固定则必须给定某个subview的size才能通过约束计算出其他的。
2.两个view之间的约束应该放在他们superview上,如果一个view是superview只要放在superview的那个上就行了。
2.约束的设计最好不要冲突,虽然可以设置优先级,但是容易出问题。
3.VFL方式的设置view的size时只有相等,小等或大等3种关系,没法像使用方法生成约束那样可以设置两个view的比例,所以在具体的应用中还是要两者结合起来使用。
在xib中使用Auto Layout
感觉在xib中使用autolayout还没有用代码来的方便,原理与代码上相同,这里就不多做描述了。
Auto Layout
ios8来了,屏幕更大,准备好使用 iOS Auto Layout了吗?
Autolayout及VFL经验分享使用Auto Layout简述 - 简书
下载简书移动应用
写了26604字,被19人关注,获得了16个喜欢
使用Auto Layout简述
翻译文章,感谢作者。
在4英寸的iPhone5发布和iOS7引入动态类型之后,使用Auto Layout 来创建程序已经很有必要了,而且它使用Auto Layout还能允许用户通过操作系统来改变字体的大小。Auto Layout在国际化的实现上也处理的很好。
1、Auto Layout是什么?
在iOS6中引入的Auto Layout,使你能够灵活的创建布局。相比于使用 autoresizingmasks 和手动布局的方式来布局程序的用户界面,使用Auto Layout将会是一个更好的选择。
你可以使用Auto Layout来给视图添加约束并且定义他们之间的关系。这个关系可以是视图和它的父视图之间的,和兄弟视图之间的关系,甚至是和它自己的关系。
相对于之间的直接明确的设置视图的frame,Auto Layout可以使用约束来设定两个视图之间的间距和相对位置。Auto Layout使用这些约束条件来计算用户视图上的元素在程序运行时的位置。
你必须给视图设置足够的约束条件以防止位置的模糊不清。另外需注意的是,设置过多的约束条件也会导致冲突甚至程序的崩溃。
在Xcode4中,当你在给视图添加了一个不完整的活着无效的约束条件,Interface Builder将会使用一个尽量不会产生影响的新约束条件来替换掉该约束条件。这回给开发者造成显著的问题。在Xcode5中,使用Auto Layout变得更加简单,当一个视图上的约束条件无效时,Xcode将会给你提示活着警告,而不是简单粗暴地强制改变你的约束条件。
虽然通过编程方式也是可以实现自动布局的,但是这篇教程是讲述了怎样通过Interface Builder来使用Auto Layout给界面布局。
2、Auto Layout 基本概念
为简单的示范一下Auto Layout能为你做些什么,我们将将会创建一个简单的实例项目,并给它的视图添加一些约束条件。创建一个新的Xcode项目,选择Single View Application模板,并且将设备类型选择为iPhone。
Xcode4.5及其之后的版本创建的故事板文件和XIB文件都是默认开启Auto Layout的。你也可以通过取消右侧File Inspector里面的Use Auto Layout选项来禁止使用Auto Layout。
由于Auto Layout只支持iOS6及以上版本,所以如果要支持iOS5或以下版本,就需要禁止Auto Layout。如果不需要的话,还是推荐使用Auto Layout的,因为这样能够使我们更快更容易的创建灵活的用户界面。
打开项目的主故事板,Main.storyboard,给View Controller Scene添加一个 UITextView 控件,具体位置如下图(译者注:其实位置什么的不要太在意,咱们只是为了测试学习,不要在这上面太费时间)。
这个textView还没有被添加任何的约束条件。当你运行程序时,textView的位置和它在Interface Builder中是一样的。当你把设备旋转到横屏模式时,textView距离左侧的距离和宽度依然是没有变化的。
在build的过程中,会给没有添加任何约束条件的视图自动添加视图当前位置的约束条件。给textView添加的约束条件有:一个左侧和顶部的约束条件来确保位置,还有一个宽度和高度的约束来确保textView的尺寸。
一旦你开始添加约束条件,你就必须确保你所添加的约束条件不会导致冲突。在下一部分,我们将会给textView添加一些约束条件以便在设备旋转情况下或程序在不同尺寸的屏幕上运行的情况下调节textView的位置和尺寸。
3、添加约束条件
有好几种方式能给一个视图添加布局的约束条件。
按住Control键并拖动
按住 Control键并从你将要添加约束条件的视图拖动到另一个视图上。当你松开鼠标时,一个可选的菜单将会出现。里面的选项取决于你所拖动的视图和方向。
实际举例说明一下,从textView拖动到视图控制器的视图的顶部,Xcode将会高亮所有的视图以表明所有的布局的约束条件。松开鼠标,菜单将会展示所有可以被添加到源视图-textView的约束条件。为了将textView放到视图控制器中视图的水平中间位置,我们在菜单中选择 Center Horizontally In Container。一个橘色的线会出现,用来代表你刚刚添加的布局约束条件。
Auto Layout 菜单
你可以使用Auto Layout 菜单来添加和编辑布局的约束条件。它位于Interface Builder的底部。
译者注:我用的Xcode7进行的测试,会与Xcode5有所不同,所以按照Xcode7来。
Stack 可以将选中的视图添加到一个 UIStackView中。
Align 创建使视图处于他的容器的中间的对齐约束条件。或者使两个视图的边缘对齐。
Pin 创建视图的间距约束条件。可以设置选中视图的高度和宽度。或者设定视图距离另外一个视图的距离。
Resolving Auto Layout Issues (译者注:在Xcode7中是Resolve Auto Layout Issues) 能够解决自动布局的问题。比如更新视图的frame,添加缺少的约束条件等。(译者注:update frames表示根据当前的约束条件改变视图的frame;update Contrainers表示根据当前的frame来设置视图的约束条件)
上面提到的每一个菜单选项都可以在Xcode的编辑菜单中找到。
添加约束条件
给textView添加布局约束条件,首先在Xcode中选中该视图,按住Control键,然后将鼠标从textView拖到控制器视图上。从出现的菜单中选择 Center Horizontally In Container 选项。这项操作添加了一个确保即便是在屏幕旋转时仍会使textView处于控制器视图中部的约束条件。
你可能已经注意到textView的轮廓是橘色的。这就表明textView的约束条件是无效的或不完整的。我们只定义了textView处于父视图的横向中心位置,但是并没有定义他的尺寸应该是多少。让我们再给textView添加一些约束条件知道线条颜色变为蓝色,这时就说明我们的约束条件已经是有效的了。
注意到即便是我们忽略这些警告不处理,在约束条件不完整的情况下程序也是可以运行的。然而,我们却不能够就这样发布我们的程序,因为我们不能保证我们的程序在不同的设备上和不同的方向上界面会变成什么样子。
选中textView,同样是按下Control键从textView拖动到控制器视图上并且选择 Top Space to Top Layout Guide(译者注:在Xcode7中为 Vertical Spacing to Top Layout Guide)。这将会设置一个textView的顶部和控制器视图顶部的距离的约束条件。
接下来,按下Control键从textView拖动到控制器视图上并且选择 Leading Space to Container (译者注:在Xcode7中为Leading Space to Container Margin),这就设置了textView的左侧和控制器视图的左侧的距离的约束条件。按下Control键从textView拖动到控制器视图上并且选择 Bottom Space to Bottom Layout Guide(译者注:在Xcode7中为 Vertical Spacing to Bottom Layout Guide),设置了textView顶部和控制器视图的顶部距离的约束条件。
这时候textView的轮廓线变为了蓝色,表明他的约束条件是有效且完整的。在iOS模拟器上运行程序并改变它的方向来查看结果。
注意到我们没有添加一个横向的约束条件来制定textView的右边距距离他的父视图的位置,这是因为我们指定了textView的左边位置和它在父视图中居中显示。有这些约束条件,Auto Layout系统就已经有足够的信息来正确的放置textView了。我们也可以忽略对齐的约束条件并通过设置四个方向的距离的约束条件来达到同样的效果。
这个例子已经向你展示了怎样给一个视图及其他的父视图之间设置约束条件。接下来我们将看一个怎样给并列视图设置约束条件的例子。
首先让我们先删掉视图上的UITextView控件。同时也会删掉它上面的所有约束条件。像下面那样在控制器视图上添加一个 textField,一个slider和一个segmented控件。
在没有添加任何约束条件的情况下运行程序,这三个视图在横屏状态下将会挨着他们的父视图的左侧显示。
然而,我们想让这三个视图能够下面展示的这样占据全部的横线宽度。textField会横向拉伸,slider也会横向拉伸来适应屏幕的宽度。而segmented却有一个固定的宽度。
选中textField,点击底部 Auto Layout菜单中的Pin 按钮。在菜单上部的 Spacing to nearest neighbor 区域,点击围绕着中间方块的上、右和左侧的线。这些线将会变红。下一步,点击底部的 Add 3 Constraints按钮来添加约束条件。
选中slider重复上面的步骤来设置上、左、右的约束条件。
对segmented也重复相同的操作,但是只添加上和右的约束条件。另外,选中With 选项之后在点击底部的 Add 3 Constraints按钮。因为我们想给他一个固定的宽度,使它在屏幕尺寸发生改变的时候不会拉伸。
4、定位Auto Layout问题
当Xcode给出以下关于约束条件的丢失或者无效的错误或警告时,哪些约束条件需要被添加或更新并不总是特别的清楚的。Xcode会在Document Outling中帮我们展示出哪些约束条件丢失了。
当一个布局无效或者不完整时,在Document Outline中会出现一个红色的箭头。点击箭头,在右侧将会出现一个展示哪个约束条件丢失或无效的窗口。这将会给我们如何修复布局提供一些线索。
在每一个错误或警告的右侧都有一个红色的圆(错误)或者黄色的三角形(警告)。点击这些错误或警告,将会出现一个带着建议的菜单的来帮助修复这些问题。
也可以使用 Resolve Auto Layout Isuues 来添加缺失的约束条件,重置视图的约束条件或者清除视图的约束条件。Xcode会自动帮你添加所选择视图的约束条件。这可以节约你的时间,但是需要注意的是这些约束条件可能并不会实现你所需要的效果。
Misplaced Views
如果你已经给一个视图添加了布局约束条件又去改变了它的尺寸和位置,Xcode会使用橘色的线来高亮以便说明视图当前的位置已经不是现在所展示的。
为解决这个问题,你可以删除布局条件并且进行重置,或者让Xcode帮你修复。你有两种选择可以修复一个错位的视图。
你可以通过Xcode的Editor菜单中的 Resolve Auto Layout Issues & Update Frames 来移动视图并且重新设置视图的大小来和它的约束条件相匹配。
或者也可以通过Xcode的Editor菜单中的 Resolve Auto Layout Issues & Update Constraints 来改变约束条件以便匹配视图的新尺寸和位置。
在上面的例子中,我们将会选择
Update Constraints 来更新约束条件来使视图达到新的尺寸和位置。
Auto Layout系统使我们可以更简单更快速的布局我们的界面。在Auto Layout被引入之前,开发者通过设置视图的frame和自适应是一件很难的工作。在Auto Layout的帮助下,这些困难都将不在了。
通过正确的设置一个视图的布局约束条件,在不同尺寸的屏幕和不同的设备方向上,他都会被自动的更新。Auto Layout另一个非常有用的地方是程序的本地化实现上。即便是同一个单词和语句,在不同的语言中,显示时会有不同的长度。这些在Auto Layout的帮助下都得到了很好的解决。
虽然这篇文章有点老,但是很多知识还是很有用的,我觉得用来简单的认识一下Auto Layout还是很有用的。最后,再次感谢原作者。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:如何做好IOS View的布局
这个命题貌似有点大,那就尽量将我理解的分享一下吧,首先说明一点,我是代码党,所以我所讲的都是代码布局。本文会围绕一些我们平常开发中常遇到的布局问题来进行叙述,包括以下几个方面:
如何布局UIViewController的view
childViewController的处理
Autolayout来布局
tableView管理
1.如何布局UIViewController的view
首先给出设计原则:
屏幕尺寸变化时能自适应,如不同尺寸设备,屏幕旋转,热点,电话等。
无论是否有navigationBar或tabBar都能够正常显示,即要考虑是否有这些bar的所有场景
尽量避免hard code间距,如20,44,49等
1.1 是否全屏
自从ios7扁平化设计以来,高斯模块是为你的应用增色的很好的工具,而为了更好的让navigationBar和tabBar实现高斯模糊的效果,最好让UIViewController能够全屏布局。我们在设计一个页面时,最好先确定好是否需要全屏布局,确定了这一点,我们就很简单的这样设置来决定是否全屏布局。
不需要全屏布局:
self.edgesForExtendedLayout = UIRectEdgeNone;
需要全屏布局:
self.extendedLayoutIncludesOpaqueBars = YES;
// 在ios7.1以后,ios会根据navigationBar的translucent来自动确定是否全屏。
1.2 subview的布局
对于subview的frame设置不难,重要的是要做到以下2点:
在ViewController的view尺寸变化时能自适应,如屏幕旋转,热点,电话等。
无论是否有navigationBar或tabBar都能够正常显示
做到第一点不难,不使用Autolayout也可以做到,那就是设置view的 autoresizingMask, 这个属性在还是frame布局时代是适配的利器。比如这样设置就可以让subview始终和view的尺寸一致:
tableView.frame = self.view.bounds;
tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
这个的详细使用可以参考这里面的介绍,本文不详细描述。
但是要做到第二点不使用Autolayout就有点捉襟见肘了,因为ios7以上全屏布局到处都是,为了能更好适配不至于navigationBar或者tabBar挡住了内容,当然你可以说不需要全屏,但是有一种情况你还要考虑:
在没有navigationBar的情况下,statusBar的高度不被考虑,于是你又不得不做出判断,当没有navigationBar的情况下,view上面留20像素来避免被statusBar挡住。
那如何来做呢,答案就是使用 LayoutGuide, 例如这个:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.mas_topLayoutGuideBottom);
make.leading.mas_equalTo(self.view.mas_leading);
make.trailing.mas_equalTo(self.view.mas_trailing);
make.bottom.mas_equalTo(self.mas_bottomLayoutGuideTop);
本文autolayout均使用 Masonry 作为示例,如果你不了解Masonry,请参考
上面的示例代码就保证了view不会被顶部或者底部的“条”遮住。
1.3 scrollView的contentInset
上面一小节的例子说明了如何在view是全屏的时候如何布局subview不被挡住, 但是如果我的subview是 UIScrollView, 也要全屏布局呢(为了炫酷的高斯模糊效果), 一般的做法就是设置 contentInset来避免内容被挡住, 这里我的答案是设置automaticallyAdjustsScrollViewInsets, 你是不是想:不是逗我吧,这东西不是坑么,很容易不起作用的。下面我来解释下。
为什么要使用automaticallyAdjustsScrollViewInsets就是我们要依从上面原则中的b. 不依赖当前是否有navigationBar或者tabBar来hardCode布局subview。 当然你也可以通过判断当前是否有navigationBar或者tabBar来手动计算,但是这样不是感觉有点dirty么。
苹果设计UIViewController的automaticallyAdjustsScrollViewInsets这个属性就是为了应对scrollView的全屏布局的,会依据viewController所处的环境(是否有navigationBar或者tabBar之类的bar), 在UIViewController的view moveToWindow的时候,自动设置scrollView的 contentInset 和 scrollIndicatorInsets来保证内容不被挡住, 但完美使用且不出问题要满足以下的条件:
UIScrollView是 UIViewConroller的view的第一个subview.
UIScrollView的位置正好是view的bounds
我想说的是,我们完全可以满足上面两点要求,经过我实践的经验,如果你的布局设计稿满足不了以上两点要求的页面,这样的页面也没有什么需要全屏的必要,这种情况下就不要全屏就好了嘛。当然如果你还是会有这样的需求,也有方法,参考2.2
1.4 SCREEN_WIDTH 和 SCREEN_HEIGHT
相信很多同学的工程里面一定可以搜到这两个宏,或者类似的东西,即是在我们使用了Autolayout布局之后,某些场景可能还是会让我们使用到这两个参数。比如设置 preferredMaxLayoutWidth(IOS7,IOS8已经可以自动) 的时候,很多时候直接用SCREEN_WIDTH来计算。
但是这两个东西也算是HARD CODE啊,只是目前用起来还好,其实也应该摒弃,像ipad分屏出来以后,这个东西就成了麻烦的事情了,但是麻烦我们也要解决啊,不然久而久之这个一定会是个坑的。
思考为了移除 SCREEN_WIDTH 和 SCREEN_HEIGHT, 我们需要兼顾哪些事情呢?
尽量依赖相对关系来计算size
类似 preferredMaxLayoutWidth 这样的属性也要去除依赖
在view的size变化时,preferredMaxLayoutWidth可以对应更改
上面做到第一点应该不难,用Autolayout完全满足,第二点和第三点可以给个参考,在view的layoutSubviews里面根据当前的size大小来设置 preferredMaxLayoutWidth,有人实际操作过可行,在IOS7上也实现了 Automatic 的 preferredMaxLayoutWidth。
1.5 IOS6问题
本来也想讲讲IOS6的兼容,但是现在淘宝天猫都开始不兼容IOS6了,还费精力干啥,把精力放到更有意义的事上吧,如果你们老板还在要兼容IOS6,你就打开淘宝天猫最新版,说:看!淘宝都不兼容了。
2. childViewController
在开发过程中,发现还有小部分同学对 childViewController的用法是错误的,你是否见过这样的代码呢?
TestViewController *vc = [[TestViewController alloc] init];
[vc willMoveToParentViewController:self];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
其实这里面第二行是多余的,具体如何使用,苹果的注释里面很清楚了:
These two methods are public for container subclasses to call when transitioning between child
controllers. If they are overridden, the overrides should ensure to call the super. The parent argument in
both of these methods is nil when a child is being rem otherwise it is equal to the new
parent view controller.
addChildViewController: will call [child willMoveToParentViewController:self] before adding the
child. However, it will not call didMoveToParentViewController:. It is expected that a container view
controller subclass will make this call after a transition to the new child has completed or, in the
case of no transition, immediately after the call to addChildViewController:. Similarly
removeFromParentViewController: does not call [self willMoveToParentViewController:nil] before removing the
child. This is also the responsibilty of the container subclass. Container subclasses will typically define
a method that transitions to a new child by first calling addChildViewController:, then executing a
transition which will add the new child&s view into the view hierarchy of its parent, and finally will call
didMoveToParentViewController:. Similarly, subclasses will typically define a method that removes a child in
the reverse manner by first calling [child willMoveToParentViewController:nil].
大概意思是:
1. 重载 willMoveToParentViewController: 和 didMoveToParentViewController:时不要忘了super
2. addChildViewController的时候只需要在后面调用 didMoveToParentViewController:
3. removeChildViewController的时候只需要在前面 willMoveToParentViewController:
4. 如果有切换动画应该先 addChildViewController 再将view添加到parentView上并做切换动画,动画结束再didMoveToParentViewController:
好了,今天讲的是 childViewController里面的布局问题,同样会遇到上面的问题:
2.1 childViewController的layoutGuide
在 childViewController 中使用 layoutGuide 不那么好使了, IOS7上完全不对,IOS9上可以正常工作,IOS8还没有测。那如何解决呢,解决方案可以取其 parentViewController的layoutGuide嘛,参考
当然取出来的 layoutGuide 可不能直接在Autolayout里面使用了,但是可以取其 length 来进行使用。
2.2 childViewController的 contentInset
在 childViewController 中 automaticallyAdjustsScrollViewInsets 也没用了, 解决方案可以同 2.1 取其 parentViewController的 layoutGuide 的length来进行设置, 这里面有个细节要注意,就是设置 contentInset 的方法放在 viewWillLayoutSubviews 函数里面才最佳。
3. Autolayout
上面讲了一些关于 viewController 怎么处理布局的问题,下面就列举一些实用布局的实例来解释如何用Autolayout来布局,已经其好处和必要性。
3.1 组合区块
这一节我们举例一个简单的区块布局,常见一些电商类活动资源模板建立。需求如下:
_______________________________________________________________________
_______________3________________
________________3_______________
| |________________________________| |
_______3______
_______3_______
| |________________________________| |______________| |_______________| |
|_____________3__________________________________3______________________|
其中 view1 和 view2 同宽, view2 和 view3, view4 同高, view3 和 view4 同宽, 所有的margin都是3。要完成这样要求的布局,可以很容易的用Autolayout来完成, 只需要指定好这些间距和宽度的关系就好了。
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(superview.mas_leading).offset(3);
make.top.mas_equalTo(superview.mas_top).offset(3);
make.bottom.mas_equalTo(superview.mas_bottom).offset(-3);
make.width.mas_equalTo(view2.mas_width);
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(view1.mas_trailing).offset(3);
make.top.mas_equalTo(superview.mas_top).offset(3);
make.trailing.mas_equalTo(superview.mas_trailing).offset(-3);
make.height.mas_equalTo(view3.mas_height);
[view3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(view2.mas_leading).offset(3);
make.width.mas_equalTo(view4.mas_width);
make.top.mas_equalTo(view2.mas_bottom).offset(3);
make.bottom.mas_equalTo(superview.mas_bottom).offset(-.offset(3)3);
[view4 mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.mas_equalTo(view3.mas_trailing).offset(3);
make.trailing.mas_equalTo(view2.mas_trailing);
make.top.mas_equalTo(view2.mas_bottom).offset(3);
make.bottom.mas_equalTo(superview.mas_bottom).offset(-3);
如何正确的设置 Constraints 的原则就是:
Contraints 设置的条件满足可以计算出view的frame.
相信大家对于这点数学基础应该没问题的。明白则个基本原则后,然后就要了解ios系统框架已经做了哪些事情,不然你可能不知道条件已经足够了,比如我们即将讲到的 UILabel
3.2 UILabel
之所以把 UILabel 单拿出来讲,是因为其布局的特殊性,可能需要根据内容来决定其真实view的size.
3.2.1 content Hugging & content Compression
有没有发现,你放一个 UILabel 在view上,然后只添加2个Constraint(top and leading), 这个Label也可以正常显示,也没有报警告或者crash, 这是为什么呢,打印一下发现 label 上有两个 constraint:
(lldb) po [0x constraints]
&__NSArrayI 0x&(
&NSContentSizeLayoutConstraint:0x UILabel:0x.width == 41.6667&,
&NSContentSizeLayoutConstraint:0x UILabel:0x.height == 20.3333&
这就是 content Hugging 或 content Compression 起作用了,api如下:
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
在xib中看起来是这样的
这两个属性是当 UIView 的 intrinsicContentSize 不为 UIViewNoIntrinsicMetric 的时候, 分别代表 ① view的边缘紧紧贴着它的内容 ② view的不透明的内容不会被压缩或者截断。
这两个属性不光是UILabel, UIImageView, UIButton等都可以使用。知道这两个属性对我们在同向多个view布局时选择如何布局有很重要的作用。
3.2.2 计算text,attributeText文本的高度
在以往的节目开发中我们经常会要预先计算 text文本的高度,然后再根据计算出来的结果设置UILabel的frame,在纯文本时代做起来顺风顺水。但是进入富文本时代后,你会发现一个问题:text的size计算不准 。 而且现在富文本的需求,比比皆是,UILabel使用富文本也在ios6之后变的easy. 但是如果还是用老的方案来设置UILabel的frame, 你会发现总有瑕疵,很可能发现文本高度计算不对,被截掉内容了。即使用系统的新api: -boundingRectWithSize:options:attributes:context:, 也存在在计算不准的情况, 我就遇到过,当设置文本段的 firstLineHeadIndent 之后,计算出来的size也会发生有偏差的情况下。
那么,有了Autolayout之后,既然计算不准,而且又要写一堆计算高度的代码(比如计算cell高度),增加了代码的阅读复杂度和可维护度,那就不计算了,让Autolayout自己算出来!
使用上面的思路,我们可以在cell里面设置好constraints之后,先预填一下内容,让cell自己先布局一遍,拿到计算的结果保存起来,避免二次计算。那么我们就可以实现cell高度的自动计算了,既省事,又准确,只是使用时要设计方案避免多余的性能损耗。
Autolayout分享到这吧,我前面也已经写过2篇关于Autolayout的了,这里就不多重复了。
4. tableView管理
tableView 作为ios开发最常用的页面控件,可以说是大家最熟悉不过的了,今天我们讨论一个大家都有类似体会的经历:
我们接到一个需求:要做一个展示商品或帖子详情的页面,第一行展示图片,第二行展示名称介绍价格等,第三行展示相关权益,第四行选择商品簇,第五行展示评价。
我们先假设用了最没有设计的做法:就是在tableView的dataSource和delegate方法里面直接if/else判断:
if (index == 0) {
} else if (index == 1) {
//名称 介绍 价格
} else if (index == 2) {
//相关权益
} else if (index == 3) {
} else if (index == 4) {
ok, 开发完了,上线,工作良好。
二期,PD说我们要做抢购,那这个商品页如果是抢购商品的话 第二行要加一行 抢购相关信息,比如倒计时,这个时候,这个时候再想想 Step1 的设计,还要继续吗,试想一下要如何 if/else才能解决?最终肯定是发现再这样搞就成了一坨了,那么这样的设计显然不合理, 就算加班搞定了,那以后再变更呢,这个坑只能越来越大。那如何重构呢?
其实这种情况是因为RPC获取的Model并不是我们最终用来布局tableView所需要的 viewModel. 我们需要根据tableView的特性抽象出来一个[{section:[row,row,row...]},{section:[row,row,row...]}, ...]这样结构的tableModel, 然后我们就可以先将 Model 解析为这个 tableModel, 然后根据这个 tableModel 来布局 tableView. 这样设计以后,我们就把业务逻辑和布局逻辑分开了,甚至都不用修改 tableView 的代理方法了。
经过Step2的重构,以后再改动需求过来,修改界面逻辑就不那么负责了,但是思考下日常中遇到这种情况的页面不在少数,那么为什么不把 tableModel 这一块封装成组件呢,毕竟tableView的布局都是类似的。
那么封装组件的话,我们要思考做到哪些设计:
低侵入性,不希望还要重载 UITableView, 或者 tabelView的 dataSource或delegate必须指向某个对象,这些设计对于原生的改造成本太高,而且造成了耦合,不利于维护。
tableModel 自身可以方便的进行编辑, 比如增删改查 sectionModel, 增删改查 rowModel, 根据indexPath查找Model,和根据model查询indexPath都方便。
能够基本做到tableView布局变化不用改动 dataSource 方法
能够缓存cell高度,避免重复计算
实现Autolayout的cell自动计算高度并缓存。
基于以上几个原则,我开发了,有兴趣的同学可以使用看看。欢迎提issue.
本文对一些ios布局上做了一些总结,提出了一些建议和原则,希望可以帮助到一部分人,当然如果有错误,欢迎指正。

我要回帖

更多关于 bottom layout guide 的文章

 

随机推荐