Skip to content

Latest commit

 

History

History
1688 lines (1686 loc) · 74.7 KB

Mac OS X Development Tutorial for Beginners Part 3 - Your First OS X App.md

File metadata and controls

1688 lines (1686 loc) · 74.7 KB

macOS X 初学者开发教程 第三部分 你的第一个OS X App

featureImage_250

欢迎回到我们的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!

注意 Note: 这个app要求OS X El Capitan操作系统和Xcode7.1及以上的版本 - 确认你在尝试跟随这个教程之前已升级。

开始啦

打开Xcode并点击 Create a new Xcode project 来启动你的新app。选择 OS X \ Application \ Cocoa Application

01_project_template

设置产品名称为 MagicEight ,语言为 Swift 并确保 Use Storyboards 被勾选:

02_project_options

选择一个磁盘上的位置来保存你的项目,并点击 Create 来打开你的空项目。

运行 MagicEight 来检查每一项正在工作:

03_bar_01

很好 - 这样app运行起来了,但它确实 没有做任何事 。这样地启动了一个新的平台却没有首先创建“Hello World!”大概是不对的...

Hello World!

OS X app的用户界面是由你使用interface builder编辑的storyboard文件提供的,它让你用一种可见的方式来编辑UI,减少了大量你必须去写的复杂的代码。

通过在左手边窗格中的Project Navigator中选择 Main.storyboard 来打开它:

04_project_navigator

你会看到现在storyboard已打开在了中部的面板:

05_storyboard

模板的storyboard包含三个组件:

  • Menu Bar :控制当app运行时出现的菜单。
  • Window Controller :管理出现的默认窗口。使用一个或多个view controller来管理它们的内容。
  • View Controller 负责window中的一个区域,来展示和处理用户的交互。

在这个引导性的教程中,你将集中你的努力到view controller上。

添加一个Label

首先,你需要添加一个label到view controller上来展示你的欢迎文本。label事实上特定的类型是 NSTextField ,它被设计来展示一个单行的不可编辑的文本 - 对“Hello World”来说完美。

为了添加你的第一个label:

  1. 点击在工具栏中的图标来展示右手边的工具面板(utilities panel)。
  2. 使用靠近面板底部的图标来打开 Object Library 。这个,就如同它名称所表达的一样,是一个你可以拖拽到storyboard画布上的物品的目录,用来构建你的布局。
  3. 使用底部的搜索框搜索 label
  4. 你会发现label对象在library中被高亮了起来。

06_object_library

将label从object library拖拽到view controller上:

07_label_vc

为了label的文本内容,在Utilities panel中选择 Attributes Inspector 这个tab,找到 Title 域,并输入 Hello World!

08_hello_world

你可以使用attributes inspector来控制OS X提供的不同的组件的外观和行为。找到 Font 入口,点击右边的 T 图标来打开字体面板。改变Style为 Thin ,Size为 40 。点击 Done 来保存你的修改:

09_font

将你的眼睛投回到view controller的画布上,你会看到一些奇怪的事:

10_wrong_size

尽管你已经调整了label的文本和大小,但它仍然是那个尺寸,造成几乎全部的文本消失了。为什么会这样?

Basic Auto Layout 基本的自动布局

你把label放在了view controller的画布上,但你并没有实际地告诉OS X 怎么 来定位它。view controller在storyboard中以一种尺寸出现,但一旦运行在app中,用户是可以改变window的尺寸的 - 因此你需要指定对于 任意 的window的尺寸,这个label该怎么定位。

这听起来是一件相当困难的任务,但幸运的是苹果会帮你(has your back)。OS X使用了一个叫做Auto Layout的强有力的布局引擎,不同view的组件之间的关系被表达为约束(constraints)。这些约束在运行时会计算view中的每个原件的尺寸和位置。

注意: Note: Auto Layout是一个复杂的话题,在这个引导性的教程中不会覆盖任何的深度。如果你想要阅读更多关于这个话题的内容,这里有一些很棒的 教程 ,在这个地址上是面向iOS的 - 幸运的是这个引擎在这两个平台上几乎是完全相同的。

确保这个label已在view controller中被选中,单后在下面的工具栏中点击 Align 按钮。选择 Horizontally in Container ,确定在点击 Add 1 Constraint 去添加约束前,它的值被设置为 0

11_align

这会确保这个label总是出现在window的中央,无论window有多宽。你会注意到在storyboard上出现了一些“生气的”红色的线(some angry red lines):

12_angry_red

这是因为你在水平方向上提供了一个约束来确定label的位置,但你没有提供关于数轴的任何信息。

再一次地,确定这个label在view controller中已被选中,这次点击底部Auto Layout工具栏中的 Pin 菜单。在点击 Add 1 Constraint 前,输入 30 在顶部的约束框中,确保下方的“I”型条是实心红色的:

13_pin

你会立即看到这个view controller更新了 - 你现在可以看到完全的label了,那个生气的红色的线变成了平静的蓝色:

14_happy_storyboard

这就是的你的“Hello World”app完成了(对于现在)。使用“play”按钮来build和运行,检查你的作品(handiwork):

