Introduction to Auto Layout

https://github.com/bluhar/IOSCook/tree/master/note15

Introduction to Auto Layout

利用自动布局,使得应用既能在iPhone上运行,也能在iPad上很好的显示。应用默认是可以在iPad上运行的,只是屏幕周边会显示黑边。

一个应用既能在iPhone上运行,又能在iPad上运行得就像为iPad开发的似的,叫做universal application.

修改项目target的General tab,修改Devices-->Universal.


此时在iPad上运行应用,table view已经可以撑满屏幕,但是自定义的view,显示得很不好。


Auto Layout

第四章讲过,view's frame指定了他的大小和相对于superview的位置。目前定义视图frame的方式是绝对坐标。

使用自动布局,可以以相对的方式描述视图的布局,从而在运行时根据设备的屏幕大小确定frame。

苹果设备的屏幕大小(points)如下表,points用来布局界面,最终映射到屏幕的像素。retina 屏幕设备和非retina设备有相同的points,但是retina屏幕像素是非retina屏幕的两倍。

Device Width * Height (points)
iPhone/iPod(4S and earlier) 320 * 480
iPhone/iPod(5, 5C, 5S, 5g) 320 * 568
iPhone6 375 * 667
iPhone6 Plus 414 * 736
All iPads 768 * 1024

参考

Alignment rectangle


Alignment rectangle由一系列layout attributes定义:

Layout attributes Description
Width/Height 定义alignment rectangle的大小
Top/Bottom/Left/Right 定义到alignment rectangle的边距离其他view的alignment rectangle的距离,类似CSS中的margin
CenterX/CenterY 定义alignment rectangle的中心点
Baseline This value is the same as the bottom attribute for most, but not all, views. For example, UITextField defines its baseline to be the bottom of the text it displays rather than the bottom of the alignment rectangle. This keeps “descenders” (letters like ‘g’ and ‘p’ that descend below the baseline) from being obscured by a view right below the text field.
Leading/Trailing 这两个值用在文本类的视图,如UITextField,UILabel。如果语言阅读方向是从左向右,则leading同left,trailing同right;反之,leading同right,trailing同left。

默认,XIB中的每个view都有一个alignment rectangle,并且都使用自动布局,但是默认并不是你想要的效果,你需要定义一些约束,系统根据这些约束来定义layout attributes,从而影响alignment rectangle。

Constraints

为BKDetailViewController的view的toolbar添加constraints,使得其在iPad上可以友好显示。

toolbar的显示规则:

  • 应该显示屏幕的最下方
  • 应该和屏幕宽度一样
  • toolbar高度应该是44 points (toolbar的苹果标准)

为将这些显示规则在Interface Builder中转变成constraints,要先理解视图的nearest neighbor,nearest neighbor是在指定方向上最靠近视图的同级视图,如果在指定方向上没有同级视图,则nearest neighbor是其superview(container)

现在toolbar的constraints可描述为:

  1. toolbar的bottom距其nearest neighbor为0 points
  2. toolbar的left距其nearest neighbor为0 points
  3. toolbar的right距其nearest neighbor为0 points
  4. toolbar的height应该是44 points

第二和第三条约束,其实就间接定义了toolbar的width为屏幕的宽度。第一和第四条约束,间接定义了toolbar的top。

要添加这些约束,可以通过Interface Builder或代码的方式。
苹果推荐只要可能就使用Interface Builder的方式来添加,如果视图是通过代码的方式创建的,可以通过代码方式来添加约束,16章会讲到。

Adding Constraints in Interface Builder

打开XIB文件,在右下角,会发现Auto Layout Constraint Menu。


为toolbar添加left, right, bottom, height约束:


Adding more constraints

为name label添加约束,使其固定在当前位置,并保持当前的高和宽,选择name label,点击auto layout constraint menu的第二个Pin menu,激活这些约束。

现在为name label的text field添加约束,激活当前的left和right。这样即使屏幕变宽了,其宽度会自动扩展,保持右侧距nearest neighbor为right指定的值。


但是只设置left和right,发现提示异常:



这是因为没有指定Y轴方向的约束,你可以打开Pin menu,为name text field激活top属性。但是还有更好的方法:根据name label来定位name text field,将name text field和name label的Baseline对齐。

选择text field,按下Shift键,然后选择name label,点击auto layout constraint menu的第一个menu: Align,然后选中Baselines,并添加这个约束。

Adding even more constraints

现在已经知道如何通过Pin和Align menu来添加约束,还可以通过Control-drag来添加约束。
Control-drag一个view到另一个view,然后释放鼠标,获得一个可以添加的约束列表。约束是动态生成的,根据drag的方向以及两个veiw。

现在为Serial label添加top, left, with, height约束。
top:保持当前距name label的距离,
left:和name label左对齐
with/height:保持当前的值


