macOS开发入门教程 : Part 2

来源:互联网 发布:js socket 编辑:程序博客网 时间:2024/06/08 18:25

原文:macOS Development for Beginners: Part 2
作者:Sarah Reichelt
译者:kmyhy

欢迎回来!

在本系列的第一部分,你学习了如何安装 Xcode,如何创建 App,添加 UI,连接 UI 到代码,运行 App,调试 App 以及如何获取帮助。如果你没有掌握这些内容,请回到第一部分进行学习。

在这一部分,你将创建一个更复杂的 APP 的 UI。你将学习如何使窗口可以拉伸大小,导航到第二个窗口以显示 App 的偏好设置。

开始

打开 Xcode,在欢迎界面,点击创建新 Xcode 项目,或者同第一部分一样用 File/New/Project… ,然后选择 macOS/Application/Cocoa Application。点 next, 将 App 取名为 EggTimer,语言用 Swift ,勾上 Use Storyboards 。点击 Next,选择项目保存地址。

编译运行 App,检查是否有错误发生。

EggTimer App

这个 App 叫做 EggTimer,它从指定的时间开始倒计时。有一张图片,当你的鸡蛋煮熟后,它会改变。同时播放声音。第二个窗口用于显示 App 的偏好设置。

https://koenig-media.raywenderlich.com/uploads/2017/01/EggTimer-1.png’ width=’200’/>

打开 Main.storyboard,和你第一部分中所见的差不多,你会有 3 个组件:

  • Application 场景
  • Window Controller 场景
  • View Controller 场景

Application 场景包含了 App 运行时会显示的菜单栏和菜单。Window Controller 是 App 的一部分,用于控制窗口的行为,它如何拉伸大小、如何显示新窗口,App 是否保存窗口大小及位置等等。一个 Window Controller 可以管理多个窗口,但如果它们的属性不同,那么需要用不同的 Window Controller。

View Controller 用于显示 window 内部的 UI——那是你放置 UI 显示主要内容的地方。

注意 Window Controller 有一个箭头指向了它。这说明当 App 一启动时它会决定要显示什么。你可以在文档树窗口中选中 Window Controller,打开属性面板,将 Is Initial Controller 的勾去掉,这个箭头就消失了。如果你需要让它编程第一控制器,你需要重新勾上它。

Window Controller

在开始设计 UI 之前,确保你在项目导航器中选中了 Main.storyboard。在 Window Controller 中点击,选中它下面的 window。在可视化编辑器中,Window Controller 显示为 View Controller,因为它包含了 View Controller。在这个 App 中,你想让窗口大小不小于 346x471。这也是窗口的初始大小。

https://koenig-media.raywenderlich.com/uploads/2017/01/WindowControllerSize.png’ width=’600’/>

在工具面板中的 Size 面板,将 content size 中的 width 设置为 346,height 设置为 471。勾选 Minimu Content Size,并将 width 和 height 值设为和 content size 一模一样。在编辑器中,Window Controller的大小会被改变。你可能想移动它,以便它不遮挡住别的对象。

如果你想将 View Controller 的大小和包含它的 Window Controller 的大小设置为相同的话,非常容易。这一步不是必须的。在文档树中点击 View Controller,确保选中它下面的 View。在 Size 模板中,设置宽高为 346 和 471。为了看见所有对象,你可以拖动位置。现在 Window Controller 和 View Controller 都是一样大小了。

选中 WindowController 下的 window,在属性面板中将标题修改为 Egg Timer。然后将 Autosave name 设为 EggTimerMainWindow,以便关闭 App 时 window 的位置和大小会被保存。

https://koenig-media.raywenderlich.com/uploads/2017/01/VCNaming.png’ width=’600’/>

如果你是一个 iOS 程序员,你可能对各种屏幕大小、各种设备类型和旋屏进行过适配。在 macOS 开发中,你不得不对数不清的窗口尺寸和宽高比进行适配,这就是为什么我要设置初始大小,因为这个窗口有一点特殊。幸运的是,自动布局为你解决了这个问题。

