黑马程序员技术交流社区

标题: 开始iOS 7中自动布局教程(一) [打印本页]

作者: 602516169    时间: 2016-9-2 23:20
标题: 开始iOS 7中自动布局教程(一)
示:团队成员Jatthijs Hollemans(iOS 初级系列作者)已经将这篇文章移植到iOS7 feast上。希望你能够喜欢。
你是否曾经想让你的app在横竖屏方向上看起来都表现良好而受挫?是否在做支持iPhone和iPad屏幕布局界面时几近大小便失禁?今天我将给你带来好消息!

一直为大小相同的屏幕设计一个用户界面并不难,但如果屏幕的尺寸改变的话,UI元素的位置和大小也需要相应的做出改变。

到目前为止,如果你的设计相当的复杂,那么你必须编写大量的代码来适应这样的布局。你应该很高兴,现在这样的情况再也不存在了--iOS6为iPhone和iPad带来了一个极好的新特性:自动布局。Xcode 5和 iOS7中对自动布局做出了改善!如果你曾经尝试着在Xcode4中使用自动布局并最终做出放弃,现在是该给Xcode5一次机会了。

在你的程序中,自动布局不仅可以很容易的支持不同大小的屏幕,一个额外的功能,它也使得本地化几乎变得微不足道。你不再需要为每种你希望支持的语言创建新的nibs或storyboards,包括像Hebrew或Arabic这样从右到左的语言。

这个教程将向你展示如何使用Interface Builder开始自动布局.在iOS6 教程中,我们进一步讨论过这个教程,然后有一个基于这个知识点完整的新章节,并且向你展示如何通过代码完全释放出自动布局的能量。

注意:我们已经开始将iOS6教程中相应的章节更新到iOS7-这只是先给大家解解馋!当我们完成后,所有iOS6 PDF教程的订阅者将会得到免费的更新下载。

so,备好零食和你喜欢的咖啡,准备成为自动布局的达人吧!

springs和struts的问题
你肯定很熟悉autosizing masks-也被认为是springs&struts模式。autosizing mask决定了当一个视图的父视图大小改变时,其自身需要做出什么改变。它有一个灵活的或固定不变的margins(struts)吗?它的宽和高要做出什么改变(springs)?

举个例子,一个宽度灵活的视图,如果其父视图边框,那么它也会相应的变宽。一个视图右边拥有固定的margin,那么它的右边缘将会一直粘住其父视图的右边缘。

autosizing系统在简单的情况下非常奏效,但当你布局变得更复杂时,它立马跪了。让我们看一个springs和struts不能处理的示例。

打开Xcode5,创建一个基于Single View Application模板的iPhone项目。叫做"StrutsProblem":

点击Main.storyboard。在你做别的之前,首先将这个storyboard的自动布局关了。你需要在File inspector,第一个选项的第六个tabs里:


将Use Autolayout的box勾选去掉。现在storyboard使用旧的struts-and-springs模型。

注意:任何你使用Xcode4.5或更高版本中,nib或者storyboard文件都默认激活了自动布局。因为自动布局是iOS6以及以上系统的一个新特性,如果你想使用最新的Xcode开发兼容iOS5的程序,你需要将这个选项去掉。

拖拽三个新的视图到主视图上,并且像这样排列起来:

为了表述更清楚,这里给出每个视图的颜色,这样你就能分清哪个是驴子哪个是马了。
每个视图的inset到窗口的距离都是20点;视图之间的距离也是20点。底部的视图的宽是280点,上面两个视图的宽都是130点。所有的视图的高都是254。


在iPhone Retina 4-inch simulator上运行这个程序,并且将模拟器旋转到landscape。程序看起来便变成这副鬼样,这不是我想象的那样:

注意:你可以使用Hardware\Rotate Left和Rotate Right菜单选项旋转模拟器,或者通过按下键盘上的? 键,同时按下←或→。

而你想象的程序在landscape应该像这样:


很明显,三个视图的autosizing masks留下了一些需要改进的地方。将左上方视图的autosizing设置改成这样:

这将会让视图贴附左上边缘(不是右下边缘),并且当父视图大小改变时,重新调整自身水平和垂直方向的大小。

同样的,右上方视图的autosizing设置改成这样:

底部视图:

再次运行程序,并且旋转到landscape。现在看起来像这样:

已经很接近了,但又不完全一样。视图之间的padding不正确。换个说法就是视图的大小不完全正确。问题出在当父视图改变大小时,autosizing masks告诉子视图调整大小,但又没告诉子视图该调整多少(坑儿?)。