上面的gif,首先为serial label激活了top属性,保持当前top距name label(top方向的nearest neighbor)的距离。其次,选择了left属性,相当于点击Align menu,选中Leading Edges。最后,Control-drag自己到自己,并且拖动方向对角线,这样width和height才会同时出现,按下Shift键可以同时选中with/height,然后按下回车键。

下面为serial text field添加leading(left),baseline(和serial label baseline对齐),trailing(right)约束。


总结:对于要和其他view对齐的constraint,用Control-drag的方式更方便,对于其他约束,比如width, height,用Pin menu更方便。

Priority

每个约束都有一个priority属性,当有多个约束冲突时,通过此值来决定优先级。
值是1-1000,1000代表为必须约束,默认情况下每个约束都是必须约束。所以当多个约束有相同的priority值,就无法解决约束冲突的问题。

当约束冲突时,可以删除某约束或减小某约束的priority来解决冲突。

调试约束

当添加很多约束后,可能会造成:约束冲突,缺少约束,约束与视图不匹配等问题,需要通过调试来解决这些问题。

Ambiguous layout

不确定的布局,现在添加两个label并将其背景色改为灰色,来模拟abmiguous layout。

在BKDetailViewController.m中重写viewDidLayoutSubviews方法,检查其子视图是否有不确定的布局。

- (void)viewDidLayoutSubviews{
    // 检查子视图是否有ambiguous layout
    for (UIView *subview in self.view.subviews) {
        if ([subview hasAmbiguousLayout]) {
            NSLog(@"AMBIGUOUS: %@", subview);
        }
    }
}

当视图大小发生变化(比如屏幕旋转)或是视图第一次被显示时,会调用viewDidLayoutSubviews方法。

在backgroundTapped方法中调用exerciseAmbiguityInLayout,此时运行应用,点击detail视图的背景,可以看到两个label的宽度来回切换。

- (IBAction)backgroundTapped:(id)sender {
    // 隐藏键盘
    [self.view endEditing:YES];
   
    for (UIView *subview in self.view.subviews) {
        if ([subview hasAmbiguousLayout]) {
            [subview exerciseAmbiguityInLayout];
        }
    }
}

由于两个label的宽度没有明确的约束,所以造成了这两种情况都满足目前的约束,只要为一个label添加width约束,就可以解决此问题。Control-drag一个label到另外一个,选择equal width,让两个label保持相同的width。

exerciseAmbiguityInLayout方法是用来调试的,当要发布应用时,要删除这种调试方法。

Unsatisfiable constraints

当约束冲突时,产生unsatisfiable constraint。
比如为一个label添加了leading, trailing约束,然后又添加了width约束,这就造成了约束冲突,因为leading, trailing已经可以确定其宽度了,不需要再添加width约束。

当有约束冲突时,console中会有以下信息,删除不必须的约束即可解决:

2015-08-10 00:45:47.957 Homepwner[1500:70b] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x8d82d10 H:[UILabel:0x8d82c00(149)]>",
    "<NSLayoutConstraint:0x8d8a650 H:[UILabel:0x8d82c00]-(85)-|   (Names: '|':UIControl:0x8d82080 )>",
    "<NSLayoutConstraint:0x8d8a680 H:|-(86)-[UILabel:0x8d82c00]   (Names: '|':UIControl:0x8d82080 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x8d9ffa0 h=-&- v=-&- UIControl:0x8d82080.width == _UIParallaxDimmingView:0x8d8ed10.width>",
    "<NSAutoresizingMaskLayoutConstraint:0x8da0740 h=--& v=--& H:[_UIParallaxDimmingView:0x8d8ed10(768)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x8d82d10 H:[UILabel:0x8d82c00(149)]>

Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Misplaced views

当XIB文件中,view的frame当前的位置和约束定义的位置(比如top, left等)不匹配时,就会有misplace view problem。

要解决此问题有两种方式,要么更新视图位置匹配约束的定义,要么更新约束匹配视图的位置。

打开auto layout constraint menu的第三个menu,即Resolve Auto Layout Issues menu。选择Update frames或Update constraints。


Debugging Using the Auto Layout Trace

UIWindow有一个私有的实例方法_autolayoutTrace,该方法会返回window的完整视图结构,如果视图有ambiguous layout,会被添加tag: AMBIGUOUS LAYOUT.

代码中添加一个断点,断点位置要保证view已经显示在屏幕上,在断点处运行以下代码查看:

(lldb) po [[UIWindow keyWindow] _autolayoutTrace]

Multiple XIB files

为XIB文件添加后缀,来达到在不同的设备上view controller加载不同的XIB文件。即使如此,还是要为XIB文件使用auto layout,auto layout可以响应用户语言,字体,设备方向的变化。

  • BKDetailViewController~iphone.xib
  • BKDetailViewController~ipad.xib

本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十五章的总结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容