原文地址 翻译:DeveloperLx
欢迎回到我们的Max OS X新手开发教程的第三部分!
这个关于在OS X上build app的引导性的系列,包含很多的东西(ground)。这些是你已学到的:
- Tooling :在 第一部分 你学到了Xcode的很多方面 - “瞥”(glimpse)了一眼你可以怎么来用它开始OS X的开发。
- App剖析 : second part 第二部分 包含了很多的OS X app在背后如何构建的理论 - 从数据层,到二进制的资源,再到设计UI。
在这个第三也就是最后一部分,你将通过创造你史无前例的第一个app,来潜入(dive deep into)OS X开发的世界!
你将要build的app是一个 magic-8 ball app ,来帮助你做出一天天生活中的困难的决定。你将从涂画(scratch)开始 - 在继续创造app来解决你的所有问题前,首先创造一个“Hello World!” app!
打开Xcode并点击 Create a new Xcode project 来启动你的新app。选择 OS X \ Application \ Cocoa Application :
设置产品名称为 MagicEight ,语言为 Swift 并确保 Use Storyboards 被勾选:
选择一个磁盘上的位置来保存你的项目,并点击 Create 来打开你的空项目。
运行 MagicEight 来检查每一项正在工作:
很好 - 这样app运行起来了,但它确实 没有做任何事 。这样地启动了一个新的平台却没有首先创建“Hello World!”大概是不对的...
OS X app的用户界面是由你使用interface builder编辑的storyboard文件提供的,它让你用一种可见的方式来编辑UI,减少了大量你必须去写的复杂的代码。
通过在左手边窗格中的Project Navigator中选择 Main.storyboard 来打开它:
你会看到现在storyboard已打开在了中部的面板:
模板的storyboard包含三个组件:
- Menu Bar :控制当app运行时出现的菜单。
- Window Controller :管理出现的默认窗口。使用一个或多个view controller来管理它们的内容。
- View Controller 负责window中的一个区域,来展示和处理用户的交互。
在这个引导性的教程中,你将集中你的努力到view controller上。
首先,你需要添加一个label到view controller上来展示你的欢迎文本。label事实上特定的类型是
NSTextField
,它被设计来展示一个单行的不可编辑的文本 - 对“Hello World”来说完美。
为了添加你的第一个label:
- 点击在工具栏中的图标来展示右手边的工具面板(utilities panel)。
- 使用靠近面板底部的图标来打开 Object Library 。这个,就如同它名称所表达的一样,是一个你可以拖拽到storyboard画布上的物品的目录,用来构建你的布局。
- 使用底部的搜索框搜索 label 。
- 你会发现label对象在library中被高亮了起来。
将label从object library拖拽到view controller上:
为了label的文本内容,在Utilities panel中选择 Attributes Inspector 这个tab,找到 Title 域,并输入 Hello World! :
你可以使用attributes inspector来控制OS X提供的不同的组件的外观和行为。找到 Font 入口,点击右边的 T 图标来打开字体面板。改变Style为 Thin ,Size为 40 。点击 Done 来保存你的修改:
将你的眼睛投回到view controller的画布上,你会看到一些奇怪的事:
尽管你已经调整了label的文本和大小,但它仍然是那个尺寸,造成几乎全部的文本消失了。为什么会这样?
你把label放在了view controller的画布上,但你并没有实际地告诉OS X 怎么 来定位它。view controller在storyboard中以一种尺寸出现,但一旦运行在app中,用户是可以改变window的尺寸的 - 因此你需要指定对于 任意 的window的尺寸,这个label该怎么定位。
这听起来是一件相当困难的任务,但幸运的是苹果会帮你(has your back)。OS X使用了一个叫做Auto Layout的强有力的布局引擎,不同view的组件之间的关系被表达为约束(constraints)。这些约束在运行时会计算view中的每个原件的尺寸和位置。
确保这个label已在view controller中被选中,单后在下面的工具栏中点击 Align 按钮。选择 Horizontally in Container ,确定在点击 Add 1 Constraint 去添加约束前,它的值被设置为 0 :
这会确保这个label总是出现在window的中央,无论window有多宽。你会注意到在storyboard上出现了一些“生气的”红色的线(some angry red lines):
这是因为你在水平方向上提供了一个约束来确定label的位置,但你没有提供关于数轴的任何信息。
再一次地,确定这个label在view controller中已被选中,这次点击底部Auto Layout工具栏中的 Pin 菜单。在点击 Add 1 Constraint 前,输入 30 在顶部的约束框中,确保下方的“I”型条是实心红色的:
你会立即看到这个view controller更新了 - 你现在可以看到完全的label了,那个生气的红色的线变成了平静的蓝色:
这就是的你的“Hello World”app完成了(对于现在)。使用“play”按钮来build和运行,检查你的作品(handiwork):
祝贺!尽管它还不是非常个性化?在下一部分,你将发现怎么让传统的问候更友好一点。
在这一部分,你将添加一个text field来让用户可以输入它们的名字,这样你就可以个性化地欢迎他们了。
在行动之前,使用object library来找到并拖拽一个 Text Field 和一个 Push Button 到view controller上。将它们放置到“Hello World!”label的上面:
记住,仅仅将控件放置到画布的上面是不足以让OS X理解,当window的尺寸发生变化时,你想怎么定位它们。你需要添加一些约束来传达你的意愿。
选择text field,然后 按住Command点击 button来同时选取。在storyboard底部的Auto Layout工具栏点击 Stack 图标:
这就创建了一个新的包含text field和button的stack view。stack view会自动地生成要放置和包含view在一条线上的布局约束。你可以使用attributes inspector来配置很多stack view的共同的熟悉。
NSStackView
是从OS X 10.9开始支持的,但在10.11(El Capitan)时收到了一个重大的更新 - 与在iOS中介绍的(
UIStackView
)一致。Stack view在两个平台上是类似的,因此你可以点击
iOS的stack view教程
来加快学习的速度。或是稍等,
NSStackView
的教程将在接下来的几周内发布。
一旦你开始stacking,你就很难停下来了。这次,你要stack你刚创建的stack view和“Hello World!”label到一个垂直的stack view上。
使用底部工具栏左侧的按钮来展示Document Outline,然后找到“Hello World”控件和已存在的stack view。 按住Command点击 它们可以同时选择:
和你刚才做的一样,使用底部工具栏的 Stack 按钮来创建一个新的stack view:
在这个新的stack view被选中时,打开 Attributes Inspector 并设置Alignment为 Center X ,spacing为 20 :
这就确保了这个label合上面的stack view漂亮地居中了,并且它们之间有20个点的间隔。
在将你的注意力转移到处理用户输入的任务之前,有一些布局需要去完成。
stack view处理了它内容中控件相互之间的位置,但它也需要在view controller的view中被定位。选择外部的stack view并使用 Align 这个auto layout菜单,来让它在容器中居中:
使用 Pin 菜单来固定住(pin)住stack view到顶部,并带有一个 30 的距离:
最后,确保text field总是含有足够的用户名字的空间,你要修正它的宽度。
选择text field并使用底部工具栏中的 Pin 菜单,来指定 Width 为 100 。点击 Add 1 Constraint 来保存新的约束:
这样你的布局就较好地完成了 - 它应该看起来像下面这样:
现在你可以将你的注意力转到那些你新添加的控件上了。
选择text field并打开 Attributes Inspector 。在In the Placeholder这里输入 Name :
这个灰色的文本将指示用户这个text field是用来干什么的,当用户开始输入时会立即消失掉。
选择按钮,再次打开 Attributes Inspector 。设置Title为 Welcome :
外观的美化就到这里了。现在是时候将这两个的控制写到代码里了。
OS X使用outlets和actions,提供了从代码和storyboard的UI交互的方法:
- 在代码中, outlet 是一个属性,它被连接到了一个storyboard中的组件。这就让你可以从你的代码中访问在storyboard中的属性。
- action 是一个代码中的类中的方法;当用户和UI中的组件交互时,就会调用它 - 例如,点击一个button。
你将为text field和label添加outlet,当用户点击欢迎按钮的时候,一个动作就会被调用。
你一直工作在的这个view controller早已有了一个骨架类关联到了代码中 - 就在 ViewController.swift 。这是你将要添加outlets和action的地方。
使用工具栏中的按钮打开assistant editor。
这会拆分屏幕,并展示出一个代码文件在storyboard旁边。它本应该展示 ViewController.swift ,但如果不是的话,就使用跳转栏(jump bar)来选择 Automatic \ ViewController.swift :
从storyboard中
右击拖拽
(或
按住control拖拽
)text field直到那条线到了
ViewController.swift
中的
override func viewDidLoad()
的上边:
确保 Outlet 已选中,并叫做 nameTextField :
这将添加下列的属性到
ViewController
中,并在加载view controller时,更新storyboard来自动地连接text field:
@IBOutlet weak var nameTextField: NSTextField!
对“Hello World!”label正确地重复相同的过程,这次指定这个outlet应当被叫做 welcomeLabel :
现在是时间把你的注意力转到button上了。再一次从storyboard中
Now time to turn your attention to the button. Once again
按住Control拖拽
Control-drag
button到你的
ViewController
类上:
这次,改变这个连接到 Action ,并命名为 handleWelcome :
点击
Connect
来完成连接,Xcode会添加下列的空方法到
ViewController
上:
@IBAction func handleWelcome(sender: AnyObject) { }
这个方法会在用户每次点击按钮时被调用。
添加下面这行代码到
handleWelcome(\_:)
方法体中:
welcomeLabel.stringValue = "Hello \(nameTextField.stringValue)!"
这会更新
welcomeLabel
的
stringValue
属性,变为一个由nameTextField的stringValue所构成的欢迎的信息。
stringValue
代表了一个基于文本的控件的当前的值 - 由用户输入的或在storyboard上定义的。
这就是你需要的“Hello World”的个人化版本的全部代码!build并执行来尝试一下。当app启动的时候,尝试输入你的名字到 Name 框中并点击 Welcome 按钮。你会看到你的非常个性化版本的“Hello world!”:
是不是相当酷?好的,乐趣不止于此。下一步你将学习关于添加image到你的app,来创建一个令人惊奇的magic-8 ball工具!
你在教程的开头被承诺了一个magic 8-ball的app,但到目前为止你看到都没有提到过一个球。好了,这就是全部将要改变的事。
8-ball是相当与众不同的,如果没有一些可视化的东西,它无法成为一个令人兴奋的8-ball app。 An 8-ball is pretty distinctive, and it wouldn’t be a very exciting 8-ball app without some visualization. Download 下载 这些你需要的图片,它们会让你的app看起来更漂亮。你会发现在zip文件中有两个图片 - 代表magic 8-ball的两面:
图片资产保存在OS X app中的一个资产目录中。这个目录管理了由app图标和app中的图像所要求的,含有不同分辨率的图像(imagery)。
通过在project navigator中选择 Assets.xcassets 来打开它:
你可以看到这个目录中当前仅包含了一个项目 - AppIcon 。这里就是你可以用艺术品(artwork)来给你的app一个很酷的图标的地方。
你需要将下载到的图片添加到asset目录中。在finder找到图片,并将它们都 拖拽到 asset catalog目录中:
这将在asset catalog中创建两个记录(entry):
注意到这里有三个图片的“单元格”(cell)。这些单元格代表了三种不同的屏幕分辨率 - 标准的 (1x), retina (2x) 和 retina-HD (3x)。通常来说,你要为每一个单元格提供资产,但在这个简单的工程中你提供一个就行了。
你刚刚提供的图像实际上被设计为用作retina分辨率的 (2x),因此将它从左边的单元格拖到中间来:
对两个8-balls资产做同样的操作,asset目录现在看起来就像这样了:
那些图像现在可以被用在你的app中了 - 无论在代码中还是storyboard中。
你已经看过怎么在你的app中展示文本,按钮和文本输入框了,但对于图像呢?进入
ImageView
。
打开 Main.storyboard ,并使用object library来搜索 image view :
将一个image view拖拽到view controller的画布上,放在stack view的底部。注意一条水平的蓝色的线是怎么出现的,它会指示新的image view会出现在stack view的哪里:
打开 Attributes Inspector ,并设置Image属性为 8ball :
这将使用来自asset目录的图像来更新画布:
运行app来查看这个图怎么出现:
好的,这个图出现了,但却被window裁剪掉了一部分。你可以以通常的方式拖拽来改变window的大小,但显然如果当app启动时,window的尺寸就是正确的就更好了,并且要保持一个固定的尺寸。
MagicEight的window的初始尺寸是由storyboard中的view controller的尺寸决定的。在 Main.storyboard 中选择view controller的view,并打开 Size Inspector 。设置Width为 350 ,height为 480 :
注意storyboard的画布现在看起来很奇怪:
这是因为布局的约束需要重新被计算。
使用在底部的自动布局菜单栏中的 Resolve Auto Layout Issues 菜单,并选择 All View in View Controller \ Update Frames :
这会在storyboard中重新运行布局引擎,并更新预览图:
build并执行来查看app的样子:
看起来好了很多。尝试调整window的大小 - 你会看到你仍然可以手动拖动它到对MagicEight的布局无效的尺寸。
在这儿你有几个选项 - 你可以更新布局,让它在window的尺寸改变时也适应得不错(例如对于小的window调整image的尺寸),或者你可以固定window的尺寸。你将使用第二种较容易的方法。
window controller是用来负责管理window的尺寸的。打开 Main.storyboard ,并选择window controller中的window:
打开 Size Inspector ,并设定Content Size的Width为 350 ,height为 480 。然后点击 Minimum Content Size 和 Maximum Content Size ,确认它们和你刚输入的content size相同:
再次build和运行MagicEight,并尝试改变window的大小。你已不再能控制window的大小 - 现在它是固定的。的确,这就是你想要的。
现在你可以将你的注意力转回手上的任务了 - build magic 8-ball的“magic”。好的,接近了 - 首先这里有一些布局要做。
当用户点击8-ball时,你将改变图片到另一面,并展示一条建议。你需要一个新的label来展示这条建议。
打开 Main.storyboard ,并找到view controller场景。使用 Object Library 去找到一个 wrapping label ,并将它从library拖拽到stack view的下方:
你想要将这个label放置在magic-8图片的中央,因此你需要添加一些自动布局的约束。
在 Document Outline 中, 按住control拖拽 Multiline Label 到 Image View 上 Document Outline :
在点击 Add Constraints 前,按住 shift 并选择 Center Vertically 和 Center Horizontally :
这个label不能自动地重新调整位置 - 但你之前已经处理了。使用底部自动布局工具栏中的 Resolve Auto Layout Issues 菜单,选择 All View in View Controller \ Update Frames :
这样就重新调整了label的位置,立即变得明显了,你需要在外表上做一些工作。
选择multiline label,使用 Attributes Inspector 来进行如下的设置:
- Title : Piece of Advice
- Alignment : Center
- Text Color : Keyboard Focus Indicator
- Font : System 20
前往 Size Inspector ,设置Preferred Width为 Explicit ,值为 75 :
为了了解产品最后可能的样子,选择image view,并使用 Attributes Inspector 来设置Image为 magic8ball :
再一次的,点击主view,并使用 Resolve Auto Layout Issues\All Views in View Controller\Update Frames 去更新布局:
这看起来相当好。当每次用户点击8-ball时,你需要响应去发现手势识别。
你可以在代码中创造一个动作,在每次用户点击button时执行。这是可以的,因为button被设计来处理点击 - 但是相同的事对于image view来说却是不成立的。
苹果提供了一组gesture recognizer,可以被添加到任何view上。这些recognizer将低级的鼠标和触控板的输入转化成语义化的事件 - 例如点击,缩放和旋转。
你将添加一个gesture recognizer到image view上,然后在view controller的代码中创建相应的动作。
在 Main.storyboard 中,前往 Object Library 并搜索 click gesture 。将 Click Gesture Recognizer 从library拖拽到image view上。它将像这样的出现在document outline中:
无论何时用户点击图片,这个gesture recognizer都会被激活 - 恰好是你想要的。现在你需要添加一些新的连接,然后你将为最后的冲刺,准备好切换到代码。
用你之前的相同的方式,打开assistant editor,并确保它正在展示 ViewController.swift 。然后通过从storyboard 按住control拖拽 到代码中:一个是从image view(叫做 ballImageView ),另一个是从multiline label(交错 adviceLabel )。这添加了下列的outlets:
@IBOutlet weak var ballImageView: NSImageView! @IBOutlet weak var adviceLabel: NSTextField!
你也需要一个action来接通新创建的gesture recognizer。从document outline中的click gesture recognizer, 按住Control拖拽 到代码中:
改变Connection为 Action ,并命名为 handleBallClick :
点击
Connect
,Xcode将添加下列的函数定义到
ViewController
类中:
@IBAction func handleBallClick(sender: AnyObject) { }
现在你完成了所有在Interface Builder中的工作,你可以切回到标注编辑器中了,打开 ViewController.swift 。
当用户点击8-ball时,你想在展示建议或展示“8”。这意味着
handleBallClick(\_:)
会同时操作image view和建议label。
添加下列的代码到
handleBallClick(\_:)
中:
// 1: if (adviceLabel.hidden) { // 2: adviceLabel.hidden = false ballImageView.image = NSImage(named: "magic8ball") } else { // 3: adviceLabel.hidden = true ballImageView.image = NSImage(named: "8ball") }
-
检查当前
adviceLabel
是否可见。hidden
是一个在NSView
上的布尔类型的属性(因此NSTextField
也就有了),允许你指定是否要让view隐藏。 -
如果建议label当前被隐藏了,显示它,并改变图片为magic这边。
NSImage(named:)
会从asset目录中加载图片,而NSImageView
的image
属性指定了要展示的图片。 - 反过来,如果当前建议label是可见的,隐藏它,并将其切换到“8”这边。
build并执行,点击那个球来查看它的“8”和一条建议之间的切换。相当好对么?注意怎么当你第一次启动app时,建议已经显示了?这确实不是你想要的,但要修复它很简单。
当app第一次启动时,你想确保那个建议label是隐藏的,8-ball则是显示的。View controller有一个完美的方法来配置这个初始化的设置 - 以
viewDidLoad()
的形式。
一旦view controller完成了从storyboard中加载所有的view组件,它就会调用
viewDidLoad()
;来给你一个机会执行最终的配置。
在
ViewController.swift
中,找到
viewDidLoad()
方法并添加下列的内容:
adviceLabel.hidden = trueballImageView.image = NSImage(named: "8ball")
你将从点击事件的处理动作中区别出这个代码 - 它仅是隐藏了建议label,并设置image为 8ball 。
运行项目,来检查你在开始时不会看到建议。
此刻,无论你“摇动”那个球多少次,它总是给你相同的建议。这不是特别有帮助的。是时候添加一些随机性了。
在
ViewController
中添加下列的代码作为属性 - 就在类定义的下面:
let adviceList = [ "Yes", "No", "Ray says 'do it!'", "Maybe", "Try again later", "How can I know?", "Totally", "Never", ]
这是一个字符串的数组,由全部不同的那个球可以分发的建议构成。
找到文件的最底部(不在)
ViewController
类中),添加下列的extension:
extension Array { var randomElement: Element? { if count < 1 { return .None } let randomIndex = arc4random_uniform(UInt32(count)) return self[Int(randomIndex)] } }
这添加了一个新的property到标注库的
Array
类型,它会返回一个随机的严肃。如果Array是空的,它就返回
nil
,否则它会在返回相应的元素前,使用
arc4random_uniform()
产生一个随机的序号。
在
handleBallClick(\_:)
中,更新
if
语句的第一个分支(也就是说
adviceLabel.hidden == true
)为如下:
if let advice = adviceList.randomElement { adviceLabel.stringValue = advice adviceLabel.hidden = false ballImageView.image = NSImage(named: "magic8ball") }
这会尝试获取一条随机的建议来展示,如果成功的话就会更新
adviceLabel
的
stringValue
来展示。
build并执行,点击8-ball几次开始从这个球的智慧中受益:
你可以下载完整版本的MagicEight在 这里 。
这个Mac OS X的开发教程引导系列已经给了你一个开始OS X app的基层的知识 - 但是这里有太多的东西要学!
苹果有一些很棒的文档,覆盖了OS X开发的全部方面 - 请前往 https://developer.apple.com/library/mac/navigation/