15_bar_02

祝贺!尽管它还不是非常个性化?在下一部分,你将发现怎么让传统的问候更友好一点。

处理用户的输入

在这一部分,你将添加一个text field来让用户可以输入它们的名字,这样你就可以个性化地欢迎他们了。

Control Layout 控制布局

在行动之前,使用object library来找到并拖拽一个 Text Field 和一个 Push Button 到view controller上。将它们放置到“Hello World!”label的上面:

16_position_textfield

记住,仅仅将控件放置到画布的上面是不足以让OS X理解,当window的尺寸发生变化时,你想怎么定位它们。你需要添加一些约束来传达你的意愿。

选择text field,然后 按住Command点击 button来同时选取。在storyboard底部的Auto Layout工具栏点击 Stack 图标:

17_stack

这就创建了一个新的包含text field和button的stack view。stack view会自动地生成要放置和包含view在一条线上的布局约束。你可以使用attributes inspector来配置很多stack view的共同的熟悉。

NOTE: 注意: 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点击 它们可以同时选择:

18_document_outline

和你刚才做的一样,使用底部工具栏的 Stack 按钮来创建一个新的stack view:

19_stack

在这个新的stack view被选中时,打开 Attributes Inspector 并设置Alignment为 Center X ,spacing为 20

20_stack_config

这就确保了这个label合上面的stack view漂亮地居中了,并且它们之间有20个点的间隔。

在将你的注意力转移到处理用户输入的任务之前,有一些布局需要去完成。

stack view处理了它内容中控件相互之间的位置,但它也需要在view controller的view中被定位。选择外部的stack view并使用 Align 这个auto layout菜单,来让它在容器中居中:

21_align

使用 Pin 菜单来固定住(pin)住stack view到顶部,并带有一个 30 的距离:

22_pin

最后,确保text field总是含有足够的用户名字的空间,你要修正它的宽度。

选择text field并使用底部工具栏中的 Pin 菜单,来指定 Width 100 。点击 Add 1 Constraint 来保存新的约束:

23_textfield_width

这样你的布局就较好地完成了 - 它应该看起来像下面这样:

24_layout

现在你可以将你的注意力转到那些你新添加的控件上了。

Outlets 和 Actions

选择text field并打开 Attributes Inspector 。在In the Placeholder这里输入 Name

25_name

这个灰色的文本将指示用户这个text field是用来干什么的,当用户开始输入时会立即消失掉。

选择按钮,再次打开 Attributes Inspector 。设置Title为 Welcome

26_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。

27_assistant_editor

这会拆分屏幕,并展示出一个代码文件在storyboard旁边。它本应该展示 ViewController.swift ,但如果不是的话,就使用跳转栏(jump bar)来选择 Automatic \ ViewController.swift

28_jump_bar

从storyboard中 右击拖拽 (或 按住control拖拽 )text field直到那条线到了 ViewController.swift 中的 override func viewDidLoad() 的上边:

29_textfield_outlet

确保 Outlet 已选中,并叫做 nameTextField

30_name_textfield

这将添加下列的属性到 ViewController 中,并在加载view controller时,更新storyboard来自动地连接text field:

@IBOutlet weak var nameTextField: NSTextField!

对“Hello World!”label正确地重复相同的过程,这次指定这个outlet应当被叫做 welcomeLabel

31_welcome_label

现在是时间把你的注意力转到button上了。再一次从storyboard中 Now time to turn your attention to the button. Once again 按住Control拖拽 Control-drag button到你的 ViewController 类上:

32_button_action

这次,改变这个连接到 Action ,并命名为 handleWelcome

33_action_settings

点击 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!”:

34_hello_sam

是不是相当酷?好的,乐趣不止于此。下一步你将学习关于添加image到你的app,来创建一个令人惊奇的magic-8 ball工具!

Assets

你在教程的开头被承诺了一个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的两面:

35_assets

图片资产保存在OS X app中的一个资产目录中。这个目录管理了由app图标和app中的图像所要求的,含有不同分辨率的图像(imagery)。

通过在project navigator中选择 Assets.xcassets 来打开它:

36_asset_catalog

你可以看到这个目录中当前仅包含了一个项目 - AppIcon 。这里就是你可以用艺术品(artwork)来给你的app一个很酷的图标的地方。

你需要将下载到的图片添加到asset目录中。在finder找到图片,并将它们都 拖拽到 asset catalog目录中:

37_drag_asset_catalog

这将在asset catalog中创建两个记录(entry):

38_added_assets

注意到这里有三个图片的“单元格”(cell)。这些单元格代表了三种不同的屏幕分辨率 - 标准的 (1x), retina (2x) 和 retina-HD (3x)。通常来说,你要为每一个单元格提供资产,但在这个简单的工程中你提供一个就行了。

你刚刚提供的图像实际上被设计为用作retina分辨率的 (2x),因此将它从左边的单元格拖到中间来:

39_drag_retina

对两个8-balls资产做同样的操作,asset目录现在看起来就像这样了:

40_finished_asset_catalog

那些图像现在可以被用在你的app中了 - 无论在代码中还是storyboard中。

