原文:macOS Controls Tutorial: Part 1/2
作者:Ernesto García
译者:kmyhy
更新说明:本教程由 Ernesto García 升级为 Xcode 8.2 / Swift 3 。上一版本由 Michael Briscoe 升级为 Xcode 6.3 / Swift 1.2 。原文作者是 Ernesto García。
如果你是一个 iOS 程序员,你想学习 Mac 开发,你有福了——由于你具备的 iOS 技能,你可以发现这很好学习!
有大量的 Cocoa 类和设计模式,比如字符串、字典和委托在 Mac 开发中都能够找到等价物。你感觉就像是回家。
但是,最大的不同在于它拥有不同的控件。 UIButton 和 UITextField 不见了——取而代之的是类似的东东(有细微不同)。
本教程介绍最常见的 macOS UI 控件——大部分 Mac app 都是由它们构成的。你会学习使用这些控件,以及它们的方法和属性,为了快速成为一个合格的开发者,你需要掌握它们!
在本文中,我们会编写一个类似于流行游戏 Mad Libs(疯狂填词) 的简单 Mac app。Mad Libs 是一个填词游戏,你可以在一段文字中插入不同的单词,并组装成一个故事——结果会非常搞笑!
当你完成 2 部分的教程时,你会基本熟练应用
- Labels 和 Text Fields
- Combo Boxes
- Popup Buttons
- Text Views
- Sliders
- Date Pickers
- Buttons
- Radio Buttons
- Check Buttons
- Image Views
注意:学习本教程需要具备基本的 macOS 开发知识。如果你是第一次接触 macOS 开发,你可以先学习我们的 macOS 开发教程初学者系列。
学习一门新平台开发技术的最好方法,是立即开始——因此不再啰嗦,让我们立即开始:]
开始
打开 Xcode,选择 File/New/Project。在项目模板对话框,选择 macOS/Application/Cocoa,这个模板让你创建一个运行在 macOS 上的图形界面 app。然后点击 Next。
接下来,在 Product name 一栏输入 MadLibs,在 Organization name 和 Organization identifier 中填入必要内容。确保 Use Storyboards 被勾选,Language 选择 Swift。
点击 Next,指定文件保存位置。点击 Create。
打开 Main.storyboard。Xcode 已经在这里为你创建了基本的 Mac app 框架:一个 Window 控制器和一个 View Controller。
选中 Window Controller 场景中的 window 对象,打开属性面板。将窗口 title 修改为 MadLibs。
一个 Mac app 通常有一个大小可变的 window,它的内容必须适应 window 的大小。要实现这样,最好的工具就是自动布局。而要在所有控件上添加自动布局将严重分散你的注意力,因此本教程我们将主要精力集中在 macOS 控件的使用上。
因此,我们只使用默认的 autoresizing 属性,这样所有的控件将保持一个固定的位置和大小,无论用户怎样修改 window 的大小——这样会导致部分控件部分超出窗口的可视区域。
注意: 如果你想了解 Auto Layout 以及在 Mac app 中如何使用自动布局,你可以参考我们的 macOS 开发教程初学者系列,第3集。
在本教程中,你需要在这个视图上添加几个 macOS 控件,视图的默认高度可能不足以放下它们。当你需要修改它的大小时,可以将 content 视图的下端往下拖,或者在 Size 检视器中修改视图的 Height 属性。
编译运行。
你编写了一个能够运行的 app——甚至没有编写一行代码。现在窗口中是空的,但你将用一些 mac 控件放在上面,这样它看起来会好一些!
现在基本架子就已经搭好了,你可以将主要精力转移到本教程的注意内容上来了——为 app 添加控件。
本教程剩下的步骤都将集中到单个的、不同的控件身上。你会学习每种控件的基本用法,以及如何在 MadLibs 中进行实际的运用。
NSControl——MacOS 控件的基石
NSControl 是其它 macOS 控件的基础。NSControl 提供了 UI 元素的 3 个基础功能:绘制屏幕,响应用户事件、发送 action 消息。
NSControl 是一个抽象类,你不可能直接使用它,你必须创建自己的控件子类。所有常用控件都是 NSControl 的子类,因此都继承了它的属性和方法。
一个控件的最常用的方法是读取/设置它的值,以及启用/禁用控件。这些方法分别是:
设置控件值
如果需要显示数据,通常需要改变控件的值。根据需求的不同,控件值可能是字符串、数字或者对象。大部分情况下,我们会使用和要显示的内容相匹配的值类型,不过 NSControl 也允许你设置多个不同的类型!
控件值的这些 get/set 方法是:
// getting & setting a string
let myString = myControl.stringValue
myControl.stringValue = myString
// getting & setting an integer
let myInteger = myControl.integerValue
myControl.integerValue = myInteger
// getting & setting a float
let myFloat = myControl.floatValue
myControl.floatValue = myFloat
// getting & setting a double
let myDouble = myControl.doubleValue
myControl.doubleValue = myDouble
// getting & setting an object
let myObject: Any? = myControl.objectValue
myControl.objectValue = myObject
这些 setter/getter 方法是符合 Swift 类型安全特性的。
启用/禁用控件
根据 app 状态的变化启用/禁用控件是很常见的 UI 特性。当控件被禁用,它就无法响应鼠标或键盘事件,同时会改变它的 UI 图像以表示控件被禁用,比如控件会“变灰”。
启用/禁用控件的方法分别是:
// 禁用控件
myControl.isEnabled = false
// 启用控件
myControl.isEnabled = true
// 获取控件的禁用/启用状态
let isEnabled = myControl.isEnabled
是的,就是这么简单——重要的一点是,这些方法对所有 macOS 控件来说都是一样的。在你的 UI 中用到的所有控件都是以同样的方式运作。
是时候来看一下几种常见控件了。
梦幻成真(注*:电影名) – NSTextField
文本 field 是最常见的 UI 控件之一,用于显示或编辑一段文本。在 macOS 这个控件就是 NSTextField。
NSTextField 可以显示、编辑文本。你会看到它和 iOS 中的是不同的,UILabel 显示静态文本,UITextField 用于编辑文本。在 macOS 中,这两者被合二为一了,它会根据 isEditable 属性值的不同而改变不同的行为。
如果你想把它当成 label 用,只需要将 isEditable 属性设置为 false。如果想让它作为文本输入控件——设置 isEditable 为 true。这个属性可以通过代码来改变,也可以在 IB 中设置。
为了节省你的时间,IB 会提供几个预先配置过的 macOS 控件,这些控件可以用来显示文本、编辑文本,但其实都属于 NSTextField。这些控件放在了 Object Library 中,分别是:
学习完基本的 NSTextField 理论知识之后,我们将在 MadLibs app 中使用它们!
活在过去——一个动词的过去式
我们会在 MadLibs app 中添加各种控件,它们允许你不断构建出一个搞笑的句子。当你完成后,所有不同的部分会组合起来并显示,以获得一种喜剧的效果。你的创意越丰富,结果就会越搞笑!
第一个添加的控件是文本输入框,你用它来输入一个动词,这个词会组成句子的一部分,同时还需要一个 label,提示文本输入框的作用。
打开 Main.storyboard。在 Object Library 中 Label 控件,将它拖到 View Controller 场景的 view 中。双击 label,修改它的默认文本为 Past Tense Verb(过去时态的动词):。
然后,拖一个 Text Field 控件到 view 中,把它放在 label 的右边:
现在,需要为 text field 在 View Controller 中创建一个 IBOutlet。确认打开 Main.storyboard,点击 Assitant editor。确认选择 ViewController.swift 源文件,用 ctrl+左键,从故事板中的 text field 拖一条线到 ViewController.swift 中的类定义下面,松开鼠标,就会创建一个新属性:
这会弹出一个新对话框,将 IBOutlet 的 name 设置为 pastTenseVerbTextField,然后点击 Connect。
就这么简单!在你的 View Controller 中你添加了一个 NSTextField 属性,而这个属性和主窗口中的 text field 连接在一起。
当 app 启动时,最好显示一些默认文本,这样人们才知道需要在 text field 输入些什么。因为人们都喜欢实物,而且实物和 MadLibs 总是最容易产生搞笑的效果,我们在这里不妨将 ate 作为默认的文本。
这个工作需要在 viewDidLoad() 中进行。现在,简单地设置控件的 stringValue 属性。
打开 ViewController.swift 在 viewDidLoad() 方法最后一句加上:
// 设置 pastTenseVerbTextField 的默认文本
pastTenseVerbTextField.stringValue = "ate"
编译运行。
好了,看到输入框以及默认的文字了吗?但是如果我们想提供一个列表供用户选择该怎么办?
这就该下拉组合框出场了!
组合框 – NSComboBox
组合框很有意思——也很有用——因为它允许你从一个选项列表中选择一个值,同时还能够输入自己的文字。
它就像是一个文本框,用户可以在里面进行输入,但同时包含了一个按钮,允许显示一个列表供用户选择。在 macOS 的日期时间偏好设置面板中,你可以找到一个现成的例子:
其中,用户既可以从预定义的列表中进行选择,也可以输入自己的服务器名称。
在 macOS 中,这个控件就是 NSComboBox。
NSComboBox 有两个不同的组成部分:文本输入框用于输入字符,选项列表用于当内嵌按钮被点击时显示选项列表。这两部分的数据都可以单独进行控制。
要读取或设置 text field 的值,可以使用前面介绍的 stringValue 属性。这样,就让事情保持简单一致了,点个赞!
为列表提供选项稍微麻烦一点,但也还好了。你可以直接调用控件的方法添加单个的元素,这就和操作可变数组的方式差不多,还可以使用数据源——只要有过 iOS 编程经验和用过 UITableViewDataSource 的人都不陌生!
注意:如果你不熟悉 Data Source 的概念,你可以参考苹果的Delegate 和 Data Source 文档。
方法 1 —— 直接调用控件的方法
NSComboBox 包含了一个内部列表,暴露了几个操作这个列表的方法,比如:
// 向列表中添加对象
myComboBox.addItem(withObjectValue: anObject)
// 向列表中添加数组
myComboBox.addItems(withObjectValues: [objectOne,objectTwo,objectThree])
// 删除列表中所有对象
myComboBox.removeAllItems()
// 根据索引删除列表中某个对象
myComboBox.removeItem(at: 2)
// 得到当前所选对象在列表中的索引
let selectedIndex = myComboBox.indexOfSelectedItem
// 选定指定位置的对象
myComboBox.selectItem(at: 1)
非常简单吧?但如果你不想在 app 中以硬编码的方式指定这些选项——比如你需要一个动态的列表,列表中的数据保存在 app 之外呢?这个时候用数据源会更加方便 :]
方法 2 ——使用 Data Source
使用数据源方式组合框会在需要显示选项时查询数据源,包括一些元数据——比如列表要显示的对象的数目。要获得这些信息,我们必须在我们的类中(通常是 View Controller,也就是控件所在的控制器)实现 NSComboBoxDataSource 协议。要让组合框使用数据源,需要分两步进行。
首先,将控件的 usesDataSource 属性设置为 true。然后设置控件的 dataSource 属性,将一个实现指定协议的对象赋给这个属性。如果实现数据源的类就是包含了控件的 View Controller,则这个步骤通常会在 viewDidLoad() 方法中进行,比如这样:
class ViewController: NSViewController,NSComboBoxDataSource {
..... override func viewDidLoad() { super.viewDidLoad() myComboBox.usesDataSource = true myComboBox.dataSource = self } .....
}
注意:上述代码中,语句书写的顺序是重点。如果在设置 dataSource 属性时 useDataSource 为 false(这是默认值),则会导致数据源设置失败,你的数据源是无效的。
要实现这个协议,我们需要实现这两个数据源方法:
// 返回数据源所管理的组合框中的对象个数
func numberOfItems(in comboBox: NSComboBox) -> Int {
// anArray 是一个数组,包含了多个对象
return anArray.count
}
// 返回和组合框 index 所对应的对象
func comboBox(_ comboBox: NSComboBox,objectValueForItemAt index: Int) -> Any? {
return anArray[index]
}
最后,当数据源发生改变时,需要刷新控件,你可以调用组合框的 reloadData() 方法。
到底用哪一种方法?
如果你的选项比较少,而且你不需要经常改变它们,可以使用将选项一次性添加到内部列表的方法。如果你的选项比较大或者它们是可变的,则使用数据源方法更高效快捷。对于本教程,我们将使用方法一。
现在,你已经学习了组合框的基本用法,是时候来在我们的 app 中使用它!:]
单身酒吧——一个单数名词
在这一节,我们会用一个组合框来输入一个单数名词。你可以从列表中选择,也可以自己输入。
首先,用一个 label 来描述这个组合框的用途。
打开 Main.storyboard。从 Object Library 中找到 Label 控件,将它拖到内容视图。将它的对齐方式设为 Right ,title 设为 Singular Noun:。
注意:更快捷的做法是,按下 Option 键,拖动一个现成的 label 就能复制出另一个 label。这很方便,因为我们可以让复制出来的 label 保持相同的大小和属性。
拖一个 Combo Box 控件到内容视图。把它放在 label 的右边。
现在为 NSComboBox 添加一个到 View Controller 的 IBOutlet。这和我们在 text field 上所用的技术是一样的:打开 Assitant Editor(确保打开的源文件窗口是 ViewController.swift),然后 ctrl+左键,从组合框拖一条线到 ViewController 类的 NSTextField 属性下边:
在弹出的对话框中,设置 IBOutlet 的 name 为 singularNounCombo。
现在这个 NSComboBox 属性连接到了组合框控件。我们接下来要添加一些数据以便在列表中展示。
打开 ViewController.swift 在刚才的 IBOutlet 后面添加:
fileprivate let singularNouns = ["dog","muppet","ninja","pirate","dev" ]
现在,在 viewDidLoad() 最后一句加入:
// 将 singularNouns 数组添加到组合框
singularNounCombo.removeAllItems()
singularNounCombo.addItems(withObjectValues: singularNouns)
singularNounCombo.selectItem(at: singularNouns.count-1)
第一句移除列表中的默认项。第二句调用 addItems() 将 singularNouns 中的名词添加到组合框。最后一句,选择列表中的最后一项。
编译运行 app,看看你的组合框是什么样子!
好——看起来一切正常。如果你点击组合框,你可以查看和选择不同的选项。
如果你想显示选项列表,但不允许用户输入自己的值呢?往后看,有一种专门的控件用于解决这个问题。
碰!黄鼠狼跑掉了(注*:一首英文儿歌)——NSPopupButton
弹出按钮允许用户从选项列表中选择,但不允许他们输入专辑的值。在 macOS 中这个控件是 NSPopupButton。
弹出按钮在 macOS 中十分常见,你几乎在每一个 app 中都能发现它们的身影——包括你正在使用的 Xcode! 在本教程中,在我们设置所用到的许多控件属性时,都要用到弹出按钮。比如:
填充空间——向弹出按钮添加选项
就像你想到的一样,向 NSPopupButton 中添加条目就像向 NSComboBox 中添加条目——但是 NSPopUpButton 不支持数据源方式。NSPopupButton 有一个内部选项列表,以及有几个用于操作这个列表的方法:
// 向列表中添加一个选项
myPopUpbutton.addItem(withTitle: "Pop up buttons rock")
// 向列表中添加多个选项
myPopUpbutton.addItems(withTitles: ["Item 1","Item 2","Item 3"])
// 移除列表中所有项
myPopUpbutton.removeAllItems()
// 获得当前所选项的索引
let selectedIndex = myPopUpbutton.indexOfSelectedItem
// 将当前选择设置为指定位置的选项
myPopUpbutton.selectItem(at: 1)
很简单吧?这就是 macOS 控件的美妙之处了——就操作这些控件的方式上来说,有许多地方都是相同的。
是时候在你的 app 中使用一个弹出按钮了!:]
小姑居处(注*:电影名) — 一个复数名词
现在,你可以添加一个弹出按钮到 MadLibs 中,以便用户可以选择一个复数名词来构成自己的搞笑金句。
打开 Main.storyboard,拖一个 label 在 Singular Noun 标签下方。
将对齐方式修改 Right ,标题修改为 Plural Noun:。然后,拖一个 Pop Up Button 控件到窗口中,放到 label 的右边。
内容视图变成这个样子:
我们还需要为弹出按钮创建 IBOutlet,这个你应该很熟悉了:打开 Assitant Editor,确认打开的源文件是 ViewController.swift,用 Ctrl-左键,从弹出按钮脱一条线到 ViewController 类中,创建一个 IBOutlet:
在弹出的对话框中,设置 IBOutlet 的 name 为 pluralNounPopup:
现在需要为控件准备数据!
打开 ViewController.swift ,在类实现中新增属性:
fileprivate let pluralNouns = ["tacos","rainbows","iPhones","gold coins"]
在 viewDidLoad() 方法最后一句加入:
// 将 pluralNouns 添加到弹出按钮中
pluralNounPopup.removeAllItems()
pluralNounPopup.addItems(withTitles: pluralNouns)
pluralNounPopup.selectItem(at: 0)
第一句删除已有的选项。第二句条用 addItems 将复数名词数组添加到弹出按钮。第三句选中列表中第一项。
编译运行 app,你会看到:
当 app 打开后,你会看到弹出按钮显示的是第一项 tacos,如果点击按钮,你才能够看到列表的所有选项。
好,你现在有两个让用户选择的控件了,其中一个还允许用户输入单行文本。不过如果要让用户输入多行文本时怎么办?
请继续阅读 text view!
第二个文本控件 – NSTextView
和 text field 不同,text view 通常用于显示富文本。更高级的功能甚至允许显示嵌入的图片。
这个 macOS 控件就是 NSTextView。
一个充分使用了 NSTextView 的例子是文本编辑器:
NSTextView 的功能非常强大,要介绍清楚非得要另外一个单独的教程,因此我们只介绍一部分在我们的 app 中将用到的几个基本功能。 (是不是感觉松了一口气?) :]
这是我们将会用到的几个 text view 的基本方法:
// 获取 text view 的文本
let text = myTextView.string
// 设置 text view 的文本
myTextView.string = "Text views rock too!"
// 设置 text view 的背景色
myTextView.backgroundColor = NSColor.white
// 设置 text view 的文字颜色
myTextView.textColor = NSColor.black
很简单吧——没啥大不了的。
NSTextView 内置了对 NSAttributedString 的支持。如果你将一个 attributed string 传递给 text view,这些字符串将按照正确的属性进行显示,比如字体、字号大小和文字颜色。
注意:一个 attributed 字符串是一种特殊的字符串,你可以用将其中每一段字符设置为不同属性——比如字体、它的颜色、是否粗写等等。要了解 attributed string,请参考我们的 TextKit 教程。这是一个 iOS 教程,但关于 NSAttributedString 的内容其实也适用于 Mac 开发。
The Phrase that Pays(注*:歌曲名) – 添加 Text View
万事俱备,你可以添加 text view 到 MadLibs app 中了!text view 允许用户输入多个短语,这些短语将用到最后的结果中。
打开 Main.storyboard,拖一个 label 在 Plural Noun 标签下面 (也可以像前面一样,复制一个现成的 label)。将 alignment 修改为 Right,title 修改为 Phrase:。
然后,拖一个 Text View 控件到这个 label 右边。
你的窗口看起来会是这个样子:
如果你想将试图拖大一点,你会发现奇怪的事情发生了。当你拖动窗口大小时,text view 会跟着移动,并改变位置。
因为默认情况下,Xcode 会自动改变 text view 的约束,当 text view 的父视图改变大小时,它的位置会随之而动。我们想让 text view 固定,就需要禁用这个特性。
从 Document Outline 视图中选择 Bordered Scroll View – Text View,打开 Size 检视器。
在 AutoResizing 栏,你会看到一个矩形框,带有 4 条红线连接到它的父试图。每一条红线代表一个自动改变大小约束。我们只需要点击右边和下边两条红线,禁用它们,让它们断开连接,变成淡红色的虚线:
现在,你再修改 window 的大小,text view 也会被固定,并和 label 进行对齐。
接下来要创建一个 NSTextView 的 IBOutlet 到 view controller。选中 textview,打开 Assistant 编辑器,确保 ViewController.swift 被打开。 Ctrl-左键,从 text view 拖一条线到 ViewController 类的其它 IBOutlet 下面。
重要提示:Text view 外面有一个 scroll view 包裹。在创建 IBOutlet 之前确认你选中的是 text view 而不是 scroll view 非常重要。要选中 text view,你需要连点 text view 三次,或者从 Document Outline 视图中进行选择。
在弹出窗口中,确认 type 是 NSTextView,然后将 name 设置为 phraseTextView。
然后,在 viewDidLoad() 方法最后一句加上:
// 设置 text view 的默认文本
phraseTextView.string = "Me coding Mac Apps!!!"
编译运行 app,你会看到:
好棒啊!Mad Libs app 已经初具雏形。
按下你的按钮——NSButton
按钮是一种 macOS 控件,当它们被点击,它们会发送一条消息给 app。
这个 macOS 控件就是 NSButton。
按钮有各种样式(你可以在 Object Library 中看到)。它们的工作方式都一样,所不同的仅仅是外观。
你可以选择适合于你的 app 设计风格的不同类型的按钮——请参考 macOS 人机交互指南,它建议你根据 app 的设计体验给出一些建议和指导。
通常在使用按钮时,你只需要给它指定一个 action 并设置它的标题。但是,有时你需要禁用按钮或者改变其外观。下列方法提供了这些操作:
// 禁用按钮
myButton.isEnabled = false
// 启用按钮
myButton.isEnabled = true
// 获取/设置按钮标题
let theTitle = myButton.title
myButton.title = theTitle
// 获取/设置按钮图片
let theImage = myButton.image
myButton.image = theImage
看起来很简单——在下一节,我们将添加一个按钮到 app 中,易如反掌。
按钮的那些事——添加一颗按钮
打开 Main.storyboard。拖一颗 Push Button 到内容视图。双击按钮,将标题修改为 Go! :
这次我们不需要为按钮创建 IBOutlet。
但是,我们需要为按钮创建 IBAction。这样 app 才能够知道按钮什么时候被点击!
打开 Assistant 编辑器,用 ctlr+左键,从按钮拖一条线到 View Controller 的实现代码中。
在弹出窗口中,确认 connection 被设为 Action。将 action 命名为 goButtonClicked。
当用户点击按钮,goButtonClicked() 方法将被调用。现在我们需要在方法中写几句测试代码,以验证它能正确工作。
打开 ViewController 在 goButtonClicked() 中加入:
let pastTenseVerb = pastTenseVerbTextField.stringValue
let singularNoun = singularNounCombo.stringValue
let pluralNoun = pluralNouns[pluralNounPopup.indexOfSelectedItem]
let phrase = phraseTextView.string ?? ""
let madLibSentence = "A \(singularNoun) \(pastTenseVerb) \(pluralNoun) and said,\(phrase)!"
print("\(madLibSentence)")
这段代码读取 text field、组合框、弹出按钮和 text view 中的文本,组合成 Mad Lib 金句。
注意一下 text view,它的 string 属性实际上是一个可空类型,因此它有可能为 nil。为了避免这种情况,我们需要用一个空合并操作 ??,当 string 为空时,用 “” 来代替。
目前我们暂时这样写——编译运行 app。
当你点击按钮,你会看到控制台中输出了一句短小、搞笑的话。
A dev ate tacos and said: Me coding Mac Apps!!!!
不错吧?但我们还可以让它更滑稽一点吗?
让电脑读出这句话怎么样?Steve Jobs(乔布斯)曾经表演过让 Mac 向世人问好。你可以让你的电脑也这样干……说干就干!
打开 ViewController.swift 在 ViewController 实现中添加代码:
// 1
fileprivate enum VoiceRate: Int {
case slow
case normal
case fast
var speed: Float {
switch self {
case .slow:
return 60
case .normal:
return 175;
case .fast:
return 360;
}
}
}
//2
fileprivate let synth = NSSpeechSynthesizer()
首先,我们声明了一个枚举,表示语速。然后创建了一个 NSSpeechSynthesizer 对象,用于将文本合成为语音。
然后在 ViewController 实现中添加方法:
fileprivate func readSentence(_ sentence: String,rate: VoiceRate ) {
synth.rate = rate.speed
synth.stopSpeaking()
synth.startSpeaking(sentence)
}
这个方法让 synth 对象以指定的语速念出某段文本。
让我们来调用它看看!在 goButtonClicked() 方法最后一句添加:
readSentence(madLibSentence,rate: .normal)
编译运行,点击 Go! 按钮,你会听到 Mac 大声念出你的金句。
结束
在本教程第二部分,我们将学习更多的 macOS 控件,包括 滑动条、日期选择器、单选按钮、勾选框和 image view——为了最终完成我们的 Mad Libs app,这些控件中的每一个都添加到其中。
同时,有任何问题和建议,请在下面留言!