对 UI 进行布局 1

基本的 UI 包含了两个 Stack View。第一个包含了属于时间和鸡蛋图片。第二个包含了 3 个按钮。首先从按钮开始:

  1. 从 Object Library 中搜索 “Button” 。
  2. 拖一个 Gradient button 到 View Controller 上。
  3. 在属性面板中,删除它 的 image,将 title 改为 Start。
  4. 将 font 改为 System 24。

https://koenig-media.raywenderlich.com/uploads/2017/01/ButtonProps.png’ width=’300’/>

  1. 拉大按钮,让它完整显示文本。

  2. 选中 Start 按钮,按 Command + D 两次,复制出两个对象。

  3. 将新对象拖朝一边,以便看到它们。

  4. 将两个新按钮的 title 分别设置为 Stop 和 Reset。

  5. 选中 3 个按钮,然后点击菜单 Editor/Embed In/Stack View。

https://koenig-media.raywenderlich.com/uploads/2017/01/StackView.png’ width=’400’/>

为了让 3 个按钮占满整个 Stack View,选中新的 Stack View 然后在属性面板中修改如下属性:

Distribution: Fill Equally
Spacing: 0

点击编辑器底部的 Add New Constraints 按钮,设置上下左右边距,勾选 Update Frames: Items of New Constraints 然后点击 Apply 4 Constraints。

https://koenig-media.raywenderlich.com/uploads/2017/01/StackViewConstraints.png’ width=’400’/>

stack view 就设置完了。但按钮比 stack view 更矮。在文档树中,Control+左键,从 Start button 拖一条线到 stack view 然后选择 Equal Heights。对其它两个按钮进行同样操作。

https://koenig-media.raywenderlich.com/uploads/2017/01/EqualHeights.png’ width=’700’/>

现在按钮的 stack view 终于是你想要的了。

编译运行程序,尝试去拉伸窗口大小:按钮仍然会牢牢处于窗口底部并均匀地拉伸以占据宽度。

https://koenig-media.raywenderlich.com/uploads/2017/01/ResizedWindow.png’ width=’400’/>

最后还有一点,需要在属性面板中反选 Enabled 属性以禁用 Stop 和 Reset 按钮。在计时还没有开始之前,不需要让它们 enable。

对 UI 进行布局 2

第二个 stack view 包含剩余计时时间和图片。拖一个 label 到 view controller 中,将它的 title 改为 6:00,Alignment 改为居中。当前系统字体(San Francisco)使用等间距数字,也就是说如果你有一个计数器,数字显示会随着它们的改变跳来跳去——这很烦。

为了避免这个,将 font 改成 Helvetica Neue,大小为100。这样 labe 就显得太小了,因此拉伸它直到你觉得合适。

https://koenig-media.raywenderlich.com/uploads/2017/01/FontAdjust.png’ width=’400’/>

要添加图片,在 Object Library 中搜索 image。搜索结果中的 Image View 就是你想要的。将它拖到 View Controller 中,放到计时标签下面。

下载这个项目用到的图片资源。解压缩文件,打开 Egg Image 文件夹。在Xcode中,点击项目导航器中的 Assets.xcassets 文件。

将 6 张图片拖到 Assets 库中。然后就可以在 App 中使用它们了。因为图片名中含有 @2x,它们会自动定位到每个 asset 的 2x 栏中。

https://koenig-media.raywenderlich.com/uploads/2017/01/ImageAssets-1.png’ width=’600’/>

回到 Main.storyboard,选择 ImageView,在属性面板中点击 Image 属性。你会看到你添加的图片都已经在其中了。选择 stopped。

创建第二个 stack view,选中计时标签和 image view。点击 Editor/Embed In/Stack View 菜单。然后设置 stack view 填充剩余空间。点击 Add New Constrains 按钮,设置约束如下:

https://koenig-media.raywenderlich.com/uploads/2017/01/SV2Constraints.png’ width=’600’/>

stack view 已经被拉大了,但 image view 仍然太小,选中 Image View 修改它的 lefg 和 right 约束为下:

