原文地址 翻译:DeveloperLx
更新日志: 已由Ernesto García更新至 Xcode 8.2 / Swift 3 版。 之前 由 Michael Briscoe 更新至Xcode 6.3 / Swift 1.2。 初始版本 作者为 Ernesto García 。
欢迎回到macOS控件教程的系列的第二部分,也就是最后一部分。
在 这个教程的第一部分 ,你开始了构建一个Mad Lib风格的macOS app,在这里用户可以输入单词和短语来创建有趣的桔子。
继续下去,你会学到一些核心的macOS UI控件 - 也就是Text Fields,Combo Boxes,Pop Up Buttons,Push Buttons和Text Views.
在这个教程中的最后一部分,你会完成你的app,并学会如何使用下面的控件:
- Sliders
- Date Pickers
- Radio Buttons
- Check Boxes
- Image Views
在这个两部分的教程的末尾,你会对macOS的控件有一个很好的理解。
这个教程将从你中断的地方再捡起来。如果你还没有之前的项目,这里是第一部分的 最后的项目 。
是时候开始工作了!
slider是一个控件,让用户可以从一个范围中进行选择。slider有一个最小值和最大值,通过移动控件的“把手”,用户可以在两个限制中进行选择。slider可以是线性或径向的。你会问,两者间的区别是什么?
线性的slider可以是垂直的或水平的,他们让你可以通过沿着轨迹移动把手来选择一个值。关于线性slider的一个很好的例子,就是macOS中的鼠标偏好面板了:
径向slider有一点的不同 - 它是一个小小的带有把手的圆心,它可以360度地进行旋转。你可以点击和拖拽把手到要求的位置来选择一个值。关于径向slider,你可以在Adobe Photoshop中来找到一个很棒的例子,它用来定义一个渐变的角度,就像下面这样:
在macOS中负责这个的控件是 NSSlider 。
全部三种类型的slider(水平、垂直和径向)实际上都是一个控件
NSSlider
。唯一的区别只是它们的外表。Interface Builder对于每种类型的slider在Object Library中都有一个对象,如同下面展示的这样:
当使用slider的时候,通常你会执行两个任务,获取或设置当前的值,以及获取和设置slider让位的最高值和最低值。这些property被展示如下:
// getting & setting an integer value
let theInteger = mySlider.integerValue
mySlider.integerValue = theInteger
// getting & setting a float value
let theFloat = mySlider.floatValue
mySlider.floatValue = theFloat
// getting & setting a double value
let theDouble = mySlider.doubleValue
mySlider.doubleValue = theDouble
// getting & setting the minimum value of the range
let theMinimumValue = mySlider.minValue
mySlider.minValue = theMinimumValue
// getting & setting the maximum value of the range
let theMaximumValue = mySlider.maxValue
mySlider.maxValue = theMaximumValue
再一次,这里没有什么好奇怪的 - 如果你现在已学到了什么,它就是实现标准macOS控件的一个相当简单的练习。移步到下一部分,让你的app包含一个
NSSlider
!
打开 Main.storyboard 。在Object Library的面板中定位到 Label 控件,并将它拖拽到content view上Phrase label的下面。改变窗口的垂直方向的大小,并将 Go! 按钮向下拖拽,如果你需要给它空间的话。
双击控件来编辑它的默认文本,改变为 Amount: [10] 。找到 水平Slider 控件并将它拖拽到窗口上,并将它放置到label的右边。
点击slider并选择它。打开 Attributes Inspector 并设置最小值为2,最大值为10。改变当前的值为5。这会作为当用户首次运行app时,slider的默认的值。
确保 Continuous 已选中。这告诉slider来通知 any slider值的任何变化。
现在你创建两个outlet;一个是slider的,另一个则是label。稍等,你可能会说 - 有一点的不同。为何要给label添加一个label?
这是因为,这样就可以在slider的值发生变化的时候,更新label的文本来列出当前的数量;因此为何要给slider的 Continuous property。哈哈,现在就有意义了吧,不是吗?
打开Assistant editor并确保 ViewController.swift 已打开。 按住Ctrl拖拽label 到 ViewController.swift 上来创建一个新的outlet。
在弹出的屏幕中,将outlet命名为 amountLabel 。
对slider重复上面的过程,将outlet命名为 amountSlider 。
现在你需要添加一个 action ,当slider的值发生变化的时候进行调用。你早已在第一部分中为你的button创建了action,因此你会获得更多的实践!
选择slider并 按住Ctrl拖拽 到 ViewController.swift 中类定义中的任何地方:
在弹出的窗口中,确认设置connection为一个 action 而不是一个outlet。将它命名为 sliderChanged ,就像下面这样:
现在你需要在action被调用时更新label。
打开
ViewController.swift
并添加下面的代码到
sliderChanged()
中:
let amount = amountSlider.integerValue
amountLabel.stringValue = "Amount: [(amount)]"
快速回顾上面展示的代码,你第一次读取了slider当前的值,然后你label的值为一个包含slider的值的字符串。请注意,用户不能从UI中编辑
amountLabel
的值,但仍然可以通过编程的方式来编辑它。
注意:
这个例子使用
integerValue
获取了一个很好的整数,但如果你需要更加精确,你可以为你的slider使用
floatValue
或
doubleValue
的值。
运行项目。尝试来回滑动slider,来查看label的值跟随slider当前的值变化而变化:
有一个小问题。你注意到了么?当app第一次运行的时候,标签并不会展示正确的值!虽然这不是一个大问题,但看起来app并未完成。这是因为label仅会在slider的把手挪动的时候才会更新。
不要害怕 - 这很容易解决。
添加下列的代码到
viewDidLoad()
尾部:
// Update the amount slider
sliderChanged(self)
现在app就会在启动时调用
sliderChanged()
,它就会更新label。Neat!
运行项目。现在label就在一运行时展示正确的值,这是一个很小的接触,但这是“配合和完成”,让你的app看起来更优美的元素。
更复杂的值,例如日历日期呢?是的,macOS也有那些的处理了!:]
Date Picker是一个展示日期和时间的macOS控件,提供了一个方法来让用户编辑这些值。Date Picker可以被指定来展示一个日期,一个时间或同时展示日期和时间。在macOS中负责这个的控件是 NSDatePicker 。
Date Picker可以以两种风格的样式来展示:文本的,它的日期和时间信息将会被展示到text field中;以及图形的,它的日期会以一个日历的形式展现,时间则以一个时钟的形式来展现。你可以在macOS的日期&时间偏好面板中找到全部风格的例子:就像下面截图中展示的这样:
对于date picker,你最常见的要执行的任务就是获取和设置它的日期和时间了,已经设置在你的控件中所允许的最小和最大的日期/时间的值。用来实现这些的property已展示在下面!
// getting & setting the date/time value
let myDate = myDatePicker.dateValue
myDatePicker.dateValue = myDate
// getting & setting the minimum date of the range
let theMinimumDate = myDatePicker.minDate
myDatePicker.minDate = theMinimumDate
// getting & setting the maximum date of the range
let theMaximumDate = myDatePicker.maxDate
myDatePicker.maxDate = theMaximumDate
再一次的 - 这个控件拥有非常简单的getter和setter风格的接口来更新这些值。现在是时候(原谅我的调皮!)把这个控件放到你的项目上了。
跟着习惯的步骤,添加一个 Label 到你的窗口上。将它的标题改为 Date: ,对齐方式改为 向右 。在对象面板中找到 Date Picker ,并把它拖拽到窗口上,将它放到label的右边。如果需要的话,改变窗口的大小,并将 Go! 按钮向下移动:
为date picker创建一个outlet,就在之前你完成的每个控件的outlet的下面。在弹出的窗口中,将property命名为 datePicker 。
就像app中的其它控件一样,当运行你的app时,向用户展示一个默认的值会比较好。选取当前的日期作为默认值听起来是一个不错的选择!:]
打开
ViewController.swift
并添加下面的代码到
viewDidLoad()
方法的末尾:
// Set the date picker to display the current date
datePicker.dateValue = Date()
运行项目!你应该会看到你漂亮的新date picker正在展示当前的日期,就像下面截图中的一样:
Radio buttons是一个特殊类型的控件,经常以组的形式出现;典型地,它们会展示为一个带有在一边的可选按钮的选项的列表。它们的表现在某种程度上时独一无二的;在组中的任意按钮都可以被选择,但只要选择了其中一个按钮,就会将组中其它按钮的选择都取消掉。在相同的组中,同时只有一个按钮可以被选中。
关于radio buttons的一个展示一系列选项的很好的例子,就是iTunes的备份的选项,就像下面这样:
Radio buttons是以radio groups的形式组织起来的。
当你点击了组中的一个按钮,它就会被选中,并且系统会自动将组中其余的按钮取消选择。你只需要考虑获取和设置合适的值。多么得方便!
但你该怎么定义一个组?引用文档中所说的:
“Radio buttons自动地作为了一个组(选择一个按钮将取消全部其它相关的按钮),当它们拥有相同的父view和动作方法。”
因此,所有你需要做的就是添加radio buttons到一个view上,创建一个动作,并设计这个组中所有按钮的动作。
然后你只需要改变按钮的状态(On/Off),系统自己会管理取消选择其它的按钮。
// Select a radio button
radioButton.state = NSOnState
// Deselect a radio button
radioButton.state = NSOffState
// Check if a radio button is selected.
let selected = (radioButton.state == NSOnState)
再一次地,一个复杂的控件被削减为一些非常简单的方法。继续阅读,来了解如何在你的app中实现一个radio button控件!
添加一个新的 Label 到你的app上(你现在应该对此感到非常舒服了!),并将它的标题改为 Place: 。在Object Library面板中找到 Radio Button ,并将它拖拽到content view上,就在label的旁边。双击radio buttons,来将他们的标题分别改为: RWDevCon , 360iDev 和 WWDC 。
现在,你需要为每个radio button创建一个新的outlet - 现在你应当相当熟悉另一个动作了!打开 Assistant Editor 并 按住Ctrl拖拽 第一个radio button到 ViewController.swift 的源文件中,就在已存在的property的下面。将outlet命名为 rwDevConRadioButton 。对另外两个radio buttons重复同样的过程,将outlet分别命名为 threeSixtyRadioButton 和 wwdcRadioButton 。
运行项目。
点击radio button,你会立刻注意到一个问题。你可以选择全部的radio button。它们并没有表现得像一个组。为什么?因为要让不同radio button成为一个组,需要有两个条件:它们必须拥有相同的父view(这是关键),他们需要有一个共同的动作。在我们的app中并不满足第二个条件。
为了解决上述情况,你需要为这些按钮创建一个共同的动作。
你早已是个使用Interface Builder创建动作的专家了吧,对么?所以,我们来学习一种替代的方法,在代码中创建动作,并将其指派到Interface Builder的radio buttons上。
打开 ViewController.swift 并在类的实现中添加下列的代码:
@IBAction func radioButtonChanged(_ sender: AnyObject) {
}
这是一个典型的动作方法,它包含一个
@IBAction
的标注,因此Interface Builder就可以找到并使用它。现在,打开
Main.storyBoard
并指派这个动作给radio button。
前往
Document Outline
并
按住Control单击
(或右击)View Controller。在弹出的窗口中,找到
Received Actions
区,并从靠近
radioButtonChanged:
的圈拖拽到
RWDevCon
radio button的上面。只需简单的动作,你就将动作指派到了radio button的上面。
重复上述过程,将相同的动作指派到另外两个radio button上面。你的received action现在应当看起来就像是这样:
运行项目:
现在radio buttons就应该表现得像一个组了。
现在,你需要让 RWDevCon radio button成为app启动时的默认项。你只需要设置radio button的state为On,然后其它的按钮就会被自动地取消选择。
打开
ViewController.swift
。并添加下列的代码到
viewDidLoad()
方法的尾部:
// Set the radio group's initial selection
rwDevConRadioButton.state = NSOnState
运行项目。你会看到在app启动时, RWDevCon radio button已被选中。
Radio button是在你的app中切换值的一种方法,但还有着执行类似功能的一个另外的macOS控件 - check boxes!
通常,你会在app中使用check box来展示一些布尔值的状态。这些状态常常会以某种方式影响app的一个特性的打开或关闭。
你会在当用户打开或关闭一个功能的地方找到check box。你会在Settings app中几乎每一个屏幕中找到它。例如,在Energy Saver窗口中,你会使用check box来打开或关闭不同的选项。
使用check box相当容易;大多数情况下,你只需要关心获取和设置这个控件的状态。check box的状态可以是下列之一: NSOnState (在所有地方打开特性), NSOffState (在所有地方关闭特性)以及 NSMixedState (在部分地方打开特性,但并非全部)。
这里是你使用它的方式:
// Set the state to On
myCheckBox.state = NSOnState
// Set the state to Off
myCheckBox.state = NSOffState
// Get the state of a check box
let state = myCheckBox.state
超级得简单!是时候添加一个checkbox到你的app上。
打开 Main.storyboard 。在Object Library中找到 Check Box Button 并将它拖拽到content view上。双击它,并将标题改为 Yell!! 就像下面图中展示的这样:
现在,为check box添加一个outlet并将它命名为 yellCheck 。你现在已经正式地成为一个创建outlet的专家了!
现在,你会将check box在app启动时的默认值设为off。为了做到这点,添加下列的代码到
viewDidLoad()
的尾部:
// set check button state
yellCheck.state = NSOffState
运行项目!你应当看到check box的状态是未勾选的。点击它来查看其行为:
一个segmented control,是由 NSSegmentedControl 类所代表的,它是当你需要从一些选项中做出一些选择时,对radio button的替代品。你可以在Xcode的Attributes Inspector中找到它:
它是非常容易被使用的。你只需要获取或设置被选择的segment来找出用户的选择。
// Select the first segment
segmentedControl.selectedSegment = 0
// Get the selected segment
let selected = segmentedControl.selectedSegment
如果你还记得,
readSentence()
方法含有一个控制语速(Normal, Fast, Slow)的参数。你将使用一个segmented control来改变语速。
打开 Main.storyboard 并添加一个label到content view上面。将它的标题改为 Voice Speed: 。找到 Segmented Control 并将它拖拽到content view上面。你可以双击控件的每个segment来改变他的标题。将标题分别改为 Slow , Normal 以及 Fast 。
在
ViewController
中为segmented control创建一个outlet,并命名为
voiceSegmentedControl
。现在,你想要在app一启动时选择Normal segment,它对应的序号是1(segment的序号是从0开始计算的)。打开
ViewController.swift
并添加下面的代码到
viewDidLoad()
:
// Set the segmented control initial selection
voiceSegmentedControl.selectedSegment = 1
就像它看起来一样得容易。只需将
selectedSegment
的property设置为1。现在,运行项目,查看Normal segment是否已选中。
OK!你已经添加了所有创建你有趣的mad lib句子所需的控件。你所缺少的只是收集每一个控件的值,并将它们连接成一个句子,并将它们展示到屏幕上!
你需要再来两个控件展示结果:一个label用来展示完整的句子,已经用来展示图片的image view,使用户的交互显得更加有生气!
打开 Main.storyboard 。在Object Library面板中找到 Wrapping Label 并将它拖拽到窗口上,就在 Go!! 按钮的下方。通过 Attributes Inspector 来让它看起来更漂亮:将border改为Frame,就是那四个按钮中的第一个。
之后,双击label移除它的默认文本,选择文本并删除它。
现在你必须创建一个outlet以方便设置这个新的label的值,来容纳你的新的滑稽的桔子!就像之前一样, 按着Ctrl拖拽 label到 ViewController.swift 文件中并将property命名为 resultTextField 。
现在,离开这个控件,让它停留在现在的样子;你将在稍候编写一些代码来填充它。
Image View是一个非常简单易用的控件 - 惊喜! - 展示一个图像。我敢打赌你都想不到是这样!:]
在运行时,你需要和Image View的几个property进行交互:
// Get the image from an image view
let myImage = myImageView.image
// Set the image of an image view
myImageView.image = myImage
在设计的时候,你可以配置其外观:border, scaling和alignment。是的,这些property也可以在代码中进行设置,但在设计时在Interface Builder中设置它会更加容易,就像下面这样:
是时候添加一个image view到你的app上了!在 Object Library 中找到 Image Well 并拖拽到view上,放到wrapping label的左边。如果必须的话,放心地去调整app窗口的大小。
用和你之前创建其它控件相同的方法,为image view创建一个新的outlet: 按着Ctrl拖拽 image view到 ViewController.swift 文件上,在弹出的窗口中将property命名为 imageView 。
运行项目。你的app现在看起来应该是这样:
哇!你的UI已经最终完成了 - 剩余的唯一一件事就是撰写代码来组装欢闹的句子,并填充你上面添加的image view!
现在你需要基于那些输出来组装句子。
当用户点击 When the user clicks the Go! 按钮的时候,你会从不同的控件中收集全部的值,来组装成完整的句子,然后在你之前添加的wrapping label中展示出来。
然后,为了装饰全文界面,你会在你上一部分添加的image view上展示一个图片。
为这个项目(正常地进入你的 下载 目录)。下载 资源文件 。如果下载到的zip文件没有自动解压,就解压它,得到 face.png 图像文件。在 Project Navigator 中选择 Assets.xcassets ,并将图片文件拖拽到assets列表中。
现在是最终的时间来添加app的核心了 - 构建Mad Lib句子的代码!
打开 ViewController.swift 并添加下列的property到类的实现中:
fileprivate var selectedPlace: String {
var place = "home"
if rwDevConRadioButton.state == NSOnState {
place = "RWDevCon"
}
else if threeSixtyRadioButton.state == NSOnState {
place = "360iDev"
}
else if wwdcRadioButton.state == NSOnState {
place = "WWDC"
}
return place
}
上面的代码添加了一个计算型的property,它会根据哪个radio button被选中返回place的名称。
现在,用以下的代码替换
goButtonClicked()
方法中的全部内容:
// 1
let pastTenseVerb = pastTenseVerbTextField.stringValue
// 2
let singularNoun = singularNounCombo.stringValue
// 3
let amount = amountSlider.integerValue
// 4
let pluralNoun = pluralNouns[pluralNounPopup.indexOfSelectedItem]
// 5
let phrase = phraseTextView.string ?? ""
// 6
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
let date = dateFormatter.string(from: datePicker.dateValue)
// 7
var voice = "said"
if yellCheck.state == NSOnState {
voice = "yelled"
}
// 8
let sentence = "On (date), at (selectedPlace) a (singularNoun) (pastTenseVerb) (amount) (pluralNoun) and (voice), (phrase)"
// 9
resultTextField.stringValue = sentence
imageView.image = NSImage(named: "face")
// 10
let selectedSegment = voiceSegmentedControl.selectedSegment
let voiceRate = VoiceRate(rawValue: selectedSegment) ?? .normal
readSentence(sentence, rate: voiceRate)
看起来有好多的代码,但当你把它拆开一部分一部分看时,每部分都是相当明确的:
-
从
pastTenseVerbTextField
获取文本。 -
在这部分代码中,你通过
stringValue
property从combo box中或取字符串。你可能会问,为何不只在选中的行进行查阅,然后检索关联到这行的字符串。非常简单,这是因为用户可以输入它们自己的文本到combo box中,因此选择使用stringValue
来获取当前的字符串,它既可以被选择,也可以被输入。 -
接下来,使用
integerValue
方法读取slider当前的值。记住,如果你需要更精确的控件,你还可以使用floatValue
或doubleValue
。 -
这里,你从popup button中获取了复数名词。这如何做到?在你的
pluralNouns
数组中使用下标语法,找到恰当的复数名词,并获取其indexOfSelectedItem
property。 -
接下来是用户输入的短语。要获取它,只需从
string
property中获取我们的text view的字符串的值。再一次,你使用了nil coalescing语法,因为这个property是可选的,它的值可能是nil
。 -
调用date picker的
dateValue
方法获取日期。然后使用 NSDateFormatter 将其妆花为一个人类可读的字符串。 -
你应该说还是喊?只需获取checkbox的状态:如果它是
NSOnState
,指派 yelled 到字符串变量中。否则,让它使用默认的 said 。 - 现在,你已经收集了所有你构建mad lib句子所需要的信息!这是神奇的事将发生的地方,基于用户的输出,从各种控件中的不同的值构建成了一个字符串。
-
现在你所有你努力工作所得出的结果了可以进行展示了!首先,通过设置results label的
stringValue
property来展示最终的句子。然后,通过向用户展示一副图像,来给app添加一些魅力,这和加载图像并设置image view控件的property一样得简单。 - 最后,大声地说出来吧!从当前被选择的segment获取语速,并调用将文本转换为朗读的方法。
就是这个!你已经完成了!运行项目app,你已可以为自己构建一些欢闹的句子。
祝贺 - 你已经完成了构建Mad Lib app,并沿途学习了大量关于最常用macOS控件的知识。
随意玩弄控件,选择不同的值,输入有趣的名称或动词,每次点击 Go! 按钮时来查看你创建了一个多么有趣的故事!:]
这里是包含这篇教程中全部代码的 最终项目 。
为了获得对macOS控件的更深的理解,我推荐你去阅读下表列出的苹果编程指南,它们包含了大量的关于如何使用可用的控件的信息。
特别低,我强烈地推荐你阅读 macOS人机交互指南 。这篇指导解释了关于macOS用户交互的概念和理论,以及苹果对于开发人员如何使用控件和设计他们UI的期望,来提供一致和令人愉快的体验。任何想要为Mac平台进行开发的人都应当去阅读一下,尤其计划要通过Mac App Store发布它们的应用的时候。
这里有一些有用的链接来增强,或更进一步地探索你在这篇教程中学到的概念: