iOS Apprentice中文版-从0开始学iOS开发-第九课

来个大整容

app在横屏状态下是不显示iPhone的状态栏的(就是有个小电池的那一栏),除非你强制app这样做。这个设计是合理的。游戏需要的是强烈的带入感,而状态栏会减弱这个效果。

在iOS7或更早的版本,横屏下的状态栏不会自动消失,那时候我们的教程中有写如何隐藏状态栏。

虽然现在已经没有必要这样做了,但是对你这个Bull's Eye游戏app,还是有点关于状态栏的事需要处理。

首先,你要从storyboard里将状态栏移除掉。

打开Main.storyboard并且选择View Controller。找到属性检查器(Attributes inspector),然后在Simulated Metrics(模拟指标)页签中找到Status Bar(状态栏)选项,选择为None。

这样就把状态栏从storyboard中移除掉了(你可以在设计界面上看到有小电池图标的那一栏已经没有了)。

从view controller中移除状态栏

这个设置对app运行是没有影响的,因为app运行时本来就不显示状态栏。这就是为什么这个页签的名字叫做“Simulated Metrics(模拟指标)”。界面建造器(Interface Bulider)仅仅是假装这里有一个状态栏可见,目的是为你设计界面时提供辅助参考,让你看看有状态栏时是什么样子。

试着改变一下Simulated Metrics中的其他选项,然后运行app,你会发现根本没有什么区别。

打开Project Setting,向下滚动页面找到Deployment Info。然后你可以看到一个Status Bar Style选项,在它下面有一个Hide status bar(隐藏状态栏)复选框,将这个复选框选中。

这样也可以在app运行期间隐藏状态栏。

隐藏状态栏设置

在app运行期间隐藏状态栏是个不错的做法。因为那会时操作系统在启动app时多花几秒时间。如果你不选这个选项,那么就算状态栏不可见,但是它还是存在的。

这只是一个很小的细节而已,但是一个殿堂级app和一个普通的app的差距也就在于大量的细节。

Info.plist
大多数Project Setting中的设置,比如设备支持的方向(supported device orientations)和app运行期间是否显示状态栏等,都存储在你app的Info.plist文件中。
Info.plist是应用程序包中的一份配置文件,它负责告诉iOS这个app应该作何表现。它也包含某些不能放在其他地方的关于app的一些具体特征,比如app的版本号。
在早期版本的Xcode中,你必须手工配置Info.plist,但是在Xcode 8中已经不需要这样做了。你可以直接在Project Setting中设置大多数内容。
然而,知道Info.plist的存在和它的作用还是有必要的。
在工程导航器(Project navigator)并且选择Info.plist,看一看它里面的内容。


Info.plist

Info.plist只是许多配置选项的列表以及每种选项的值。它们中的大多数对你而言都没什么意义,不用担心什么,它们对我而言同样没有意义。
注意一下选项“Status bar is initially hidden”。它的值是“Yes”。这是你刚刚作出的变更。

来点漂亮的图片

摆脱status bar只是我们宏大计划的一小步。我们想要把下面这个界面:

见证奇迹的时刻

变成下面这个样子:

这些控件其实都没有变。你只是简单的用图片使它们变的好看,并且对颜色和字体作出变更。

你可以在背景中,按钮上,甚至滑条上放入图片,用来自定义它们的外观。图片必须是PNG格式的。

如果你的艺术细胞不足以应付这一挑战,也不用担心,我会给你提供这些图片(原书附件中有)。但是假如你的技能点全部点到photoshop上了,那么你就自己做些自己喜欢的图片吧。

教程中Resource文件夹下有一个子目录Images。你首先要将这些图片导入到Xcode project中。

在Project navigator(工程导航器)中找到Assets.xcassets然后点击选中它。

这就是所谓的app资产目录(asset catalog),它里面包含关于app的所有图片。目前,它是空的,里面只有一个关于app图标的预占位置,很快你会把它填满。

起初资产目录是空的

在面板的底部你会找到一个+号。点击它并且选择Import...

选择Import...导入图片到资产目录

然后Xcode会弹出一个文件选择器。进入Images文件夹,然后使用command+A全选文件夹里的图片。

选择要导入的文件

点击Open,然后Xcode会将所有图片拷贝到资产目录中:

现在资产目录中有所有需要的图片了

如果Xcode导入了名为“Images”的文件夹,而不是具体的文件,那就是你操作错了,不要选择文件夹,而是选择Images文件夹中的所有文件,你可以重新导入试试,记得把错误导入的文件夹删掉。

1x,2x,3x
目前资产目录中的所有图片都只是2x尺寸,但是你同时也可以指定1x和3x尺寸。同一个图像的不同尺寸图片可以让你支持多种iPhone和iPad的分辨率。
1x的图片是用于低分辨率设备的,它们的像素又大又笨。能够运行iOS 10的低分辨率设备已经不存在了,它们实在太老旧了。所以你并不需要1x的图片。除非你的app需要支持iOS 9或者iOS 8。
2x适用于高分辨率的视网膜屏(Retina screens)。它包含大多数的iPhone,iPod,iPad型号。视网膜屏的图片的大小是低分辨率的2倍,因此它们叫2x。你刚才导入的图片都是2x的。
3x适用于超级高分辨率的视网膜HD屏(Retina HD screen),比如iPhone 6s Plus和iPhone 7 Plus。
这里有一种对图片的特殊命名方式的公约。如果文件名以@2x或者@3x结尾,那么它们就被认为是用来支持Retina或者Retina HD屏幕的。低分辨率的1x不需要特殊结尾(不需要添加@1x)。

设置壁纸

让我们开始把这个毫无生机的白色背景换成好看的一个。

打开Main.storyboard。找到对象库(Object Library),搜索Image View(你可以在对象库底部的搜索栏内敲入“image”,用来快速查找Image View)。

对象库中的image view组件

拖一个image view到用户界面上面,放在那里并没有关系,只要在Bull's Eye的View Controlller内部就行。

拖一个Image View到View Controller

选中刚拖入到image view,进入到尺寸检查器(Size inspector),属性检查器右边的那个,将X和Y设置为0,Width(宽)为568,Height(高)为320。

这样就可以使image view覆盖整个屏幕。

image view的尺寸检查器

打开image view的属性检查器(Attributes inspector)。在顶部有一个选项叫“Image”,在它的下了框里选择“Background”。

这一步的作用是将资产目录中的“Background”图片加载到image view上。

将Background图片显示在image view上

这里现在有一个问题,这个图片把其他的控件都覆盖掉了。解决这个问题非常简单,你只要把图片放到其他视图的后面就可以了。

在Xcode的顶部找到Editor菜单,选择Arrange->Send to back。

这里Xcode经常会给你找麻烦,你会发现Send to back是灰色的并且无法选取(Xcode的bug之一),如果出现这种情况,你就先取消选择image view,然后再点击选定一次。然后再去Editor菜单选择Arrange->Send to back,反复多试几次。

另一个方法是,在大纲面板中,将image view拖到顶部,仅仅在view的下面,也可以达到同样目的。

你的界面应该看上去是这个样子了:

新的背景图片

我们还有另一个界面About View Controller,记得吗,对这个界面重复上面的步骤,把它的背景也变的酷一些。

运行app,欣赏一下新的界面。

轮到标签了

因为背景图片的颜色非常深,标签上的字已经看不清楚了。幸运的是,你可以改变标签的颜色,并且你可以把它们的字体变的非常漂亮。

还是在storyboard中,选择顶部的那个标签,就是最长的那个,打开Attributes inspector,然后点击Color项目。

设置标签文本的颜色

你会看到一个颜色选择器。这里有很多选择颜色的方法。我喜欢用滑条来选择颜色(第二个标签页,图标是三个滑条的样子)。如果你看到所有滑条的颜色都是灰的,那么就将颜色滑条上面的下拉框里选择为RGB Sliders就好了。

选择纯白色,Red:255,Green:255,Blue:255,Opacity(不透明):100%。(其实三个滑条的默认值就是255,但是这样你看不到颜色的变化,随便拖一下其中一个滑条,再拖回到255就好了)

还是在Attributes inspector中,找到“Shadow”项目。它可以给标签添加微妙的阴影效果。默认值为全透明,所以你看不到阴影。我们需要选择纯黑色,Red:0,Green:0,Blue:0,Opacity(不透明):50%。

注意:有时你改变Shadow(阴影)属性的颜色的时候,标签的背景色也跟随改变了,这也是Xcode的一个BUG,如果发生了这种事情,就把Shadow还原为默认的透明色,然后多试几次。

改变Shadow Offset(阴影偏移),将Horizontal(水平)置为0,Vertical(垂直)置为1。这样就将阴影放在标签的下面了。

Shadow Offset

这个阴影的效果非常的微妙,如果你不确定它真的存在,那么就将Shadow Offset的值在1到0之间反复变化,并且凑近了看,你能看到一些区别的。就像我说的那样,它非常微妙。

系统的默认字体是自动选定的,在iOS 10上,默认字体是San Francisco。这是一种非常好看的字体,但是不是我们需要的。

字体选择器

将Font选择为Custom。这样Family就可以使用了,将Family选择为:Arial Round MT Bold。Size调整为16。

设置标签的字体

标签还有一个AutoShrink(自动收缩)属性。确保将它设置为Fixed Font Size(固定字体大小)。

如果启用AutoShrink,那么AutoShrink会在文本字体太大的时候动态调整字体大小,使字体大小适应标签,这对某些app非常有用,但我们这个不是。相反,你应该固定字体的大小,而让标签的大小去适应字体。

还是选中这个标签,在键盘上使用command加=组合键,或者在Editor菜单中选择Size to fit Content(大小适应内容)。
(如果Size to fit Content是灰色的,不能选择,那么就取消选定标签,然后再次选定标签多试几次。很多时候Xcode都会搞不清楚你选定的到底是啥,真是弱智。)

操作完后,这个标签应该明显的变大或者变小了,完美的适应你的文本内容。如果你在之前改变字体后,文本被截取了,没有显示全的话,现在应该又重新正常显示了。

你不需要像这样一个个的设置标签,那样太麻烦了。你可以一次性选取多个标签然后整体进行设置。

点击Score标签选定它,然后按住command键再点击Round标签,这样你就一次选择了两个标签,然后对它们进行下列设置。

1、将颜色设置为纯白色,100%不透明。
2、设置阴影为纯黑色,50%不透明。
3、设置阴影偏移为水平0,垂直1.
4、设置字体为Arial Rounded MT bold,大小16.
5、设置自动缩放为Fixed Font Size。
(如果忘了在那里设置,可以翻翻上面的内容)

如你所见,在我的storyboard上,文本已经无法完全容纳到Score和Round标签中去了。

字体太大了,超出了标签的范围

你也可以手动去拖动标签的外框,调整它们到合适的大小,或者你可以简单的使用command加=组合键实现Size to Fit Content。我比较喜欢后者,因为这样很容易。

小帖士:Xcode很聪明,会记住你最近使用过的颜色。你可以在最近使用过的颜色中快捷选取它们。

点击color选项最后的一个上下双箭头的符号,会弹出一个菜单:

快速选取颜色

练习:你还剩下几个标签需要处理。重复一下刚才做过的几个步骤。它们应该是白色的,有点阴影效果,字体都为16,之后在调整滑条两端的标签内字体为14,显示Score和Round具体数据的那两个标签字体应该是20,显示目标随机数的那个标签字体也应该是 20.