https://koenig-media.raywenderlich.com/uploads/2017/01/ImageConstraints.png’ width=’600’/>

在属性面板中,将 Scaling 设置为 Proportionally Up or Down。

运行 App。拉伸窗口,看看是不是所有的 UI 元素都得到正确地拉伸和移动。

https://koenig-media.raywenderlich.com/uploads/2017/01/RunWithConstraints.png’ width=’600’/>

连接 UI 和代码

和第一部分中一样,要连接 UI 和代码必须创建 @IBOutlet 和 @IBAction。对于这个窗口,你需要为下列 UI 元素创建 @IBOutlet:

  • 倒计时标签
  • 鸡蛋图片
  • 3 个按钮

这 3 个按钮也需要 @IBAction,当用户点击它们时才会触发方法。在项目导航器中,选中 Main.storyboard。

在项目导航器的 ViewController.swift 上点击 Option+左键,打开 Assistant 编辑器。如果你的屏幕空间不够,用右上角的按钮隐藏工具面板和导航器。

选中倒计时标签,control+左键拖到 ViewController 类中,和第一部分中所做的一样。设置 name 为 timeLeftField。对 Image view 重复同样步骤,设置 name 为 eggImageView。为按钮创建 IBOutlet 分别命名为 startButton、stopButton 和 resetButton。

按钮还需要创建 @IBAction。从 Start 按钮用 control + 左键拖一条线,但 Connection 中设为 Action,name 设置为 startButttonClicked。重复同样步骤在剩余按钮上,分别创建两个 IBAction 叫做 stopButtonClicked 和 resetButtonClicked。

如果你像我一样经常会搞忘记将 Connection 修改为 Action,你最终会得到两个 @IBOutlet 而没有 @IBAction。要删除多出来的 @IBOutlet,先在 ViewController 中删除多余的代码。然后打开工具面板中的连接面板。

你会看到在 Referencing Outlets 下面有两个 Outlet。点击错误的那个 Outlet 右边的 X,将它删除。然后重新创建 @IBAction,这次不要忘记修改 Connection 了。

https://koenig-media.raywenderlich.com/uploads/2017/01/FixConnection.png’ width=’700’/>

ViewController 代码现在是这个样子:

https://koenig-media.raywenderlich.com/uploads/2017/01/OutletsActions.png’ width=’400’/>

在第三部分,你将实现这些方法。关闭助手编辑器,重新打开导航器和工具面板(如果之前关闭过它们的话)。

菜单

在 Main.storyboard 中,点击菜单条或者 Application Scene 以选中它。 App 模板默认提供了一些菜单,但对于这个 App,绝大部分都是用不到的。找到菜单的最简单办法是使用文档树。用倒三角按钮去展开视图的菜单和它的内容。

https://koenig-media.raywenderlich.com/uploads/2017/01/ViewMenu.png’ width=’300’/>

菜单栏由多个嵌套的菜单和菜单条构成。切换到 Identity 面板,你可以看到每个条目,和你点击它的时候一模一样。Main Menu 是一个 NSMenu 类实例。它包含了一个 NSMenuItem 数组:View 就是数组中的一个。

View 菜单项包含了一个下级菜单(NSMenu),这个下级菜单又有它自己的 NSMenuItems。注意分隔条仅仅是一个特殊的 NSMenuItem。

首先需要删除 App 不用的菜单。在文档树中选择 File 菜单,按 Delete 键就删除它了。如果你在可视化编辑器中选择它并 Delete,你只是删除了 File 菜单项(MenuItem)下面的菜单(Menu),这样会在菜单栏上留下一个空白。如果这样,选中这个空白,再按一下 Delete 键将它删除。

删除除 EggTimer、Window和 Help 之外的所有菜单。

https://koenig-media.raywenderlich.com/uploads/2017/01/MenusAfterDeletion.png’ width=’300’/>

现在,你可以添加一个新的菜单用于模拟 3 颗按钮。在Object Library 中搜索 menu。