你可以调戏autosizing masks-比如,改变灵活宽度和高度设置(springs)-你不会得到完全正确的三个间距20点的视图。

为了解决这个springs和struts方法的布局问题,非常不幸,你需要额外写一些代码。

在旋转用户界面之前、之间、之后,UIKit会发送一些消息到你的视图控制器,你可以截获这些消息,从而对你UI做出改变。代表性的像viewWillLayoutSubviews,你会重写这个方法从而改变任何需要重新排列的视图的frame。

在这之前,你需要先做出一个outlet属性来引用这个视图。

切换到Assistant Editor模式,按住Ctrl,将三个视图都拖到ViewController.m中去:


分别链接视图到这三个属性:
@property (weak, nonatomic) IBOutlet UIView *topLeftView;
@property (weak, nonatomic) IBOutlet UIView *topRightView;
@property (weak, nonatomic) IBOutlet UIView *bottomView;

下面的代码写到ViewController.m:
- (void)viewWillLayoutSubviews
{
    if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
    {
        CGRect rect = self.topLeftView.frame;
        rect.size.width = 254;
        rect.size.height = 130;
        self.topLeftView.frame = rect;
  
        rect = self.topRightView.frame;
        rect.origin.x = 294;
        rect.size.width = 254;
        rect.size.height = 130;
        self.topRightView.frame = rect;
  
        rect = self.bottomView.frame;
        rect.origin.y = 170;
        rect.size.width = 528;
        rect.size.height = 130;
        self.bottomView.frame = rect;
    }
    else
    {
        CGRect rect = self.topLeftView.frame;
        rect.size.width = 130;
        rect.size.height = 254;
        self.topLeftView.frame = rect;
  
        rect = self.topRightView.frame;
        rect.origin.x = 170;
        rect.size.width = 130;
        rect.size.height = 254;
        self.topRightView.frame = rect;
  
        rect = self.bottomView.frame;
        rect.origin.y = 295;
        rect.size.width = 280;
        rect.size.height = 254;
        self.bottomView.frame = rect;
    }
}
当视图控制器旋转到一个新的方向,这个回调将会被调用。它会监控视图控制器旋转的方向,并且适当的调整视图大小-在这种情况,根据已知iPhone屏幕大小会有一个hard-code(将可变变量用一个固定值来代替的方法叫做hard-code)偏移。这个回调会在一个动画block中发生,所以会动态的改变大小。

暂时还不要运行这个程序。首先你需要按下面的样子重新保存三个视图的autosizing masks,否则autosizing mechanism将会和你在viewWillLayoutSubviews中设置的位置和大小冲突。

这样就可以了,运行程序并且翻转到landscape。现在视图排列的非常号。翻转回到portrait,经核实,一切都良好。

这样奏效了,但是你需要为这个非常简单的例子编写大量的布局代码。想象一下,为布局付出的努力是非常复杂的,特别是个别视图动态的改变大小,或者子视图的个数是不固定的。

现在试着在3.5寸的模拟器上运行程序。我了个去。视图的位置和大小又错了,因为viewWillLayoutSubviews的hard-code坐标是基于4英寸大小的手机(320x568取代320x480)。你可以增加另一个if语句判断屏幕大小,并使用不同的坐标集,但是你可以看到这个方法很快变得不切实际。

注意:另一个你可以采取的方法就是为portrait和landscape模式建立独立的nibs。当设备旋转时,你从另一个nib中装载视图并替换掉当前的那个。但这任然需要做很多工作,并且维护两个nibs也会增加问题。当你使用storyboards替代nibs的时候,这个方法也变得不切实际。

自动布局拯救猿!
现在你将会看到如何用自动布局实现相同的效果,从ViewController.m中移除viewWillLayoutSubviews,因为我们不再需要写任何代码。

选择Main.storyboard,并在File inspector中选择开启Use Autolayout:

运行程序,旋转到landscape。现在看起来像这样:

让我们把自动布局付诸行动。当你点击顶部两个视图时,按住?键,这样两个视图都被选中了。从Xcode的Editor菜单中选择Pin\Widths Equally:

再次选中两个相同的视图,选择Editor\Pin\Horizontal Spacing。(尽管你执行完第一次Pin处理后,两个视图看起来还是被选中的,但其实他们只是在一个特别的布局关系显示模型里。所以你需要重新选择这两个视图)




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2