Displaying Images 展示图像

你已经看过怎么在你的app中展示文本,按钮和文本输入框了,但对于图像呢?进入 ImageView

打开 Main.storyboard ,并使用object library来搜索 image view

41_image_view

将一个image view拖拽到view controller的画布上,放在stack view的底部。注意一条水平的蓝色的线是怎么出现的,它会指示新的image view会出现在stack view的哪里:

42_add_to_stack

打开 Attributes Inspector ,并设置Image属性为 8ball

43_set_8ball

这将使用来自asset目录的图像来更新画布:

44_vc_with_image

运行app来查看这个图怎么出现:

45_bar_03

好的,这个图出现了,但却被window裁剪掉了一部分。你可以以通常的方式拖拽来改变window的大小,但显然如果当app启动时,window的尺寸就是正确的就更好了,并且要保持一个固定的尺寸。

Window Size

MagicEight的window的初始尺寸是由storyboard中的view controller的尺寸决定的。在 Main.storyboard 中选择view controller的view,并打开 Size Inspector 。设置Width为 350 ,height为 480

46_vc_size

注意storyboard的画布现在看起来很奇怪:

47_stange_vc

这是因为布局的约束需要重新被计算。

使用在底部的自动布局菜单栏中的 Resolve Auto Layout Issues 菜单,并选择 All View in View Controller \ Update Frames

48_fix_al

这会在storyboard中重新运行布局引擎,并更新预览图:

49_fixed_vc

build并执行来查看app的样子:

50_bar_04

看起来好了很多。尝试调整window的大小 - 你会看到你仍然可以手动拖动它到对MagicEight的布局无效的尺寸。

在这儿你有几个选项 - 你可以更新布局,让它在window的尺寸改变时也适应得不错(例如对于小的window调整image的尺寸),或者你可以固定window的尺寸。你将使用第二种较容易的方法。

Window Sizing 调整Window的尺寸

window controller是用来负责管理window的尺寸的。打开 Main.storyboard ,并选择window controller中的window:

51_select_window

打开 Size Inspector ,并设定Content Size的Width为 350 ,height为 480 。然后点击 Minimum Content Size Maximum Content Size ,确认它们和你刚输入的content size相同:

52_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的下方:

53_wrapping_label

你想要将这个label放置在magic-8图片的中央,因此你需要添加一些自动布局的约束。

Document Outline 中, 按住control拖拽 Multiline Label Image View Document Outline

54_adding_constraints

在点击 Add Constraints 前,按住 shift 并选择 Center Vertically Center Horizontally

55_center_constraints

这个label不能自动地重新调整位置 - 但你之前已经处理了。使用底部自动布局工具栏中的 Resolve Auto Layout Issues 菜单,选择 All View in View Controller \ Update Frames

56_fix_al

这样就重新调整了label的位置,立即变得明显了,你需要在外表上做一些工作。

选择multiline label,使用 Attributes Inspector 来进行如下的设置:

  • Title Piece of Advice
  • Alignment Center
  • Text Color Keyboard Focus Indicator
  • Font System 20

57_label_config

前往 Size Inspector ,设置Preferred Width为 Explicit ,值为 75

58_label_size

为了了解产品最后可能的样子,选择image view,并使用 Attributes Inspector 来设置Image为 magic8ball

59_change_image

再一次的,点击主view,并使用 Resolve Auto Layout Issues\All Views in View Controller\Update Frames 去更新布局:

60_magic8_layout

这看起来相当好。当每次用户点击8-ball时,你需要响应去发现手势识别。

Gesture Recognizer

你可以在代码中创造一个动作,在每次用户点击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中:

61_gesture_recogniser

无论何时用户点击图片,这个gesture recognizer都会被激活 - 恰好是你想要的。现在你需要添加一些新的连接,然后你将为最后的冲刺,准备好切换到代码。

IB Connections

用你之前的相同的方式,打开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拖拽 到代码中:

62_gesture_action

改变Connection为 Action ,并命名为 handleBallClick

63_action_config

点击 Connect ,Xcode将添加下列的函数定义到 ViewController 类中:

@IBAction func handleBallClick(sender: AnyObject) {
}

现在你完成了所有在Interface Builder中的工作,你可以切回到标注编辑器中了,打开 ViewController.swift

在代码中操作UI

当用户点击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")
}
  1. 检查当前 adviceLabel 是否可见。 hidden 是一个在 NSView 上的布尔类型的属性(因此 NSTextField 也就有了),允许你指定是否要让view隐藏。
  2. 如果建议label当前被隐藏了,显示它,并改变图片为magic这边。 NSImage(named:) 会从asset目录中加载图片,而 NSImageView image 属性指定了要展示的图片。
  3. 反过来,如果当前建议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几次开始从这个球的智慧中受益:

64_final_bar

从这儿去向哪里

你可以下载完整版本的MagicEight在 这里

这个Mac OS X的开发教程引导系列已经给了你一个开始OS X app的基层的知识 - 但是这里有太多的东西要学!

苹果有一些很棒的文档,覆盖了OS X开发的全部方面 - 请前往 https://developer.apple.com/library/mac/navigation/