记住,每个菜单都是以一个菜单项开始,拖一个 Menu Item到菜单栏的 EggTimer 和 Window 之间。它会显示成一个蓝色的方块,那是因为它下面还没有任何带标题的菜单。

现在脱一个 Menu 到这个蓝色方块中。如果你不能准确地拖到方块中,请将它拖到Document Outline窗口中的 new Item下方。这个新菜单还没有标题,但它有 3 个菜单项。

https://koenig-media.raywenderlich.com/uploads/2017/01/NewMenu.png’ width=’600’/>

选中这个菜单(不是菜单项),切换到属性面板,将标题修改为 Timer。这会给新菜单命名。选中 Item 1,将标题修改为 Start,你可以用双击的方式、也可以通过属性面板编辑标题。

在属性面板中点击 Key Equivalent字段,按下 Command+S 键,指定快捷键。通常 Command+S 是保存的快捷键,由于我们删除了 File 菜单,因此这样并不会引发快捷键冲突,当然这种将常用快捷键用于其他功能的体验并不太好。

使用同样的方法设置第二个 item 的标题为 Stop,快捷键为 Command+X,第三个item的标题设为 Reset,快捷键为 Command+R。

https://koenig-media.raywenderlich.com/uploads/2017/01/TimerMenu.png’ width=’700’/>

你可以在可视化编辑器中看到 3 个按钮和菜单栏顶端形成十字交叉。切换到属性面板。挨个点击每个 item,查看它们已经连接好 Application、First Responder 和 AppDelegate。First Responder 通常就是目前最上层的 View Controller,它可以从菜单项接收到动作。

Option+左键,点击 ViewController.swift,在 3 个按钮的 IBAction 下面添加下列代码:

// MARK: - IBActions - menus@IBAction func startTimerMenuItemSelected(_ sender: Any) {  startButtonClicked(sender)}@IBAction func stopTimerMenuItemSelected(_ sender: Any) {  stopButtonClicked(sender)}@IBAction func resetTimerMenuItemSelected(_ sender: Any) {  resetButtonClicked(sender)}

这些函数将通过菜单调用,它们会调用按钮的 action 方法。你也可以直接让菜单项调用按钮的 action 方法,但我使用的这种方式是为了在调试时更容易看出事件的调用顺序。保存文件,关闭助手编辑器。

Control+左键,从 Start 菜单项拖一条线到橙黄色立方块(即 First Responder)。弹出一个菜单,显示一个常常的选项列表。输入 sta 快速定位,然后选择 startTimerMenuItemSelected。

https://koenig-media.raywenderlich.com/uploads/2017/01/MenuAction.png’ width=’500’/>

用同样方式,连接 Stop 菜单项到 stopTimerMenuItemSelected ,连接 Reset 菜单项到 resetTimerMenuItemSelected 。 当 EggTimer 窗口位于前台时,选择这些菜单项将调用对应的函数。

当然这 3 个按钮不可能在同时处于可用状态,菜单项也应该和按钮状态保持一致。这不应该在 View Controller 中进行,因为它不会一直是 First Responder,因此菜单项我们应该在 AppDelegate 中进行管理。

在 Main.storyboard 打开且菜单可见的情况下,option+左键,点击项目导航器中的 AppDelegate.swift。Control+左键,从 Start 菜单拖到 AppDelegate,然后创建一个 IBOutlet 名为 startTimerMenuItem。

同样的方式,创建其他菜单项的 stopTimerMenuItem 和 resetTimerMenuItem。

https://koenig-media.raywenderlich.com/uploads/2017/01/MenuOutlet.png’ width=’660’/>

在第三部分,你将添加代码去禁用和启用这些菜单项。目前,你需要关闭自动启用/禁用。通常,App 会检查当前 First Responder 是否有一个和菜单项对应的 action,如果没有回禁用它。对于这个 App,你想自己处理这个。选择 Timer 菜单,然后在属性面板中反选 Auto Enables Items。

偏好设置窗口

https://koenig-media.raywenderlich.com/uploads/2017/01/Prefs-2.png’ width=’500’/>