因为你改变了标签的大小,所以你之前精心的布局可能会显得有些乱了,不再那么整齐。

目前,我的界面是这样的:

调整标签外观后的界面

现在总算看起来有点模样了。顺便说下,去试试其他的字体和颜色,尝试多做些调整,这是你的app,应该由你做主。

按钮

改变按钮的外观和标签的操作很相似。

点击Hit Me按钮。在Size inspector(尺寸检查器)中设置它的Width(宽)为100,Height(高)为37.

拖动Hit Me按钮的位置到背景图片的中心圆点处。

按钮是只有文本而没有边框的。你可以将按钮自定义为你想要的任何样式。

还是选中Hit Me按钮,打开Attributes inspector(属性检查器),找到Background,然后选择Button-Normal作为背景图片。

设置字体为Arial Rounded MT bold,大小20.

设置字体颜色为red:96,green:30,blue:0,opacity:100%。就是深棕色。

设置阴影为纯白色,opacity:50%.阴影偏移Width为0,Height为1.

透明度
任何颜色的opacity(不透明)被设置为低于100%时,都会有透明效果,如果opacity为0%,就是完全透明,这时可以透过上层的颜色看到底层的颜色,会弄出一些艺术效果。(稍微玩过photoshop或者修过图的朋友应该都清楚吧。_

这就是Hit Me按钮“default(默认)”状态的设置:

Hit Me按钮默认状态下的各种属性

按钮拥有多种状态。但你点击按钮,手指还没有离开屏幕时,按钮应该显示一个被按下的状态,好让你知道你确实点击到了它。这就是大家都知道的‘高亮’状态,这是给用户的一个非常重要的可视化的提示。

还是选中Hit Me按钮,找到State Config选项,然后选择其中的Hightlighted(高亮)。现在就可以进行对高亮状态的属性设置了。

在Background选项中选择Button-Hightlighted作为高亮状态下的背景图案。字体颜色为red 96,green 30,blue 0.(注意这里字体颜色一定要重新设置,因为高亮状态的属性和默认状态的属性是相互独立的,你对默认状态做的操作对高亮状态都需要重新做一遍,它们可以一致,也可以不一致,原则是美观至上。)

Hit Me按钮的高亮状态下的属性设置

你可以在属性检查器的Control分节中测试HightLighted效果,这里可以通过勾选复选框来观察按钮的各种状态下的效果:

勾选Hightlighted观察其效果

重要:记住在观察完效果后,一定将Hightlighted复选框取消选中,恢复原样,否则app在一开始会显示按钮的Hightlighted状态。

这就是Hit Me按钮的全部设置,对Start Over按钮的设置和它基本类似,除了你需要用一个图标来代替按钮的文本。

选择Start Over按钮,进行如下设置:

1、打开属性检查器,将Type选项由System改为Custom。

2、删除文本内容Start Over。

3、在Image选项中选择StratOverIcon(这些都是你导入图片的文件名)作为该按钮的背景图片。

4、Background图片选项选择为SmallButton

5、在尺寸检查器中设置Width和Height为32.

这个按钮不需要设置hightlighted状态,UIKit会自动处理。如果你没有为一个按钮设置hightlighted状态,那么当你按下它时,UIkit会自动将其颜色加深。

接下来是(i)Button,这次选择InfoButton作为Image的选项。

用户界面基本搞定了,只剩下Slider(滑条)。。。

基本成型了

滑条

非常不幸,你只能在界面建造器中对滑条做一点点改变。我们这个app需要的更多的自定义滑条设置,比如放置背景图片,更改滑条上的浮标,都需要用代码来实现。

你在界面建造器中做的一切设置都可以通过代码实现。比如设置按钮的颜色,可以通过给按钮发送一个setTitleColor()消息来实现。

无论如何,通过代码实现总不如在界面上直接设置来的更好,不但更快,而且你可以实时的观察效果。但是对滑条而言,我们没有选择(希望后面的Xcode版本加大这方面的支持吧)。

打开ViewController.swift文件,并且在viewDidLoad()中添加以下代码:

let thumbImageNormal = UIImage(named: "SliderThumb-Normal")!
        slider.setThumbImage(thumbImageNormal, for: .normal)
        
        let thumbHighlighted = UIImage(named: "SliderThumb-Highlighted")!
        slider.setThumbImage(thumbHighlighted, for: .highlighted)
        
        let insets = UIEdgeInsets(top: 0, left: 14, bottom: 0, right: 14)
        
        let trackLeftImage = UIImage(named: "SliderTrackLeft")!
        let trackLeftResizable = trackLeftImage.resizableImage(withCapInsets: insets)
        slider.setMinimumTrackImage(trackLeftResizable, for: .normal)
        
        let trackRightImage = UIImage(named: "SliderTrackRight")!
        let trackRightResizable = trackRightImage.resizableImage(withCapInsets: insets)
        slider.setMaximumTrackImage(trackRightResizable, for: .normal)

上面的代码配置了4张图片,两张用于浮标,两张用于拖拽的轨迹。

浮标和按钮类似,有normal和highlighted两种状态,当你按下它时和不按它时表现不同。

并且使用了两种不同的图片作为拖拽的轨迹,向右拉呈现绿色轨迹,向左拉呈现灰色轨迹。

运行app。看看你这些幸苦有没有白做。

漂亮的游戏界面

.png or not
如果你回想一下,你就会发现我们导入asset catalog(资产目录)的图片文件名都是“SliderThumb-Normal@2x.png”这样子的。
而当你创建UIImage对象的时候,你使用的是asset catalog(资产目录)列表中的名称,比如“SliderThumb-Normal”。
这就是说你可以省略@2x和.png。

小帖士:Xcode 8有一个非常先进的特色,可以使你很简单的在代码中添加图片。比如下面这行语句:

let thumbImageNormal = UIImage(named: "SliderThumb-Normal")!

你可以在写代码的时候仅仅敲入下面一点内容:

let thumbImageNormal = Sli

然后Xcode会自动匹配出可能关联的所有以Sli开头的项目,包括以Sli开头的所有图片并且在一个下拉列表中展现出来:

Xcode自动匹配出的项目

这时你可以在下拉列表中直接选择需要的图片,然后你的代码会变成下面这个样子:

图片直接变成代码的一部分了

务必动手试一试这个功能,把图片的略缩图直接包含在代码里实在太酷了。

使用一个HTML的网页作为AboutViewController的内容

我们还有些剩余工作,关于About界面的。

练习:试着自己动手把About界面上的close按钮弄成和Hit Me按钮一样。你现在应该可以独立完成这个工作了。如果你进行的不顺利的话,就回过头去复习一下关于Hit Me按钮的内容。

完成以后选择text view然后点击键盘上的Delet键。没错,我们已经不需要它了。

用一个web view取代它(你可以在对象库(object library)中找到它,记得调整下大小,否则窗口会非常小)。

web view的作用就和它的名字一样,是用于展现网页内容。你要做的就是给他一个网页的URL地址。web view object(网页视图对象)的名字叫做UIWebView。

在这个app中,你将展现一个来自应用程序内部的静态网页,你并不需要实际的链接网络并且下载某些东西。

在工程导航器中,右键单击BullsEye group(那个黄色的文件夹)。在右键弹出的菜单上选择“Add Files to BullsEye”。。。

在工程中添加新的文件

在文件选择窗口,选择资源文件夹(就是刚才你选择图片的那个文件夹)中的BullsEye.html。这是一个HTML5文档,其中包含游戏的介绍信息。

添加新的文件

确保选中图中箭头处的Copy items if needed,以及Add to targets中BullsEye复选框式被选中的。(如果你看不到这些选项,点击图中位于底部的Options按钮)

确认完选项后点击Add按钮,文件就添加到工程中了。

打开AboutViewController.swift,添加一个web view的outlet:

class AboutViewController: UIViewController {
    @IBOutlet weak var webView: UIWebView!
    . . .

然后在storyboard中,将UIWebView和这个新的outlet链接起来。最简单的方法是按住ctrl然后从About View Controller拖向Web View。
(如果你拖反了,从Web View向About View Controller拖,那么你会得到一个错误的链接,当app运行时,web view什么都不会显示)

回到AboutViewController.swift,添加一个viewDidLoad()方法:

 override func viewDidLoad() {
        super.viewDidLoad()
        if let url = Bundle.main.url(forResource: "BullsEye", withExtension: "html") {
            if let htmlData = try? Data(contentsOf: url) {
                let baseURL = URL(fileURLWithPath: Bundle.main.bundlePath)
                webView.load(htmlData, mimeType: "text/html",textEncodingName: "UTF-8",baseURL: baseURL)
            }
        }
    }

这段代码的作用就是读取HTML文件到web view。

这段代码看上去令人恐惧,但是它做了什么并不是很难懂:首先它在应用程序包中找到BullsEye.html文件,然后将它读取到一个数据对象(Data object)中,并且最后它请求web view展示这个Data object中的数据。

运行app,点击info按钮。About界面上显示了关于游戏规则的介绍,这一次不是文本而是来自html文档的内容。

新的About界面

支持3.5寸屏幕

目前你的屏幕设计是基于4英寸屏的,比如5,5c和iPhone SE,它们仍然是今天的主流设备。

在我们早期版本的课程里我们是用iPhone4s进行讲解的。这种设备现在已经过时了,它只有3.5英寸屏幕。

4英寸和3.5英寸的宽是相等的,都是320个点,但是4英寸的Retina屏的高是568,而3.5英寸的高仅为480个点,相差88个点。

然而,讨论这个是无意义的 ,iOS 10并不支持3.5英寸的设备。注意一下,在Xcode窗口顶部的模拟器列表中并没有iPhone 4s这个设备类型,它已经被抛弃了,你已经无法在4s上运行app了,哪怕是在模拟器中。

但是学习如何使app在这些小屏幕上工作还是有必要的,基于以下几个理由:

1、当你在iPad中运行BullsEye这个app时,它是以3.5英寸模式运行的,iPad有一个特殊的仿真模式,可以运行一切iPhone的app,但是由于屏幕尺寸限制,这种仿真运行采用的是3.5英寸模式。

2、如果你以开发iOS程序为生,那么你需要支持尽可能多的设备类型,哪怕是已经落伍的4s现在仍然有大量人在使用,这些人也是你的用户。

3、这是一个学习Auto Layout(自动布局)的好机会,这是UIKit的一个核心技术,它可以轻易的使你的app支持多种不同尺寸的屏幕,包括大型的6s plus,7 plus和iPad。

所以即使官方的iOS 10已经不支持4s了,我们仍然可以让它运行我们的app。

想要看3.5英寸的app是什么样子的,可以在iPad上运行看看,你可以将模拟器更换为(iPad Air 2)。你可以在Xcode窗口的顶部找到切换模拟器的地方:

点击写着iPhone SE的地方就可以切换模拟器了
切换为iPad Air 2

选择好之后重新运行app,也许和你期待的有些不同,屏幕被截短了:

3.5英寸无法显示全部屏幕

(注意,这个教程更新的时候是Xcode 8,但是现在已经是8.2了,也许你在iPad Air 2中看到的模拟运行结果是一个完整的屏幕,不过这没什么,你还是需要做这个练习)

小帖士:也许iPad Air 2的模拟器窗口会非常大,你可以用command加数字1到5调整模拟器窗口的大小。

Universal app(通用app)
许多app都是Universal(通用)的,就是说它们同时支持iPhone和iPad,这种app很好的利用了iPad的大屏优势。
但是BullsEye不是一个通用型app。iPad在处理这种类型的app时,都使用一个3.5英寸的仿真环境来运行它们。
把BullsEye做成通用型app当然是更好的,但是涉及的内容无法在这个教程中讲清楚,你要先学会走,再学跑,我们会在最后一个教程中讲如何实现同时支持iPhone和iPad的通用型app。

界面建造器中有非常便利的工具使这个游戏适合3.5英寸屏幕。

打开Main.storyboard。打开View as面板并且选择其中最右边也是看起来最小的那个设备(选定后你需要重新将方位选择为landscape)。

预览3.5英寸屏幕效果

storyboard中的预览效果和iPad模拟器中运行效果应该一致(其实不一定一致了已经,不要在意细节),右半部分界面被截短了。现在你可以看看storyboard是如何让它适应3.5英寸屏幕的。

首先,来调整背景图片。现在的背景图片有568点宽,但是3.5英寸屏幕只有480点,所以图片被截短了。

这就是等待Auto Layout拯救的事情之一。

在storyboard中,选择View Controller中的Background image view,并且点击Xcode窗口底部的一个按钮Align(对齐):

比较不起眼,好好找找

这个菜单可以使将一个视图和场景中的其他视图对齐(这里的视图和对齐概念比较宽,比如可以定义两个视图相距多少个点也叫对齐)。

背景图片的最佳位置应该是中心处的原点永远位于屏幕的中心。Auto Layout实现它的方法是创建两个alignment constraints(对齐约束),一条水平的(horizontal),一条垂直的(vertical)。

使用Auto Layout的方法就是定义不同视图之间的关系,就是所谓的约束。当你运行app时,UIKit会评估这些约束,并且最终计算出屏幕上的最终布局。这可能听起来很抽象,我们还是通过练习来说明吧。

在Align菜单中,选中Horizontally in Container(水平居中)和Vertically in Container(垂直居中)两个复选框:

使用align菜单使背景图片居中

将Update Frames选择为Items of New Constraints。

然后点击Add 2 Constraints结束。现在背景图片应该居中了。可以撤销并且重做几次,看看效果。

添加的alignment constraints(对齐约束)在界面上显示为蓝色的线:

蓝色的线就是你添加的约束

在纲要面板上同样出现了新的项目,叫做Constraints。

新添加的约束

这里应该列出了两条约束,一条是Background.centerX,一条是Background.centerY。

⚠️:根据你在Xcode中观察约束的地方不同,它们的名字也许是“Align Center X”和“Align Center Y”。

在iPad和iPhone SE中分别运行下app,看看背景图片是不是都正好居中了。

你也可以在View As面板中切换回iPhone SE,看看背景图片效果。

在About界面中同样对背景图片添加两条这样的约束,这样背景图片就居中了,当然close按钮和web view并没有变化。

在storyboard中,拖动close按钮到中央位置,在close经过中央位置时,会有一根蓝色的辅助线出现,自己多拽两次就明白了。然后在把它拖到最底下,不是屏幕的最下面,而是拖到能看到底部的蓝色辅助线为止。

蓝色虚线可以引导你放置界面元素

和前面一样,你要创建向中心对齐的约束用于保持close按钮始终在屏幕中间,不管屏幕多宽。

点击close按钮选中它。然后打开Align菜单,选择Horizontally in Container然后点击Add 1 Constraint。

现在界面上出现了一条红线,代表这个约束,并且一个红色的框框把按钮围起来了。

close按钮有一个红色的约束

这是有问题的:所有代表约束的线都应该是蓝色的,红色暗示着这个约束哪里出了问题,通常这个问题就是约束条件不够。

对每一个视图(按钮这些也叫做一个视图)都应该有足够的约束定义它们的位置和大小。对于close按钮而言,它的大小已经确定了,你早先在Size inspetor(尺寸检查器)中输入好了,但是它的位置只有一条约束,就是X轴上的位置,你必须同时定义它在Y轴上的位置。

这里有两种不同类型的约束。目前为止你所做的都是alignment constraints(对齐约束),这里还有另一种约束叫做spacing constraints(间隔约束)用于确定两个视图见的间距保持恒定。间隔约束在Pin菜单(就是Align右边的那个)中操作。

仍然选定close按钮,点击Pin菜单:

用于添加间隔约束的Pin菜单

这个菜单使你可以将一个视图“钉”在相邻视图的固定位置。对于close按钮而言,你希望它总是距离屏幕的底部20个点的距离,所以我们要把它钉在那里。

在Pin菜单中,在Spacing to nearest neighbor(最近的邻居)部分,这里有4条线代表这个视图可以被固定的4个方向。因为你希望将close按钮钉在底部,所以你选择下方的红色虚线,将它点一下,变成红色实线。

红色的实线决定固定的方向

然后在红色实线下方的方框中输入20,然后点击Add 1 Constraint,这样就永远把close按钮固定在离屏幕底部20个点的位置上了。

现在所有的线都应该是蓝色了,这代表一切OK:

所有的约束都生效了

如果你没有看到所有的线都变蓝,而是看到一些橙色的线,那么你的Auto Layout仍然有点问题:

视图没有依据约束的位置放置

这种情况出现在约束生效但是视图的位置不对的情况下,橙色的虚线方框是视图应该在的位置(比如你定义了水平居中,但是你把按钮放到非常靠左的地方,就会出现这种情况,这个橙色虚线框说明这才是这个按钮该呆的地方,而这个位置是根据你定义的约束计算出来的)。

为了解决这个问题,再次选中close按钮并且点击Resolve Auto Layout Issues(解决自动布局问题)菜单,就是Pin右边的菜单,选择其中的Update Frames(更新框架):

解决自动布局问题

⚠️:你有可能看不到Update Frames这个选项,此时你可以点击选中close按钮并且使用option加command加=组合键来实现这个目的,或者可以在顶部菜单的Editor菜单中找到Update Frames选项。

close按钮的位置完美结局了,不管它在3.5英寸屏幕,还是在4英寸屏幕上close按钮的相对位置都是一致的。

⚠️:如果你的界面上没有任何约束会怎么样?在这种情况下,Xcode会在生成app时自动添加约束。这就是之前我们很长时间没添加约束而且还能正常运行的原因。
然而,这些默认的约束不总是和你期待的效果一致。例如,它们不会自动调整自己的尺寸去适应3.5英寸屏幕。如果你需要这样做,那么你就要自己添加约束(Auto Layout不能读取你的想法)。
一旦你在添加了一条约束,那么Xcode不会再进行自动约束。你必须自己添加完剩下的所有约束以便UIKit知道每个视图的尺寸和位置。

About界面上还剩下一件事情,那就是web view。

点击选定web view然后打开Pin菜单。首先,确保Constrain to margins复选框没有被选中。然后选择全部四个方向的红线,选好后红线会由虚线变为实线,设置它们的值为20,出了代表底部的那个设置为8:

为web view创建视图

在Update Frames中将选项选择为Items of New Constraints。这样可以将web view的尺寸缩放到合适的尺寸,符合你刚才添加的4条约束。

选择正确的Update Frames选项

没有这些设置的话,Xcode会显示一些橙色的线抱怨你,因为web view的位置和尺寸和你约束中的说明不一样。你可以像刚才处理close按钮那样在Resolve Auto Layout中解决这个问题,但是为什么一步做好的事情要分成两步走呢?

点击Add 4 Constraints结束。

现在web view有4条约束了(蓝色线条的那种):

web view的约束做好了

其中三条将web view固定在main view上,这样它始终随着屏幕的变化调整自己的大小,一条固定web view与close按钮的距离始终为8个点的距离。在任何情况下,这4条约束都能准确的说明web view的尺寸和位置。

⚠️:你得到的结果肯定和上面显示的不一样,这是书中的一处疏漏,因为在Pin菜单中距离底部8个点,指的是离main view8个点,你得到的结果也许是这样的:

web view覆盖了close按钮

虽然实际运行的时候close按钮还是显示在web view之上,不会被它覆盖,但是如果web view中的文字很多,就会被close挡住看不到,虽然说可以向下滚动web view,但是始终会有一部分内容被close挡住,要达到教程中的效果,我们应该这样做。
首先点击选定web view,可以直接在纲要视图中选定。
选定web view

然后打开web view的Size inspector(尺寸检查器):
web view的尺寸检查器

找到图示中红色箭头指向的Bottom Space to:。。。这一条,这一条就是指定web view和main view底部距离的,点击选中这一条,然后按delete删除它。
然后在纲要视图中按住ctrl,将web view拖向close按钮,在弹出菜单中选择Vertical Spacing(垂直距离)
按住ctrl将web view拖向close按钮会弹出这个菜单

这一条约束的作用就是指定web view和close按钮的垂直距离,然后回到web view的尺寸检查器,会发现多了一条约束,叫做Bottom Space to:Close。

点击图中的Edit按钮:
设置web view与close按钮之间的约束

将Constant设置为8。
然后还是选中web view,在Xcode顶部菜单中选择Editor->Resolve Auto Layout Update->Frames。然后你的界面应该变得和教程中的一样了_
调整后的效果

回到游戏的主界面,它也需要经过调整才能适应更小的屏幕。

首先,你需要将界面上的所有控件都向左拖,使它们在3.5寸屏幕上全部可见,目前很多控件是在屏幕之外看不到的,而且因为看不到,所以你想把它们向左拖也是不可能的。

幸运的是,界面建造器有一个非常棒的预览界面可以帮助我们。

你需要再次使用View As面板,在View As面板中将设备重新选择为4英寸的iPhone SE。现在所有元素都可见了。

确保选中mian View Controller:


就是这个

然后点击Xcode顶部工具栏中的两个圆圈套在一起的按钮(辅助编辑器):

就是这个,在右上角

然后在跳转栏选择Preview并且跳转到Main.storyboard(Preview),你要用鼠标点击一下Preview才能看到这个选项:

中间橙色箭头的地方就是跳转栏

现在屏幕被分成了两个。左边的是storyboard,右边的是预览面板,预览面板可以展示各种不同类型的设备界面效果。

也许目前你屏幕上的各种界面都被挤在一起看不到了,你可以先关掉一部分不要的界面:

取消选中红色箭头处的两个方块按钮,可以暂时关闭屏幕的左右两边

如果你的预览界面不是3.5英寸模式,你可以先选中预览界面然后delete将它删除掉:

点击白色空白的部分可以选中预览界面

然后点击预览界面左下角的一个小小的‘+’号的按钮,会弹出一个选择设备的菜单,选择iPhone 4S,现在你有一个3.5英寸的预览界面了,只不过是竖屏模式的。

点击下图中的方向选择按钮,就可以转换为横屏模式了:

转换预览界面的方向

现在你可以看到一个完美的分屏显示了。

在storyboard中(左边的那个),将所有控件(按钮,滑条,标签都往左拖),可以在右面观察效果,注意摆放整齐,不要拖乱了(我们接下来可不会一个个都调整,建议你先把Hit Me按钮放到背景图片正中心的原点处,然后以Hit Me按钮为参考调整其他视图的位置):

左边为storyboard右边为预览

当然,虽然满足了3.5英寸屏幕,但是对与4英寸屏幕而言,它们显得太靠左了,都挤在一起。你将通过把所有的标签,按钮,滑条全部装到一个“集装箱”里,来统一使它们位于屏幕的正中央,不管是多大的屏幕。

选择所有的标签,按钮,和滑条。你可以按住command键然后一个个点击它们选择,也可以直接在纲要面板中按住ctrl一个个选,或者先选第一个然后按住shift选最后一个。

在纲要面板中选择会简单一些

你需要选择除了background以外的所有视图。

然后在Xcode顶部菜单中选择Editor->Embed In->View。这会将选择的所有视图整合到一个容器中,形成一个大视图:

所有视图都被嵌入到了一个容器视图中

这个新的视图是纯白色的,这并不是你最终要的结果,但是它使你添加约束的工作简单了许多。

选择这个刚增加的容器视图并且打开Pin菜单。选中Width和Height复选框,并设置Width为464,Height为286,然后点击Add 2 Constraints。

设置Width与Height

界面建造器现在会画出几条线来表示你刚定义好的Width和Height,但是它们是红色的。不要惊慌!这只是意味着还有其他约束没有添加而已。没关系,我们接下来会做完这些工作。

还是选中这个容器视图,打开Align菜单。选择Horizontally in Container和Vertically in Container选项。在Update Frames选项中选择Items of New Constraints。点击Add 2 Constraints。

现在所有的线条都应该变成蓝色了,并且视图完美的居中摆放。

最后,还是选中容器视图,将它的背景色选择为Clear Color(就是100%透明,也就是将Opacity设置为0%)。

现在你有了一个可以在3.5英寸屏幕和4英寸屏幕上都完美运行的布局了,试试看:

3.5英寸屏幕和4英寸屏幕

添加自动布局花了我们不少时间,为了摆放界面元素的位置所增加的约束看起来也没有直接拖拽它们简单。

但是这给了你更加灵活的能力,当你需要应对多种不同设备类型的时候,你会发现它的强大。

你会在我们剩余的三个课程中学习更多关于Auto Layout(自动布局)的知识。

支持iPhone 6和更高的型号

让游戏支持3.5英寸小屏幕是一会事情,但是支持更大的设备比如4.7英寸的iPhone 6,7和5.5英寸的6 plus 和 7plus又是另外一回事了。

我们先来试试看,你可以使用View as:或者预览面板先看看iPhone6,7或者plus 6,7的效果。

你看到了什么?这是它在iPhone 7上运行的效果:

在iPhone 7上的运行效果

记住,你可以使用command➕1-5来调整模拟器窗口的大小。

我猜这个运行效果极不算坏,也不能算好。游戏界面并没有占满屏幕空间。如果界面上所有的东西都变的更大一些的话,就更好了。

有很多方法可以搞定这个目的,但是我们要用最简单的一种方法,一种叫欺骗的方法。

app有必要默认支持iPhone 6及更大的屏幕。

如果没有默认,那么iPhone6,7 plus会自动按比例缩放app,使得其可以填满整个屏幕。这样做使得哪些老旧的app仍然在这些大屏设备上可用。这对我们而言是非常棒的,因为按比例缩放正是我们要的最省力的方法。

app对iPhone 6默认支持的功能由一个叫做launch screen的东西提供。其实你已经见过它了,只是不认识而已。

⚠️:启动app时,往往需要花一点时间。从点击app的图标,到真正可以使用app之间的这点时间内,你可以使用launch screen做个无缝衔接。launch screen会占用屏幕,直到app被完全加载。
如果没有launch screen占用屏幕,那么在app被加载出来前,iPhone屏幕会空白一片,这不是非常好的选择。
很多开发者利用这个特点来显示一个界面用于展示公司的log(guang gao _),但是更好的用法是显示一个和app界面完全一样的静态图片(让用户以为app启动速度奇快)。
你也可以使用一种叫做XIB的storyboard文件,也叫“nib”,来代替图片,这是一种和storyboard很像的东西,只是它只能包含一个单屏幕的设计。

app的launch screen在LaunchScreen.storyboard文件中设置。目前这个storyboard包含一个空的view controller,显示一个空白的屏幕。你每次运行app都会看到这个空白的屏幕,所以你可能没有留意到它的存在。(仅仅是为了好玩,拖一个标签到这个storyboard上,运行app看看效果)

为了在iPhone 6及更高设备类型上获得自动缩放效果,你需要删掉这个storyboard文件。换句话说,你通过删除launch screen放弃了使用大屏设备上的额外的像素。而为了不浪费屏幕空间,UIKit会自动缩放游戏界面填满屏幕。

在工程导航器中,选择LaunchScreen.storyboard然后点击delete删除它。当Xcode要求确认时,选择Move to Trash(扔到垃圾筐)。

这样做还不够,你还需要告诉Xcode不再使用这个launch screen文件。

打开Project Setting界面:

点击这里打开

选择顶部的gengral标签,然后向下滚动,找到App Icons and Launch Images部分,然后将Launch Screen File选项中的内容清空:

必须将Launch Screen File选项清空

为了完全的清空Xcode对launch screen文件的记忆,按住Option键,选择Xcode顶部菜单Product->Clean Build Folder。然后点击Clean确认清除(一定要按住Option不放,才能看到这个菜单)。

运行app,现在你已经看不到launch screen了。如果还能看见,那么在选择Simulator->Reset Contents and Setting,重置模拟器(注意,模拟器必须为当前显示的窗口,你才能看到Simulator菜单)

你也许会惊讶。我们的app现在在iPhone 6s或者7模拟器中现在是这个样子:

在iPhone 6s或者7中运行

我们看到两边出现了两条黑边。好奇怪!

解决方法是添加一个4英寸的launch图片到工程中。这并不是一个XIB或者storyboard文件,仅仅是一个有着木头纹理的静态图片。

在工程导航器中,右键单击BullsEye group(黄色文件夹图标的那个)然后选择Add Files to “BullsEye”。

然后选择随书附件中resources目录中的Launch Images目录,里面有一个Default-568@2x.png文件。这个图片和app的背景图片一模一样,只是是竖着过来的(launch image总是竖着的)。

确保Copy items if needed复选框被选中(如果看不到就点击Options选项),然后点击Add就将这个图片添加到工程中了。这就是我们全部要做的事。

运行app,你会发现app从启动到加载完成之间,平滑了许多。这只是众多小细节中的一个。最好的是,现在我们的app在iPhone 6s和7上的表现非常完美了。

⚠️:在大屏设备上,简单的放大尺寸,对我们的BullsEye来说是没问题的,但是大多数其他的app希望你可以充分利用屏幕上剩余的空间。iOS有多种方法帮助我们实现这个目的,比如Auto Layout或者Siza Class,我们会在下一个课程中学习相关内容。

Crossfade(淡入淡出)

我不能在还没有涉及Core Ainmation时就结束这个课程。这个技术可以使你非常简单的为你的app创建一些动画效果,仅仅需要几行代码。添加一些巧妙的动画,可以使你的app充满生气。

你要在玩家点击Start Over按钮后添加一个简单的淡入淡出效果,这样在转换到第一回合时,就不会太生硬了。

打开ViewController.swift,在import UIkit下面添加:

import QuartzCore

Core Animation技术居住在它自己的框架中,就是QuartzCore。用这条import语句你就可以告诉编辑器你想要使用其中的对象。

将StartOver()方法修改为:

@IBAction func startOver() {
        startNewGame()
        updateLabels()
        
        let transition = CATransition()
        transition.type = kCATransitionFade
        transition.duration = 1
        transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
        view.layer.add(transition, forKey: nil)
    }

这里的startNewGame()和updateLabels()是之前就有的,但是CATransition相关的东西是新增的。

这里我们不会介绍太多细节。你只要知道当调用 startNewGame()后,你屏幕上所有发生改变的东西比如重置滑条位置,更新标签的值等,此时都具备了一个淡入淡出效果就足够了。

运行app,移动一次滑条,使它不再位于中间位置,然后点击Start Over按钮,你就可以看到这个动画效果了。

动画效果

The icon(图标)

我们几乎已经完成了全部工作,现在只差一点收尾了。你也许已经注意到了,我们的app图标丑的一吼。该美容下了。

打开资产目录(Assets.xcassets)并且选择AppIcon:

资产目录中的图标组

这里有许多种不同设备类型需要的图标

打开随书附件中的resource文件夹中的Icon文件夹,将Icon-40.png拖到第一个插槽中,就是标记有iPhone Notification 20pt的那一组中的第一个:

拖拽图标到资产目录

你也许想知道为什么是拖拽Icon-40.png而不是Icon-20.png到用于20pt的插槽中。注意一下,这个插槽的名字叫做2x,也就是说它是用于视网膜屏设备的,而视网膜屏的一个点是两个像素。

拖拽Icon-60.png到它旁边的3x插槽中。这是给iPhone 6s plus或者7 plus用的,它们是3x的分辨率。

在iPhone Spotlight & Settings 29pt组中,将Icon-58.png拖入2x插槽,Icon-87.png拖入3x插槽(29 * 2与29*3)。

在iPhone Spotlight 40pt分组中,拖入Icon-80.png到2x插槽,拖入Icon-12d0.png到3x插槽。

在iPhone App 60pt分组中,拖入Icon-120.png到2x插槽,Icon-180.png到3x插槽。

这就是4组为iPhone准备的图标,都放好了。

剩下的图标是为了iPad准备的,虽然这个app没有iPad版本,但是这并不能阻止iPad运行它。所有的iPad都可以运行iPhone的app,只不过显示的画面比较小。所以我们也必须为iPad准备图标。

还是选中AppIcon,在Attributes inspector(属性检查器中)将iPad选项选择为iOS 7.0 and Later。(也许你电脑上会是默认选好的,那样就不用管了),这样你会看到新增出9个图标插槽来。

拖拽图标到相应的插槽中去,注意一下,iPad图标的插槽都是1x和2x的,你可以计算应该将那个图标放入那个插槽。

图标全家福

最后剩下的一个图标Icon-1024.png不是给设备用的,是提交app到apple store时用的。当你提交app到应用商店的时候,会要求你上传一个1024 * 1024的图标。

运行app,并且关掉它。你可以看到模拟器上的图标已经变了。如果没有的话,从模拟器上删除app,在重新运行一遍。(有时候模拟器会讲旧的图标保存下来)

模拟器上的图标

Display name(显示的名称)

最后一件事。你将工程命名为BullsEye,所以图标下面的名称也显示为BullsEye。但是最好显示为Bull's Eye,因为这才是正确的拼写。

图标下能显示的名称长度极为有限,如果你的app名字太长的话,你就需要减少字符。但是对于我们这个游戏而言,这里是有足够的空间容纳名称中的全部字符。

打开Project Setting界面。其中第一行就是Display name选项,将它改变为Bull's Eye。

改变app显示的名称

和其他大多数project settings一样,你可以在Info.plist中找到display name的值。

在工程导航器中,选择Info.plist。

Info.plist中的display name

Bundle display name这一行中就包含你刚刚设置的名称。

⚠️:如果Bundle display name这一行不存在,那么app将使用Bundle name中的值作为名称。这是一串特殊字符 $(PRODUCT_NAME),意思是Xcode会自动选择工程名称BullsEye作为app显示的名称。而设置了Bundle display name后,你可以使用任意名称替Bundle name作为app显示的名称。

运行app,看看图标下的名称改变了没有:

app的名称改变了

太棒了,你的第一个app彻底完成了。

你可以在07-Final App中找到这一小节的代码。这里还有一版叫做08-Final App with Comments的代码,在这个版本里,我添加了很多注释用于说明每一条代码的作用,同时我删除了哪些Xcode模版中自带的,但是对我们没有用的部分代码,使它看起来非常简洁。

推荐阅读更多精彩内容