Chapter 1 构建自适应用户界面
@(Intermediate iOS 10 Programming with Swift)
起初,苹果发布的初代iPhone手机是固定3.5英寸屏幕,我们很容易为它开发应用程序,只需要考虑两个不同的方向(垂直或水平)就可以。后来苹果公司发布了一款9.7英寸的iPad,如果你在当时就是一位应用程序开发者,就必须要在Xcode中创建两个不同的UI设计——一个适用于iPhone,而另一个则适用于iPad。
时光荏苒,到了2015年,苹果的iPhone和iPad产品已经发生了巨大的变化。随着iPhone6和iPhone6 Plus(现在的iPhone 7和7 Plus)推出,我们的应程序需要支持各种屏幕尺寸和分辨率的iOS设备。它包括:
- iPhone 4/4s (3.5-inch)
- iPhone 5/5c/5s (4-inch)
- iPhone 6/6s/7 (4.7-inch)
- iPhone 6/6s/7 Plus (5.5-inch)
- iPad / iPad 2 / iPad Air / iPad Air 2 (9.7-inch)
- iPad Mini / iPad Mini 2 / iPad Mini 3 / iPad Mini 4 (7.9-inch)
- iPad Pro (9.7/12.9-inch)
对于iOS开发人员来说,创建一个通用的应用程序,是一个非常大的挑战,我们需要适应所有上面列出来的屏幕尺寸和方向的哟,唉,那么你可以如何来设计这种像素级的完美的应用程序UI呢?
从iOS 8开始,移动操作系统提供了一个称之为Adaptive User Interface(自适应用户界面)的概念,这是苹果支持iOS设备的任何尺寸和任何方向的解决方案。现在,应程序可以对用户界面的调整细节到特定设备甚至是特定的方向上。
这导致了一种称为自适应布局的新的UI设计概念,从Xcode 7开始,开发工具允许开发人员使用Interface Builder构建适应所有不同设备屏幕尺寸和方向的应用程序UI,在Xcode 8中Interface Builder进一步重新设计,以便于我们简化自适应用户界面的制作。我们甚至可以实时预览任何的iOS设备上呈现的UI设计效果。
要实现自适应布局,我们需要使用一个名为Size Classes的概念。这在iOS 8或更高的版本中可用,它也可能是实现自适应布局最重要的一个方面。Size Classes是根据屏幕的尺寸和方向对设备进行分类的抽象。你可以同时使用Size Classes和自动布局(Auto Layout)来设计自适应用户界面。在iOS8/9/10中创建自适应布局的过程如下:
- 你首先要设计一个通用的布局,这个通用布局足以支持大多数屏幕尺寸和方向。
- 你选择一个特定的size class并提供你的布局特性。例如,当设备处于横向的时候,你希望增加两个Label之间的距离。
在本章中,我们将通过构建通用应用程序引导大家了解所有自适应的概念,比如Size Classes类。该应用程序支持所有目前可用的屏幕尺寸和方向。
Adaptive UI demo
这个项目我们不需要编写代码,你将主要使用故事板布局用户界面组件,了解如何使用自动布局和size Classes让UI自适应。经过本章之后,你将拥有一个适用于多个屏幕尺寸和方向的单个视图控制器的应用程序。
Creating the Xcode Project
首先启动Xcode,并使用Single View Application模板创建一个新的项目,在项目选项中,将项目名称命名为AdaptiveUIDemo,并确保device设置为Universal。
项目创建好以后在Project Setting页面中将Deployment Target从10.0调整为9.3(或者更低)。因为iOS 10不再支持3.5英寸的设备,所以将其设置为9.3可以允许我们测试iPhone4s的应用程序。你可能不想测试旧一代的设备,但是在本书的示例中,我们想要验证的是如何通过自适应界面构建适应所有的屏幕大小的UI。
现在,打开Main.storyboard。在Interface Builder中,您会发现有一个大小为iPhone 6的视图控制器。如果你以前使用Xcode 7,我相信你超喜欢这个改变。它不会再显示一个具有方形画布的通用视图控制器。新的Interface Builder允许你在模拟设备中布置用户界面,您可以使用底部栏中的设备配置控件轻松地在不同设备之间切换。例如,选择View as打开设备配置面板,然后选择iPhone 6s Plus。故事板中的视图控制器的大小便相应发生变化。
现在我们就来开始设计app的用户界面。首先,从http://www.appcoda.com/resources/swift3/adaptiveui-images.zip下载图像包,并将图像导入到Assets.xcassets。
接下来回到故事板。我通常从iPhone 6s开始布局用户界面,然后再为其他屏幕尺寸添加特定的布局。因此,如果您选择了其他设备(例如iPhone 6s Plus),建议您将设备设置更改为iPhone 6s。
现在,将image view从对象库拖动到视图控制器。将其宽度设置为375,高度为390。选中image view,然后在Attributes检查器中,设置image为tshirt,将mode设置为Aspect Fill。
然后,拖动一个view到视图控制器,并将其放在image view的正下方。此view用作容纳其他UI组件(如Label)的容器。通过分组相关的UI组件到同一个view下,可以使你以后的工作变得更加轻松。在Size检查器中,确保将width设置为375,Height为277。
在整个章节中,我会将这个视图称为Product Info View。 您的界面布局应类似于下图。
接下来,将Label拖到Product Info View。将Label更改为PSD T-Shirt Mockup Template
。将字体设置为Avenir Next
,其大小为25
点。按command + =
调整label到合适的大小。在Size检查器中,将X的值更改为15
,将Y更改为15
。
拖拽另一个label,将其放在上一个label的正下方。在Attributes检查器中,将text更改为This is a free psd T-shirt mockup provided by pixeden.com. The PSD comes with a plain simple tee-shirt mockup template. You can edit the t-shirt color and use the smart layer to apply your designs. The high-resolution makes it easy to frame specific details with close-ups.
并将字体设置为Avenir Next
。将字体大小更改为18
点,将行数更改为0
。
在Size检查器中,将X的值更改为15,将Y更改为58。将宽度设置为352
,将高度设置为182
。
请注意,两个label应放置在Product Info View中。您可以通过双击打开Document Outline来仔细检查。两个label放在view的下面。如果你按照正确的程序,你的屏幕应该类似于下面这样。
即使你的设计完全不符合理想的参考设计,但也没有问题。我们将使用auto layout constraints来进行之后的布局。
现在,应用程序界面是iPhone 6s或4.7英寸的屏幕。我们进行一个快速测试,以查看不同设备上的设计外观和感觉。
在Xcode 8中,您有两种实时预览app UI的方法:
- 通过使用设备配置面板
- 使用预览助手(Preview Assistant)
如您以前尝试过的,您可以单击View as按钮切换到另一个设备。现在尝试将设备更改为iPhone SE,在看看它的外观。看起来不好看图像和文本都被截断了。您可以继续切换到其他设备来预览用户界面。
另外,你也可以使用Preview assistant,它可让你一次评估不同尺寸iOS设备上的设计效果。
要打开预览助手,请打开Assistant pop-up menu > Preview (1)。 然后按住option键,单击Main.storyboard(Preview)。
然后,Xcode将在助理编辑器中显示app UI的预览。默认情况下,它显示在iPhone 4英寸设备上的预览。您可以点击助手编辑器左下角的+按钮来预览iPhone 3.5英寸和其他设备。如果您将所有设备(包括iPad)添加到助理编辑器中,则屏幕应如下图所示。如您所见,目前的设计在除iPhone 6之外的所有设备上都看起来不尽如人意。到目前为止,我们还没有定义任何自动布局约束。这就是为什么视图不适合所有设备。
Adding Auto Layout Constraints
现在,我们要定义UI组件的布局约束。首先,我们从image view开始。一些开发人员害怕使用自动布局。我曾经以描述性的方式写过布局约束。以image view为例,这里有一些我可以想到的限制:
- image view的顶部,左侧和右侧与main view之间不应有间距。
- image view占主视图的55-60%。
- image view和Product Info View之间没有间距,应为零。
如果将上述约束转换为自动布局约束,则它们将按原样转换:
- 为image view的top、leading(即左)和trailing(即右)边缘创建间距约束。将距离设置为0。
- 定义image view和main view之间的高度约束,并将约束的乘数设置为0.585。 (我预先计算了这个值,但0.55和0.6之间的任何值都可以正常工作。)
- 在image view和Product Info View之间创建间距约束,并将其值设置为0。
现在选择image view,然后单击自动布局菜单上的Pin按钮以创建间距约束。对于左侧、上侧和右侧,将值设置为0
。确保Constrain to margin
选项未选中,因为我们要设置相对于super view边缘的约束。 然后点击Add 3 constraints
按钮。
接下来,打开Document Outline。从image view (tshirt)拖动鼠标到main view。出现提示时,从弹出菜单中选择Equal Heights
。
一旦添加了等高度约束,它应该出现在Document Outline的Constraints部分。选择约束并转到Size检查器。在这里您可以编辑约束的值来更改它的定义。
在下一步之前,确保第一个item设置为tshirt.Height
,第二个item设置为Superview.height
。 如果不是,您可以单击第一个item的选择框,然后选择Reverse First and Second item
。
默认情况下,multiplier的值设置为1
,这意味着tshirt image view占用main view的100%(这里的main view是superview)。如前所述,image view应该只占main view的大约60%。 所以将乘数从1改为0.585。
接下来,选择Product Info View,然后单击Pin按钮。选择左侧、右侧和底部,并将该值设置为0
。确保未勾选Constrain to margin
选项。然后单击Add 3 constraints
按钮。这为Product Info View添加了三个间距约束。
此外,我们必须在image view和Product Info View之间定义间距约束。在Document Outline中,拖拽image view(tshirt)到Product Info View。出现提示时,从菜单中选择Vertical Spacing
。这会创建一个垂直间距约束,使image view底部和Product Info View顶部之间没有间隙。
如果您现在在其他设备上查看所呈现的视图,则image view现在应适用于所有设备;然而,label还有很多工作要做。
现在,我们来定义两个label所需要的约束。
选择字体较大的标题label。点击Pin按钮。 将top的值设置为15,左侧为15和右侧为15。同样,确保取消选择边界约束,然后单击添加3约束。
接下来,选择另外一个标签。为顶部、左侧、右侧和底部添加四个间距约束。单击Pin按钮并相应地添加约束。
一旦添加了约束,您将看到一些红色的约束线,表示出现了一些布局问题。当某些约束是不明确的时候,可能会发生自动布局问题。要解决这些问题,请打开Document Outline,然后单击红色箭头以查看问题列表。
Xcode会智能的尝试为我们解决这些问题。只需点击指示图标,弹出窗口就会显示可能的解决方案。出现提示时,单击更改优先级以解决这些问题。
有时,您可能会看到黄色指示器。这表明有一些毛病。同样,您可以让Interface Builder通过updating the frame来匹配约束以解决这个问题。
酷!您已经创建了所有的自动布局约束。可以查看预览并查看UI在各种设备上的外观了。
注意:Interface Builder和Preview assistant在Xcode 8中仍然很差。有时,在实时预览中渲染的UI与真实设备或模拟器上显示的UI不完全相同。因此,如果您发现任何问题,请运行该项目并在模拟器中执行该应用程序。
视图看起来好多了,image view完美显示和对齐了。但是,还有几个问题:
- 描述label在大尺寸设备的屏幕上垂直居中。我们希望它在标题label的正下方显示。
- 某些型号的iPhone上标题和说明label只显示了一部分。
我们来看看第一个问题。你还记得我们为描述label定义了几个垂直空间限制吗?描述label应距标题label 8点,距super view的底部15点。 为了满足约束,iOS必须在更大的屏幕尺寸上扩展描述label。因此,描述文字就垂直居中了。
Content Hugging Priority
问你一个问题。为什么iOS不扩展标题label而是描述label呢?实际上有两个选项让iOS用于渲染满足布局约束的UI。iOS如何实现这一选择?
如果选择说明label并转到Size检查器,则应注意,content hugging priority(vertical)被设置为250
。现在选择标题label并查看其content hugging priority。你应该注意到它设置为251
。换句话说,它具有比说明label更高的content hugging priority(vertical)。
content hugging priority的value会帮助iOS确定应该放大哪个view。具有较高hugging优先级的view可以抵抗来自其内在的尺寸的增长。这里,标题label具有较高的hugging priority。这就是为什么iOS选择让说明label更大,而描述标签的大小不变。
Editing the Relation of the Constraints
现在你应该对content hugging priority有一个基本的了解,让我们继续解决我们发现的第一个问题。
您可以随时查看Size检查器下特定组件的约束。选择描述label,然后转到Size检查器。你将在Constraint部分找到约束的列表。
我们已经为这个label定义了四个间距约束。如果你查看这些约束,他们每个都有一个Equal关系。这意味着在渲染描述label时,label的每一边应该与指定的约束完全相同。间隔不能大也不能小。
那么,如何修改约束,使描述label放置在标题label下,而不管屏幕尺寸如何?
我想你可能会知道答案。不用严格地将约束关系设置为等于,底部间隔约束应该有一些更大的灵活性。空间不需要等于15点。这只是我们想要的最小空间。该空间实际上可以随着屏幕大小而增长。
现在,双击Bottom Space约束来编辑它。将关系从Equal更改为Greater than or Equal,一旦作出改变,空间问题应该是固定的。
好的,现在是研究第二个布局问题的时候了。
- 某些iPhone型号的标题和说明label会显示一部分。
这个问题很容易解决。 我们可以让较小屏幕尺寸的iOS设备显示较小的字体。选择标题label,然后转到Attributes检查器。将Autoshrink
选项的值设置为Minimium Font Size
。对描述label重复相同的步骤。
仅此而已。如果您预览iPhone 4s / 5上的用户界面或在这些设备上执行项目,则两个label都将正确显示。
Size Classes
正如我在本章开头提到的,设计自适应用户界面要分为两个部分。到目前为止,我们已经创建了通用布局。基本布局足以支持大多数屏幕尺寸。该过程的第二部分是使用Size Classes来微调设计。
首先,什么是Size Classes?
一个Size Classes标识显示空间的垂直(高)和水平(宽度)尺寸的相对量。iOS中有两种类型的大小类:regular(常规)和compact(紧凑)。常规尺寸类别表示较大的屏幕空间,而紧凑尺寸类别表示较小的屏幕空间量。
通过使用Size Classes描述每个显示维度,这导致有四个抽象设备:常规宽度 - 常规高度,常规宽度 - 紧凑高度,紧凑宽度 - 常规高度和紧凑宽度 - 紧凑的高度。
下表显示了iOS设备及其相应的Size Classes。
要表征显示环境,您必须同时指定水平Size Classes和垂直Size Classes。例如,iPad具有Regular水平(宽度)Size Classes和Regular垂直(高度)Size Classes。
使用基本布局,您可以使用Size Classes来提供布局特殊化,以覆盖基本布局中的某些设计。例如,您可以更改采用紧凑高度 - 常规宽度Size的设备的Label字体大小。或者您可以更改按钮的位置,特别是对于Regular Size。
请注意,纵向的所有iPhone都具有紧凑的宽度和常规高度。换句话说,您的用户界面将显示在iPhone 4上几乎相同,就像在iPhone 7上一样。
iPhone 6/7 Plus在横向上具有Regular宽度和Compact的高度Size。这允许您创建一个完全不同于iPhone 7(或更低版本)的UI设计。
Using Size Classes for Font Customization
通过对Size Classes的一些基本了解,我们来看看如何使用它进行app 的布局专业化。
不用说,我们想让iPhone的标题和描述label完美。当前的字体大小是iPhone的理想选择,但对于iPad来说太小了。我们要做的是使iPad设备上的字体更大一些。
使用Size Classes,您现在可以调整特定屏幕尺寸的字体样式。在这种情况下,我们要更改所有iPad型号的字体大小。在Size Classes方面,iPad设备默认为水平(宽度)的Regular和垂直(高度)的Regular Size Classes。
要设置此特定Size Classes的字体大小,请先在Interface Builder中切换到iPad设备,然后选择标题label。在Attributes检查器下,您应该看到字体设置旁边的加号(+)按钮。点击+按钮。将width和height都设为Regular,然后单击Add Variation。
然后,您将看到一个新的Font选项条目,它专用于该特定的Size Classes。保持原始字体选项的大小不变,但将wR hR字体字段的大小更改为35点。
这将指明iOS在iPad上使用较大的第二种字体。对于iPhone设备,原始字体仍将用于显示文本。现在选择描述Label。再次,在Attributes检查器下,单击+按钮,然后单击Add Variation。 将wR hR
font字段的大小更改为25点。 在模拟器中查看预览或测试应用程序。所有屏幕尺寸的布局看起来都很完美。
Using Size Classes to Customize a View
现在,用户界面适合所有设备的纵向方向,他们如何对待横向呢? 在Preview Assistant中,单击设备上的旋转按钮(例如iPhone 4 inch)。或者您可以使用模拟器在横向模式下查看UI。该视图看起来不错,但我认为有更好的方式在横向上布局UI。
我将向您展示如何为视图创建另一种设计,以利用更宽的屏幕尺寸。这是Size Classes的真正魅力。
更宽但更短的屏幕尺寸,最好同时呈现image view和Product Info View。每个占主视图的50%。此屏幕显示了iPhone 横向的最终设计效果。
那么我们如何使用Size Classes创建两个不同的UI呢?目前,我们只有一套适用于所有Size Classes的自动布局约束。为了创建两个不同的UI,我们必须为每个UI使用两组不同的布局约束:
- 对于iPad和iPhone(纵向),我们利用现有的布局和布局约束。
- 对于iPhone(横向),我们重新启动UI并定义一组新的布局约束。
首先,我们必须将现有的布局约束移动到Size Classes,以便当设备是纵向方向的iPad或iPhone时激活约束。在设备配置面板中,您将找到Vary for Traits
按钮,它是用来设计用户界面变体的。当您点击按钮时,会显示一个popover,其中包含两个选项供您选择。在这种情况下,选择高度,然后单击Interface Builder中的任意位置。设备配置面板变为蓝色,并显示我们刚刚选择的Size Classes的受影响设备。如果您单击Varying 18 Regular Height Devices
选项,它将显示所有受影响的设备,包括iPad和iPhone(人像)。
在变化模式下,您对画布所做的任何更改仅适用于当前变体(或Size Classes)。因为我们希望将所有现有的限制迁移到这个变体。 在Document Outline视图中选择所有约束(按住Command并选择每个约束)。接下来,转到Size Classes检查器,然后单击+按钮(安装的选项旁边)创建一个变体。
Interface Builder 会在Regular-height复选框中显示Installed
。因为所有现存的约束都被应用在此Size Classes。取消选中Installed
的复选框,并选中hR Installed
的复选框。这意味着为iPad和iPhone(横向)设备被激活所有选定的限制。最后,单击完成更改以完成更改。
您如何知道约束是否仅应用于Regular-height设备?在设备配置窗格中,您可以将iPhone的方向更改为横向。您应该发现,横向的UI不再正确地呈现。而且,所有的约束都是灰色的,这意味着它们不属于这个Size Classes。
现在是时候以横向重新设计应用程序布局了,可以定义一组单独的布局约束。
确保您在设备配置栏中选择iPhone 6s设备和横向。再次点击Vary for Traits
按钮。在popover中,选择Height可以在横向模式下为所有iPhone设备创建变体。
从现在开始,您所做的所有更改将仅适用于所选Size Classes(即Compact宽度和Compact高度)。
首先,选择T恤image view。在Size检查器中,将x
设置为0
,将y
设置为0
,将width
设置为333
,将height
设置为375
。在Attributes检查器中,确保Clip to Bounds
选项已启用。
接下来,选择标题和描述Label。转到Size检查器,将x
设置为333
,将y
设置为0
,将width
设置为334
,将height
设置为375
。
最后,调整标题和描述标签的大小,使其适合。这里我将两个标签的宽度改为303
点。您的布局应与图1.31类似。
到目前为止,我们还没有为此Size Classes定义任何布局约束。现在选择T恤Image view
,然后点击Pin按钮。添加顶部、底部、左边和右边共四个空间约束。
接下来,选择view,并为顶部,左侧和右侧添加三个空间约束。
其余的是添加标题和描述Label的布局约束。选择标题Label,然后单击Pin按钮。添加顶部、底部、左侧和右侧的空间限制(参见图1.34)。然后,为描述标签添加两个空间约束(左侧和右侧)。
由于我们希望这两个视图各占据屏幕的50%,因此可以从T恤image view拖动到容器视图。当弹出面板出现时,选择Equal Width
以添加约束。
一旦定义了约束,Interface Builder会检测到一些歧义。单击黄色圆圈按钮以显示问题列表,然后单击每个指示符图标,选择更新框架以解决问题。最后,确保您单击完成来保存更改。您的最终布局看起来与图1.36相似。
如果您看到Document Outline中的约束,您应该看到启用了一些约束,而有些则会变为灰色。正常颜色中的这些约束应用于当前Size Classes。在这种情况下,它是Compact宽度和Compact高度。如果切换到纵向模式,您将看到启用了一组不同的约束。
这是您如何使用Size Classes应用到指定设备的布局约束集,并在Interface Builder中布置两个不同的UI。如果您使用任何iPhone模拟器运行应用程序,您将看到两个不同的用户界面,用于纵向和横向。另一件好事是,当视图从纵向更改为横向时,iOS会平滑过渡。 它看起来很棒,对吧?
Using Size Classes to Customize Constraints
希望您现在可以了解如何使用Size Classes自定义字体并查看特定Size Classes组合的设计。在这些自定义之上,您可以使用Size Classes来自定义特定的约束。
如果要将iPhone 6/7 Plus(横向)的视图设计更改为此视图,但保持其他iPhone的设计完好无损?
您可以看到,标题和描述已被移动到视图的右下角部分。显然,我们必须定制标题标签和其Super view之间的顶层间距约束。
让我们看看如何做到这一点。
首先,将设备更改为iPhone 6s Plus,并在设备配置窗格中将方向设置为横向。因为我们要在这个特定的方向上为这个设备应用特定的布局,请点击Vary for Traits
按钮,并选择height和width选项。 Interface Builder应该指出这种更改只适用于Regular宽度和compact高度设备。接下来,选择标题Label顶端的垂直空间约束。在属性检查器中,您应该看到常量字段。该值以点为单位定义垂直空间。因为我们要为这个特定的Size Classes增加这个值,请点击+按钮并确认添加变体。
这将为wR hC大小类创建一个附加字段。将值设置为150点。仅此而已。记住单击Done Varying
按钮保存更改。
您可以在Interface Builder中或使用模拟器预览新的UI设计。在iPhone 6 / 6s / 7 Plus上,当设备处于横向时,会出现新的UI。
总结
随着Xcode 8中的改进的Interface Builder,Apple为开发人员提供了一个更好的工具来在iOS应用程序中构建自适应UI。我希望你已经了解了Size Classes的概念,并且知道如何使用它们来创建自适应布局。
自适应布局是自iOS 8以来引入的最重要的概念之一。开发人员只有单个设备和屏幕大小才能设计出来。如果您要构建下一个应用程序,请确保掌握Size Classes和自动布局的概念,并使您的应用适应多种屏幕尺寸和方向。 应用程序设计的未来更有可能适应。所以准备好了!
作为参考,您可以从http://www.appcoda.com/resources/swift3/AdaptiveUIDemo.zip下载Xcode项目。