EggTimer App 的主窗口看起来完成了,但还需要一个偏好设置窗口,这样用户可以选择鸡蛋需要几成熟。

偏好设置窗口用一个单独的 Window Controller 显示在另外的窗口中。这是因为偏好设置窗口拥有不同的默认大小,而且它是不可以拉伸的。将多个 View Controller 用同一个 window controller 显示也是可以的,但是它们会使用相同的 window controller 属性。

打开 Main.storyboard。如果助手编辑器已经打开,请关闭它。在 Object Library 中搜索 window。拖一个 Window Controller 到可视化编辑器中。它会自带一个 View Controller 以便显示窗口内容。重新调整一下位置,以便能够看到它们同时将它们挪动带靠近菜单栏的位置。

打开 EggTimer 菜单,control+左键,从 Preferences… 拖到新的 Window Controller 上。当弹出菜单显示时,选择 Show。这会创建一个 segue,当用户在 EggTimer 中选择 Preferences… 菜单项时,这个 Window controller 会将新的 view controller 显示出来。

https://koenig-media.raywenderlich.com/uploads/2017/01/PrefsConnect.png’ width=’600’/>

偏好设置窗口会显示一个新的 View Controllor,因此你需要创建这个 View Controller 类。在项目导航器中,选择 ViewController.swift,这样可以保证你的新文件会放在项目导航器中的正确位置。选择 File/New/File…,然后选择 macOS/Cocoa Class 并点击 Next,类名设置为 PrefsViewController ,并继承NSViewController 类。语言选择 Swift ,反选 Also create XIB file for user interface。点 Next 、 Create ,保存文件。

回到 Main.storyboard。选择新的 view controller。确认你选择的是 view controller 自己而不是它的 view,这用 Document Outline 比较容易做到。在 Identity 面板,设置它的 Class 为 PrefsViewController。

在 preferences window controller 中选中它的 window,用属性面板设置它的 title 为 preferenceds。不要设置它的 autosave name,因为这个窗口将在显示的时候居中显示。反选 Minimize and Resize controls ,这样窗口大小就会固定。

https://koenig-media.raywenderlich.com/uploads/2017/01/PrefsWindow.png’ width=’300’/>

打开 Size 面板,在 Content Size 中设置 width 为 416,height 为 214。

在 Initial Position 下面,选择 Center Horizontally 和 Center Vertically。

https://koenig-media.raywenderlich.com/uploads/2017/01/PrefsSizeLoc.png’ width=’300’/>

选择 PrefsViewController 的 view,在 Size 面板中将 width 设置为 416,height 设置为 214。

PrefsViewController 上有一个弹出按钮用于选择预设时间、一个 slider 用于选择自定义时间、两个控件所对应的 label 、两个按钮:Cancel 和 OK、以及一个变化的 label 用于显示当前选择的时间。

https://koenig-media.raywenderlich.com/uploads/2017/01/PrefsLayout-2.png’ width=’500’/>

拖下列控件到 View Controller 中,并设置它们如下所示:

  • Label – 设置 title 为 “Preset Egg Timings:”
  • Pop Up Button
  • Label – 设置 title 为 “Custom Egg Timing:”
  • Label – 设置 title 为 “6 minutes”
  • Horizontal Slider
  • Push Button – 设置 title 为 “Cancel”
  • Push Button – 设置 title 为 “OK”

因为窗口是不能缩放的,不需要使用任何自动布局约束——这些对象会按照你所摆放的方式来显示。以拖动的方式对它们进行布局,这时蓝色导线就会有用了。将“6 minutes”标签的宽度拉宽到接近窗口右边,以便显示更多内容。双击 Pop up 按钮,查看最初的的 3 个条目并将 title 设置为:

  • For runny soft-boiled eggs (barely set whites):3 minutes——松软蛋羹(几乎还没变白):3 分钟
  • For slightly runny soft-boiled eggs: 4 minutes——半软蛋羹:4 分钟
  • For custardy yet firm soft-boiled eggs: 6 minutes——凝固的蛋羹:6 分钟

