美文网首页
Introduction to Auto Layout

Introduction to Auto Layout

作者: ilaoke | 来源:发表于2015-08-10 17:27 被阅读1027次

    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》第十五章的总结。

    相关文章

      网友评论

          本文标题:Introduction to Auto Layout

          本文链接:https://www.haomeiwen.com/subject/cljiqttx.html