在本课中,你将连接FoodTracker应用的基本UI到代码,并定义一些用户可以在这UI上进行的操作。完成后,应用将如下所示:
image: ../Art/CUIC_sim_finalUI_2x.png
学习目标
结束本课时,你将能够:
- 解释storyboard场景和底层控制器之间的关系。
- 在storyboard中的UI元素和源代码之间创建outlet(插座)和action(行动)。
- 处理用户在text filed上的输入并将结果显示在UI上。
- 使一个类符合协议
- 理解代理模式
- 在设计应用架构的时候,遵从目标-动作模式。
连接UI到源代码上
storyboard中的元素被链接到源代码。了解故事板与您编写代码的关系很重要。
在storyboard中,一个场景显示一屏幕的内容,通常也表示一个视图控制器。视图控制器实现应用的行为。一个视图控制器管理一个单一的内容视图,这个内容视图又有着它自己的子视图层次结构。视图控制器协调应用数据模型(data model,它封装应用的数据)和显示这些数据的视图之间的信息流,管理它们的内容视图的生命周期,处理当设备旋转时的方向改变,定义应用的导航,以及实现对用户输入的响应行为。所有iOS的视图控制器对象都是UIViewController及其子类的对象。
你可以通过创建和实现自定义的视图控制器子类,进而使用代码来定义视图控制器的行为。稍后你将创建一个在这些类和场景之间的连接,用来得到定义在代码中的行为和定义在storyboard中的用户界面。
Xcode已经创建了一个你之前看过的类,ViewController.swift,并且已将其连接到storyboard中的那个场景。将来,你会添加更多场景,你要自己在Identity inspector(身份检查器)中建立连接。Identity inspector让你编辑storyboard中的一个对象的属性,这个属性和这个对象的身份有关联,例如这个对象属于哪个类。
image: ../Art/CUIC_inspector_identity_2x.png
在运行时,storyboard创建一个ViewController的实例,你的自定义视图控制器子类。storyboard中的场景出现在设备的屏幕上,而用户界面的行为定义在了ViewController.swift中。
虽然场景连接到了ViewController.swift, 但那不是唯一需要连接的。为了定义应用的交互,你的视图控制器源代码需要能够和storyboard中的视图进行通信。你可以在storyboard的视图和视图控制器的源代码之间定义额外的的被称为outlet和action的连接来做到。
为UI元素创建Outlet
Outlet提供了一个从源代码文件引用界面对象(添加在storyboard中的对象)的方式。要创建一个outlet,按住Control键,从storyboard的特定对象拖拽到视图控制器文件。这个操作会在视图控制文件中创建一个该对象的property,这个property让你能够在运行时从代码访问和操作这个对象。
你将创建用户界面中的text filed和label的outlet,从而可以引用它们。
连接text field到ViewController.swift代码
- 打开storyboard,Main.storyboard.
-
点击Xcode工具条靠近Xcode右上角的Assistant按钮来打开助理编辑器(assistant editor)。
image: ../Art/assistant_editor_toggle_2x.png -
如果更多的空间,点击Xcode工具条上的Navigator和Utilities按钮把project navigator和utility area折叠起来。
image: ../Art/navigator_utilities_toggle_on_2x.png
你也可以折叠outline view。
-
在编辑器选择器栏中,它出现在助理视图的顶部,把助理视图从Previw to Automatic转换到ViewController.swift。
image: ../Art/CUIC_switchtoviewcontroller_2x.png
ViewController.swift显示在右侧的编辑器中
- 在ViewController.swift中,找到class那一行。它看上去像这样
class ViewController: UIViewController
- 在class行的下面,输入:
//MARK: Properties
你刚刚添加了一条注释到你的代码。回想一下,注释时源代码文件的一段文本,不会被编译为程序的一部分,但是它提供了关于特定部分代码的上下文或者有用信息。
一条从//MARK:开始的注释是一种特殊类型的注释,它通常用来组织你的代码并帮助你(或其他读你代码的人)浏览它。你可以稍后看到这种行为。具体来说,这段你添加的注释表明,这是一部分罗列属性的代码。
- 在storyboard中,选择text field。
-
按住Control从画布拖拽text filed到右侧编辑器的代码中,拖到刚添加的注释的下面然后停止。
image: ../Art/CUIC_textfield_dragoutlet_2x.png -
在弹出的对话框总,Name选择nameTextField。其他选项保持原样。对话框应该如下所示:
image: ../Art/CUIC_textfield_addoutlet_2x.png - 点击Connect。
Xcode添加必要的代码到ViewController.swift来存储一个指向text filed的引用,并且配置storyboard来设置这个连接。
@IBOutlet weak var nameTextField: UITextField!
花点时间理解一下这行代码做了什么。
IBOutlet属性告诉Xcode你能从Interface Builder(这就是为什么属性有IB前缀的原因)连接到nameTextField属性。weak关键字表明这个引用不会阻止系统释放被引用的对象。Weak引用帮助防止引用环的产生;然而,为了保持对象在内存中存在,你需要确保应用的其他部分对这个对象有强引用。在这种情况下,它是text field的超视图。一个超视图有着其所有子视图的强引用。只要超视图还存在于内存中,那么所有它的子视图也都存在。类似的,视图控制器对它的内容视图有一个强引用——保持整个视图层次结构存在于内存中。
声明的剩余部分定义了一个名为nameTextField的UITextField类型变量,这个变量是一个隐式解包可选变量(implicitly unwrapped optional).特别注意声明结尾处的感叹号。这个感叹号表明这个类型是隐式解包可选类型,它是一个在第一次设置后就会一直有一个值的可选类型。当你访问一个隐式解包可选类型对象时,系统会认为它有一个有效的值并自动为你解包。注意,如果这个变量的值还没有被设置,那么应用会终止。
当一个视图控制器从storyboard被加载时,系统实例化视图层次结构,并分配合适的值给所有视图控制器的outlets。这时,视图控制器的viewDidLoad()方法被调用,因为系统已经为所有控制器的outlets分配了有效的值,所以你可以安全的访问它们的内容。
现在,用连接text field相同的方式连接label到你的代码。
连接label到ViewController.swift代码
- 在storyboard中,选中label。
-
按住Control从画布拖拽label到右侧编辑器的代码中,拖到nameTextField属性的下面然后停止。
image: ../Art/CUIC_label_addoutlet_2x.png - 点击Connect。
再次,Xcode添加必要的代码到ViewController.swift来存储一个指向label的引用,并且配置storyboard来设置这个连接。这个outlet类似于text field的那个,除了名字和类型有所区别(这次的类型是UILabel,与storyboard中的对象类型相匹配)。
@IBOutlet weak var mealNameLabel: UILabel!
如果你打算访问来自接口对象的值或修改代码中的接口对象,那你只需要一个接口对象的outlet。这种情况,你需要设置text field的delegate属性和label的text属性。你不需要修改button,所以没有必要为它创建一个outlet。
Outlets让你在代码中引用界面元素,但当用户和元素交互时,你仍需要一种方式来响应。现在来说说行动。
定义一个Action来执行
iOS应用是基于事件驱动编程(event-driven programming)的。也就是说,应用的工作流取决于事件:系统事件和用户行为。用户在界面中实施一个行为会触发应用的事件。这些事件会引起应用逻辑的执行和应用数据的操作。应用对于用户行为的响应之后会反映到用户界面。当应用的某些代码执行的时候,因为是用户(而不是开发人员)在控制,所以你需要确定哪些动作(action)用户能执行以及这些动作的响应是什么。
一个action(动作,或称为动作方法)是一段代码,它连接到一个可以在应用中发生的事件。当这个事件发生时,系统执行这个action的代码。你能定义一个action方法来完成从操作一段数据到更新用户界面的所有事。你使用action方法来驱动应用的工作流,来响应用户或系统的事件。
你可以用创建outlet的相同方式创建一个action:按住Control键从storyboard中拖拽一个特定的对象到视图控制器文件。这个操作会在视图控制器文件中创建一个方法,它会在用户与这个附加有action方法的对象交互时被触发。
创建一个简单action方法,这个方法可以让用户在任何时候点击Set Default Text按钮时把leable设置为默认文本。(把lebel的文本设置为text filed中的文本有点复杂,我们将在处理用户输入Process User Input部分写它们。)
在ViewController.swift中创建名为setDefaultLabelText的action方法
- 在ViewController.swift中,在花括号(})的上面,添加下面这个注释
//MARK: Actions
这行注释表明,这个部分的代码是罗列actions 方法的。
- 在storyboard中,选择Set Default Label Text 按钮。
-
按住Control键,从画布中拖拽Set Default Label Text按钮到右侧编辑器中的显示代码处,到刚添加的注释的下面时停止拖拽。
image: ../Art/CUIC_button_addaction_2x.png - 点击连接。
Xcode添加必要的代码到ViewController.swift来设置这个action方法。
@IBAction func setDefaultLabelText(_ sender: UIButton) {
}
Sender参数引用的对象是触发这个action的对象,在本例中是一个按钮。IBAction属性表明这是一个action方法,你可以从Interface Builder中的storyboard连接到它。剩下的声明声明了这个方法的名字:setDefaultLabelText(_:)。
现在,方法声明是空的。重设label值的代码非常简单。
在ViewController中实现label重设action方法
- 在ViewController.swift中找到你刚添加的setDefaultLabelText action方法。
- 在两个花括号之间,添加如下代码
mealNameLabel.text = "Default Text"
就像你猜的,这个代码设置label的text属性为Default Text。注意,你无需指定Default Text的类型,因为Swift的类型推断能够看到你正在分配String类型,并且能够正确的推断类型。
iOS为你处理所有重绘代码,所以,这就是现在你需要写的所有代码。你的setDefaultLabelText(_:) action方法看上去就像这样:
@IBAction func setDefaultLabelText(_ sender: UIButton) {
mealNameLabel.text = "Default Text"
}
image: ../Art/CUIC_sim_defaulttext_2x.png检查点:运行模拟器来检查改变。当点击Set Default Label Text按钮时,你的setDefaultLabelText(_:)方法被调用,mealNameLabel对象的text值从Meal Name变为Default Text。你应该可以在你的用户界面上看到这种改变。
让菜品名变为“Default Text”没有什么大用,但是它说明了一个重要的点。你刚刚实现的行为是iOS的目标动作(target-action)设计模式的一个例子。目标动作模式是一种当指定行为发生的时候一个对象给另一个对象发送消息的设计模式。
在本例中:
- 事件是用户点击Set Default Label Text按钮。
- 动作是setDefaultLabelText(_)。
- 目标是ViewController(action方法被定义的地方)。
- 发送者是Set Default Label Text按钮
系统通过调用目标中的action方法和传递发送者(sender)对象来发送消息。Sender通常是控件,例如按钮、滑块(slider)、或开关(switch),这些控件能够触发事件来响应用户的点击、拖拽和改变值等交互操作。这种模式在iOS 应用编程时非常常见,你在本课中还将看到大量这种模式。
(未完待续......)
网友评论