在这一节课中,你将会学会把基本的用户界面(UI)与FoodTracker app的代码联系起来,并为界面上的组件定义一些动作和事件让用户可以进行操作和查看到。
Learning Objectives
在开始之前,需要定下这节课的学习目标:
1、可以清晰的解释storyboard和它关联的view controller之间的关系
2、学会从UI 组件与源代码之间创建outlets关联和action关联。
3、从输入框中传递用户的输入值给代码,并把处理结果展示在UI中
4、根据协议protocol创建一个class
5、了解委托模式
6、在设计应用架构时,遵循针对对象的模式。
Connect the UI to Source Code
storyboard中的组件们联系到源代码中,理解storyboard和源代码之间的关系对你来说非常重要。
在一个storyboard中,一个视图表现屏幕中的内容通常由与它相关的一个view controller(视图控制器)进行控制。View controller实现你app的所以事件。一般来说,一个view controller管理一个界面(包括一个主视图和它的子层)。View controller也负责协调界面流程与数据库间的数据流转。它还负责每个视图如何展示这些数据,管理父层视图的生命周期,获取手机方向旋转时的信息,定义app页面间的导航跳转,响应用户的动作和输入进行代码实现。所有的view controller都是UIViewController类或者它的子类。
你可以在view controller中的代码或运用view controller的各种子类来定义自定义的一些动作。你还可以在storyboard中为这些类、界面之间创建关系,做用户界面布局等。
Xcode已经做了第一个示例类,ViewController.swift,它已经自动跟在storyboard中创建的页面关联了起来,以后,当你创建更多的界面,你需要自己去连接界面与class,可以用Identity inspector设置你的storyboard属性,比如当前界面是月哪个class相连。
在运行时,你的storyboard创建了一个ViewController的实例,也就是你自定义视图的子类,界面将参照storyboard在设备上显示出来,用户的交互行为可以在ViewController.swift文件中被定义。
虽然默认在ViewController.swift中已经与界面做了关联,但这仅仅是最基础的关联。你需要在代码里与storyboard中的组件进行更多的关联来定义你想在app中定义的交互。你需要添加 “outlet”和“actions”来定义这些在storyboard中的视图和view controller 代码文件中的关联。
Create Outlets for UI Elements
Outlets 提供了一种对象映射的方法,它将可以让你把storyboard中的对象映射到代码文件里。如何添加一个Outlet呢,在storyboard中选中你要关联的对象,然后按住control,再点击拖动到一个view controller文件中。这个方法为storyboard的对象在代码中创建了一个值属性。你可以在代码中接收或修改操纵它。
现在来说,你需要创建一个outlets给app中的text field和label文件。让它们在代码中表现出来。
To connect the text field to the ViewController.swift
通过按住control并拖动到 ViewController的class中,注意是放在class声明属性的地方,别放在某个函数内。然后你就可以看到这么一条:
@IBOutletweakvarnameTextField:UITextField!
然后我们也把label给拖进来。然后我们来看看,这些代码都表示什么意思?
IBOutlet属性告诉Xcode可以通过这个IBOutlet连接到interface Builder里面的一个nameTextField的属性(这也是为什么前面有个IB前缀的意思)。weak关键字声明了这种引用并不能防止系统断掉这些对象引用(?没明白),但是Weak 引用有效的避免无效的延长引用周期。但是从另一面来说,你的项目需要保持活跃在内存中的话,总需要app的部分对象引用是强关联的。在这个例子中,text field的父类就是强关联的,一个父类对它所有的子类都维持着强关联的关系。父类在内存中保持活跃的同时,它的子类也一样保持活跃。类似的,view controller对它下面包含的内容view以及这些view下面的层都拥有强关联。
注意这个属于UITextField类型的nameTextField是一个强制解包的可选类型。注意最后的 “ ! ”符号,这个符号表示确定这个optional属性一定有值并把它解包出来,如果你接收到一个强制解包的可选类型,系统会认为它一定有值并自动为它解包,但是如果它实际上并没有值得话,这会导致app崩溃。
当一个视图控制器从storyboard中加载时,系统将实例化,对view controller中的outlets属性赋予合适的值。同时,view controller的viewDidLoad()函数被唤起,系统将会为是所有的controller里面的outlets属性赋值,这样你就可以安全的访问里面的内容。同上,我们为Label添加outlet属性。s
outlet只在你需要获取界面元件的值或者在代码中改变这个交互原件,在这个例子中,你需要设置text field的委托属性和label的文本属性,所以需要用到outlet。但是你不需要修改button本身,所以你不需要为它创建一个outlet。那么outlet让你可以与前端元件在代码中进行交互,但你还需要一个可以反馈用户操作的部分,我们称之为事件,这就是actions属性派上用场的地方。
Define an Action to Perform
iOS apps是基于对象来进行创建的,所以整个app的流程是由各个事件构成的:比如系统事件和用户行为。用户与app在界面上进行交互,然后唤起app中的事件,这些事件作用于app的逻辑并修改某些数据。然后再返回结果到用户界面。因为用户不同于开发者,他们无法定义app在那些地方将会执行什么东西。所以开发者你需要清晰的定义你期望的用户行为将会触发什么事件,并如何执行。
一个action(或者成为action方法)是一小段代码,它连接着一些你app上可触发的事件。当事件运行时,系统将会执行对应连接的action方法代码。你可以用action方法完成任何事情,从操作一个数据到刷新用户界面都可以用action方法实现。且你需要用它来驱动你的app在与用户发生交互时按照流程执行下去。
创建action和创建一个outlet是一样的,按住Control键,然后从storyboard中拖动对应对象到一个view controller 文件中。这样为这个对象在视图控制器(view controller)中就创建了一个对应方法,当用户对这个对象发生交互时,action方法就会被唤醒。
我们先来创建一个简单的方法,是通过“Set Default Text ” button 来设置label中的text在点击button时,变成“Default Text”。(从text field里面获取文本值并赋值给label会更加复杂,我们将会在Process User Input章节中进行学习)
注意拖拽的时候是拖到函数范围,选择的是Action属性,最重要的一点是,我们可以看到默认在Type区域的是AnyObject,这代表着这个对象可以属于任何类型。但是我们需要只让这个action方法对应到UIButton类,代表着只有button对象可以连接这个action,尽管现在我们看不出它有什么用,但是后面你将会发现它的重要性。然后我们在它的action方法内写下执行代码,最后如下:
@IBActionfuncsetDefaultLabelText(_sender:UIButton) {
mealNameLabel.text="Default Text"
}
在这里面,sender的后面跟着的是对象连接的类型,这里是UIButton,所以我们发生事件的对象应该是一个button,IBAction属性表明这个方法是从storyboard到视图控制器中的一个映射action方法。剩余的代码声明了一个叫setDefaultLabelText(_:)的函数方法。并在方法里执行让mealNameLabel的text变成“Default Text”。现在我们来运行一波:
成功啦!现在点击Set Default Label Text button我们成功的将mealName改成了Default Text。
我们发现系统在唤起action方法时发送了信息给发送对象。发送器一般是一个控件——比如一个按钮(button),滑条(slider)或者一个开关(switch)——这些可以通过用户的行为来触发一些事件比如一个点击,拖动或者值改变。这个响应式布局在iOS app中非常常见,你将会在这个课程中接触更多这方面的知识。
Process User Input
我们现在实现了的是把meal name label中的文本重置为default value,但是实际上你想要的是用户输入什么名称,点击按钮,就可以显示用户自定义的名称在meal name上。简单来说,用户输入文本并回车,再点击按钮,就可以改变label的值。
当你准备接收text field里的用户输入值,你需要text field delegate的帮助。delegate往往是一个对象,作为其他对象的参考或者协调作用。这个被委托对象(delegating object)——在这个案例中,就是text field——与另外一个委托对象保持关系,在适当的时候,text field作为委托者向被委托对象发起消息。这个消息告诉委托者去调用被委托者的一个事件。然后委托者就会根据这个事件函数去执行比如刷新展示页面或者状态,又或者改变app中的其他对象,甚至返回一些值来对即将执行的事件进行引导。
text field的委托对象与text field在用户在text field中编辑文本时进行交互,然后在一些重要的关键节点执行事件代码——比如用户开始或者停止编辑text field中的文本。委托者可以在这些时候保存或清除数据、禁用键盘、收起键盘等等。
任何服从对应协议的对象都可以作为其他对应对象的委托。比如说定义了一个text field的委托叫UITextFieldDelegate。我们常把对象的委托对象放在一个view controller中去管理,在这个例子中,你将会将text field的委托对象放在ViewController里。
首先,ViewController需要更新服从于UITextFieldDelegate协议,你需要在代码中作如下声明:
首先,我们找到ViewController类
然后在后面加个逗号,然后输入UITextFieldDelegate协议
通过上述代码植入了UITextFieldDelegate协议后,这代表着你告诉了编译器:ViewController类可以作为一个text field对象的委托,这意味着你可以调用协议中的方法来处理text field中的文本输入,然后你可以将ViewController的实例指定为text field的委托。
下面我们就在ViewController中设置一个叫nameTextField属性的委托
在ViewController.swift中,你会找到一个viewDidLoad()方法,像这样的:
这个方法里包含了一个注释说明,我们不需要这个注释,删除它。然后再super.viewDidLoad()代码行的下方,我们加入如下代码:
这里面,self引用了ViewController这个类,因为它在类中引用了self。你也可以为代码加上你自己的注释,方便以后对代码进行回顾查询。
When a ViewController instance is loaded, it sets itself as the delegate of its nameTextField property.
这意味着一旦实例化了ViewController,那么这个实例就是作为nameTextField属性的委托对象。
The UITextFieldDelegate protocol defines eight optional methods. Just implement the ones you need to get the behaviors you desire. For now, you’ll need to implement two of these methods:
UITextFieldDelegate协议定义了八种方法。你可以选择你需要的方法来实现你需要的效果,当前你需要选择其中的两种:
要明白这些方法是如何被调起和它们需要做什么,一个很重要的点事你需要知道text field是如何反馈用户事件的。当一个用户点击输入栏(text field)时,它就成为了当前第一位的响应。在一个app中,第一位的响应者将会接收到各种app的事件,比如关键点事件,动态事件,或者动作消息等。换句话说,app很多事件都会发送给处于第一位的响应者。
输入栏成为第一位的响应者的结果就是,iOS展示输入键盘并开启输入栏的编辑任务,即显示用户在键盘上输出的内容。
当用户想停止对输入栏的编辑,那么输入框将会从第一响应者状态退出来。因为停止对输入栏的编辑代表着输入栏已经不再是app中的活跃对象(即用户操作的对象),用户的视线和操作将会对更合适的对象发生事件。
现在是UITextFieldDelegate方法该起作用的地方了。你需要在用户点击按钮去结束text field的编辑时,就重新唤起text field作为第一响应者的状态。上述的动作需要在textFieldShouldReturn(_:)方法,这将在用户点击“Return(或在这个例子,Done)”时被调起。
执行UITextFieldDelegate协议中的方法textFieldShouldReturn(_:)
在ViewController.swift文件中,在//MARK:Actions section的上面,添加如下提醒:
这个提醒可以让你(或者其他读你代码的人)通过这个来找到对应的方法对象。
到现在为止你已经添加了一些评论了。Xcode把这些有特殊标注的注释加入到源代码的Functions menu的队列里,你可以在顶端的编辑栏里点击文件名字打开这个列。方法队列可以让你快速的跳转到代码中的指定方法部门去。你会在这个行列中看到通过//MARK:listed here这种标注的特殊性,也可以试着点击它们来跳转到代码中的对应位置。
在这个注释下面,我们声明TextFieldShouldReturn(_:)方法。
输入栏在用户点击了"Return"按钮的时候唤醒这个方法,你可以在这个方法里实现自定义的行为代码。比如说你想让用户点击“Return”后,键盘缩下,那么你需要调用resignFirstResponder()方法。添加如下的代码来让text field的第一响应者状态清空.
第二个你需要调起的方法即textFieldDidEndEditing(_:),当text field从第一响应者状态被释放时,就需要调起这个方法。它基本上都是跟随在textFieldShouldReturn()后面。textFieldDidEndEditing(_:)方法让开发者有获取text field里面被输入的内容并对它们进行编辑操作。在这个例子里,你需要获取到这个text并改变label的值。
在这节课中,你学会了如何添加outlets以及动作到你的代码中,你还可以在代码中对这些映射对象添加控制来改变交互界面。这个项目还是一个非常简单、单个的界面,你即将为它添加更多复杂的功能给它。