从 Object Library 后拖进 2 个 MenuItem,一个 Separator Menu 和另一个 MenuItem。如果你无法将它们放到正确的位置,请使用 Document Outline。

剩下菜单标题分别是:

  • For firm yet still creamy hard-boiled eggs: 10 minutes——凝固但仍然是羹状的煮蛋:10 分钟
  • For very firm hard-boiled eggs: 15 minutes——非常硬的煮蛋:15分钟
  • Custom——自定义

https://koenig-media.raywenderlich.com/uploads/2017/01/PresetPopup-1.png’ width=’350’/>

我并没有冒充煮蛋专家的意思,这些时间和描述来自于The Kitch。

选中 Pop Up 按钮,不要选中它的选项,然后设置它的 Selected Item 为 6 分钟。

现在你将允许 App 获知用户所选择的时间。对于 Pop Up 按钮的每个 item,用属性面板将它的 tag 设置为分钟数:3,4,6,10,15。将 Custom item 的 tag 设置为 0。

https://koenig-media.raywenderlich.com/uploads/2017/01/Tags.png’ width= ‘400’/>

选中 Slider 在属性面板中设置 Tick marks 为 25,Minimum value 为 1,Maximum value 为 25,Current Value 为 6,然后勾上 Only stop on tick marks。当显示 tick marks 时,你可能想将 slider 下移几个像素。反选 Enabled 禁用 slider——只有在 Popup 按钮中选择了 Custom 时才启用 slider。

https://koenig-media.raywenderlich.com/uploads/2017/01/SliderSettings-1.png’ width=’300’/>

连接偏好设置中的对象

Option+左键,在项目到导航器中点击 PrefsViewController.swift,如果屏幕空间不足,请隐藏两边的窗口。你需要为 popup 按钮、slider 和“6 minutes”标签创建 @IBOutlet。Control+左键,从每个控件拖到 PrefsViewController.swift 文件并分别命名为:

  • Popup: presetsPopup
  • Slider: customSlider
  • Label: customTextField

然后,右键+左键,创建 @IBAction,请记得将 Connection 改成 Action:

  • Popup: popupValueChanged
  • Slider: sliderValueChanged
  • Cancel button: cancelButtonClicked
  • OK button: okButtonClicked

你的代码会变成这个样子:

https://koenig-media.raywenderlich.com/uploads/2017/01/PrefsOutletsActions-1.png’ width=’400’/>

偏好设置的窗口布局已经完成。编译运行 App,从 EggTimer 菜单中选择 Preferences。欣赏完偏好设置窗口后,点击标题栏的红色关闭按钮,关闭窗口。

https://koenig-media.raywenderlich.com/uploads/2017/01/RunPrefs-2.png’ width=’450’/>

App 图标

UI 中还剩一个工作就是为 App 添加图标。你之前已经下载过 App 的图片资源,其中的部分图片已经导入到项目的 Assets.xcassets 中了。再次打开下载的文件夹,找到 egg-icon.png 文件。

在项目导航器中选择 Assests.xcassets,点击 AppIcon,然后将 egg-icon.png 拖到 Mac 256pt 1x 方框中。正如在第一部分中所说,对于真正的 App,你应该提供所有尺寸的 AppIcon,但在这里,只需要一个尺寸就够了。

https://koenig-media.raywenderlich.com/uploads/2017/01/EggTimerAppIcon.png’ width=’600’/>

编译运行 App,确认信的图标在 Dock 中显示。如果仍然看到默认图标,可以点击 Xcode 的 Product/Clean 菜单后重试。

https://koenig-media.raywenderlich.com/uploads/2017/01/EggTimerDockIcon.png’ width=’100’/>

结束

现在 App 的 UI 就实现完了,但 App 还没有功能。如果你有某些地方不对,可以从这里下载已经实现好整个 UI 的 Xcode 项目,它是为下一部分教程准备的。

在第三部分,你会添加让 App 工作起来的代码。

有任何问题和建议,请在下面留言。