iOS_book 02 - 基本交互(约束、视图控制器、基本控件:按钮、文本框、分段控件、开关、标签、图像控件)

实现基本交互
MVC模式
Cocoa Touch 设计者们采用MVC(Model-View-Controller, 模型 - 视图 - 控制器)模式作为指导原则。
MVC 模式把代码功能划分为3个不同的类别。
模型: 保存应用程序数据的类。
视图:包括窗口、控件以及其他一些用户可以看到并能与之交互的元素。
控制器:把模型和视图绑定在一起的代码,包括处理用户输入的应用程序逻辑。
MVC的目标最大限度地分离这三类代码。MVC可以帮助确保代码的最大可重用性。
控制器组件通常有应用程序的具体类组成。控制器可以是完全自定义的类,但在大多数情况下,它是UIKit框架提供的通用控制器类的子类。
 
创建一个新的项目可以看到:
 
Xcode自动挡创建了一个名为ViewController的控制器类来负责管理应用程序的视图。
ViewController是UIViewController的子类。UIViewController是一个通用的控制器类,是UIKit框架的一部分。
 
输出接口和操作方法
控制器类可以通过一种名为输出接口(outlet)的特殊属性来引用storyboard或nib文件中的对象。可以把输出接口看做指向用户界面中对象的指针。
反过来,也可以对storyboard或nib文件中的界面对象进行设置,以触发控制器类中的某些特殊方法。这些特殊方法称为操作方法(action method),或者简称操作(action)。
输出接口和操作方法是创建iOS应用的两个最基础的模块。
Xcode支持多种创建输出接口和操作方法的方法。
一种是在源代码里先设定好,然后再使用Interface Builder将它们与相应的代码关联起来。
一种是通过在Xcode中从对象出拖动鼠标指针到想要关联的属性上(甚至可以拖动到类代码中想要创建新输出接口的地方)就可以创建输出接口了。
 
输出接口
在Swift里,输出接口是一个常见的属性,用修饰符@IBOutlet来标记。代码如下:
@IBOutlet weak var myButton: UIButton!
myButton属性的声明后面要加一个感叹号。这是因为Swift在初始化函数执行之前,所有属性都应该初始化,除非它被声明为一个可选值(optional)。
从storyboard中加载视图控制器时,输出接口属性的值会根据storyboard中保存的信息进行设置,但这一过程是在视图控制器的初始化函数运行之后发生的。这样,输出接口属性必须是可选值,或者可以特意为它们赋予无意义的临时值(这样不可取)。
有两种方法可以用来声明输出接口属性为可选值:
 @IBOutlet weak var myButton1: UIButton?
 @IBOutlet weak var myButton2 : UIButton!
第二种方法更易于使用,因为这样不需要再之后视图控制器代码用到可选值时特意去对它拆包:
let button1 = myButton1!  //可选值需要手动去拆包
let button2 = myButton2  //myButton 自动被拆包。
但是在Objective-C中输出接口是一种特殊属性,用IBOutlet关键字声明。输出接口可以在控制类的头文件中声明,也可以在控制器实现文件的类扩展(class extension)中的某个特定部分进行声明,如下所示:
@property (weak, nonatomic) IBOutlet UIButton *myButton;
以上都声明了一个名为myButton的输出接口,可以用它指向用户界面中的任何按钮。
Swift编辑器在看到@IBOutlet声明时不会进行任何特殊处理。它存在的意义仅仅是提示Xcode这个属性需要关联到storyboard或nib文件中的对象上。在Swift中,任何要与storyboard或nib文件中的对象进行关联的属性,都必须在前面写上@IBOutlet。
 
输出接口属性声明里weak指示符的意思是这个按钮的属性不是强引用类型。
当对象不再被强引用所关联的时候,就会立即自动释放。这种情况下按钮并不会有被释放的危险,因为只要它还是视图控制器中视图结构的一部分,就会有强引用存在。
设置属性为弱引用就能在不需要这个视图时将它从用户界面上移除并释放。完成释放后,属性的引用设备为空值nil。
 
IBOutlet并不属于Objective-C内置的关键字,仅仅是一个用C写成的简单的预处理指令,位于系统头文件中。它的定义如下:
#ifndef IBOutlet
#define IBOutlet
@endif
对于Objective-C编辑器来说,IBOutlet没有执行任何作用。他唯一的意义就是告诉Xcode,这个属性与storyboard或nib文件中的对象进行关联,任何要与storyboard或nib文件中的对象进行关联的属性,都必须在前面写上IBOutlet关键字。
操作方法:
在Swift中,操作方法是拥有@IBAction修饰符的方法,它告诉Interface Builder这个方法可以被storyboard或nib文件中的控件触发。
但在Objective-C中操作方法是一种返回类型为IBAction的特殊方法。
操作方法的声明通常如下:
Swift:
@IBAction func doSomething(sender : UIButton) {}
@IBAction func doSomething() {}
 
Objective-C(在.h文件的声明):
- (IBAction) doSomething: (id)sender;
- (IBAction) doSomething;
 
在Objective-C中飞返回类型必须是IBAction。而且,操作方法要么不接收任何参数,要么只接收一个参数,该参数通常命名为sender。在操作方法被调用的时候,sender会指向触发该方法的对象。
由于sender参数的存在,一个操作方法可以对多个控件做出响应。通过sender参数可以知道到底是哪个控件触发了这个操作方法。
 
如果声明了一个带有sender参数的操作方法,而在方法里面却没有使用到这个参数,也不会有任何问题。
 
类扩展:是一种特殊的Objective-C分类(category)声明,可以在其中声明方法和属性,但是这些方法和属性只能在当前文件的implementation块中使用。
将使用类扩展来放置视图控制器的输出接口,在iOS中是一种常见的方案。之所以把输出接口放在类扩展里,是因为在此视图控制器之外的代码中并不需要用到它们。
相对的,需要在其他类中使用的属性和方法应该在ViewController.h中声明。
Action的Event:
Touch Up Inside: 仅在用户的手指离开屏幕(且用户的手指在离开屏幕之前位于按钮内部)时触发相应的操作方法。
如果用户的手指在离开屏幕之前从按钮上移到别处,那么就不会触发这个方法。
Objective-C 是在实现类的类扩展里创建输出接口。
 
编写操作方法,为了区分上面两个button,需要用到sender参数。使用sender参数可以获取到被点击的按钮标题,根据这个标题创建一个新的字符串,然后把这个字符串作为标签的文本。代码如下:
Swift:
    @IBAction func buttonPressed(sender: UIButton) {
       let title = sender. titleForState(.Normal)!
       let plainText = "\ (title ) button pressed"
       statusLabel.text = plainText
   }
 
Objective-C:
- (IBAction)buttonPressed:( UIButton *)sender {
   NSString *title = [sender titleForState:UIControlStateNormal ];
   NSString *plainText = [ NSString stringWithFormat:@"%@ button pressed." , title];
   _statusLabel.text = plainText;
}
 
第一行通过sender参数获取被点击的按钮的标题。
由于按钮在不同状态下可以有不同的标题(在这这个例子里木有),因此使用UIController。Normal参数表明我们需要获取的是按钮在正常状态时(未被按下时)的标题。获取控件标题时,通常使用这种状态。
接下里的一行代码创建了一个新的字符串,字符串的内容是在上一行代码获取到按钮的标题末尾添加文本拼接而成。最后把文本赋给标签的text属性就行了。
自动布局实际上就是使用约束让控件的位置按照自己的意愿分布。
可以通过在代码中创建NALayoutConstraint类的实例来为视图添加自动布局约束。有时这是创建正确布局的唯一方法。不过大多数情况下可以通过Interface Builder来得到你想要的布局。
 
添加自动布局约束:
通过从某个视图拖到另一个,Interface Builder就会知道你想在它们之间使用自动布局约束。释放鼠标后会出现一个灰色的浮动框供选择。浮动框中的每一个选项都是约束。点击任意一个约束就会生效。可以通过按住Shift键来多选约束。
 
约束:
Center Horizontally in Container : 在容器中水平居中
Top Space to Top Layout Guide : 顶端与顶端之间相对距离。
 
Interface Builder使用橙色来暗示自动布局出现了问题。一般有3种问题会导致Interface Builder像这样的特殊标示:
1 : 你的所有约束不足以完全指定视图的位置和尺寸
2 : 视图的约束意义不明,即它的位置和尺寸有不只一种方案,无法确定。
3 : 约束是正确的,但视图的位置或尺寸在运行时与Interface Buidler所显示的不一致。
从左到右介绍自动布局按钮,位于storyboard编辑器的右下角:
1:旧版本的Xcoed的Resizing Behavior(尺寸变化行为)按钮(可以控制已有的约束在视图尺寸变化时的效果)已经替换成了stack按钮。
2:Align(对齐)按钮可以将你选中的视图与另一个视图对齐。
3:点击Pin(固定)按钮会弹出一个面板,通过上面的控件可以设定某个视图与另一个视图的相对位置并且使用尺寸约束。
4:Resolve Auto Layout Issues(解决自动布局问题)按钮可以纠正布局问题。可以使用弹出菜单的选项让Interface Builder移除某个视图(或整个storyboard)的所有约束,推测遗漏了哪种约束并补上,以及调整视图在运行时的布局。
这个按钮的弹出菜单有两组相同的操作,上面那组中选择某个操作,只会对当前选中的视图有效,而下面那组的操作对视图控制器中的所有视图均有效。
点击弹出菜单顶端的Update Frames选项,完成之后橙色的轮廓和活动视图中的警告三角都会消失,因为标签现在的位置和尺寸与运行时一致。也就是标签的宽度缩小到零,它在storyboard上表现为一个小正方形。
 
许多UIKit提供的视图(包括UILabel)都能让自动布局基于它们的实际内容设定尺寸。它们通过本体内容尺寸(即正常尺寸)进行计算。标签的本体内容就是宽度和高度刚好足够完全包住里面包含的文本。此时的标签没有内容,因此它的本体内容尺寸实际上应该宽高都是零。当我们运行app并点击某个按钮时,标签的文本会变化,而它的本体内容尺寸也会变化。自动布局会自动调整标签的尺寸以便你能看到全部的文本。
Pin按钮
选中left按钮并点击Pin按钮来调出弹出面板。在顶部会看到4个输入框通过橙色虚线链接着一个小正方形。小正方形代表我们想要约束的按钮。通过4个输入框可以设置按钮与上下左右的视图之间的距离。虚线表示目前没有约束存在。
 
想让left按钮与父视图的左侧之间保持一个固定的距离,点击正方形左边的虚线,之后它会变成一条橙色的实线,表示当前有约束生效。接下来在左边的输入框中填入32来设置Left按钮与它的父视图之间的距离。right按钮需要的是在右边的输入框中填入-32.
 
我们设定了所需的全部约束,但活动视图中还是有警告。如果进行查看就会知道原因是按钮在运行时的位置不正确。为了修复这个问题,可以再次使用Resolve Auto Layout Issues按钮,点击下面那组选项中的Update Frames,是因为我们需要视图控制器中所有视图的布局都得到调整。
添加字体类型
NSAttributedString类可以对字符串附加格式信息,比如字体和段落对齐。可以在整个字符串上使用这些元数据,也可以对字符串的不同部分使用不同的属性。
Swift:
    @IBAction func buttonPressed(sender: UIButton) {
        let title = sender.titleForState(.Normal)!
        let plainText = "\(title) button pressed"
        //statusLabel.text = plainText
        let styledText = NSMutableAttributedString(string: plainText)
        let attributes = [
            NSFontAttributeName:
            UIFont.boldSystemFontOfSize(statusLabel.font.pointSize)
        ]
        let nameRange = (plainText as NSString).rangeOfString(title)
        styledText.setAttributes(attributes, range: nameRange)
        statusLabel.attributedText = styledText
    }
 @IBOutlet weak var statusLabel: UILabel!
 
Objective-C
- (IBAction)buttonPressed:(UIButton *)sender {
    NSString *title = [sender titleForState:UIControlStateNormal];
    NSString *plainText = [NSString stringWithFormat:@"%@ button pressed.", title];
    //_statusLabel.text = plainText;
    NSMutableAttributedString *styledFText = [[NSMutableAttributedString alloc] initWithString:plainText];
    NSDictionary *attributes =
  @{
    NSFontAttributeName : [UIFont boldSystemFontOfSize: _statusLabel.font.pointSize]
    };
    NSRange nameRange = [plainText rangeOfString:title];
    [styledFText setAttributes:attributes range:nameRange];
    _statusLabel.attributedText = styledFText;
}
 
新代码做的第一件事就是基于我们要显示的文本创建一个属性字符串。具体说来,就是NSMutableAttributesdString的一个实例。这里需要使用可变的属性字符串,因为需要改变它的属性。
接下来创建一个用于保存字符串属性的字典。现在只需要一种属性,所以这个字典只包含一个键值对。通过名为NSFontAttributeName的键可以为属性字符串的一部分指定字体。这里传入的值是一种名为“bold system font”的字体,字体大小与标签当前使用的字体大小一致。相对于使用硬编码的字体名称,通过这种方式指定字体更为灵活。
然后,取得plainText字符串中待改变的子字符串(也就是这里的title)所属的范围(由一个起始索引和一个长度组成)。把属性应用到属性字符串,然后把属性字符串赋给标签。
let nameRange = (plainText as NSString).rangeOfString(title)
注意plainText变量从Swift类型String格式被强制转换成了Core Foundation类型的NSString。这是必须的,因为String和NSString都有名为rangeOfString()的方法。我们需要调用NSString的方法来获取NSRange对象作为范围,因为下一行setAttributes()方法会用到它。
 
应用程序委托:
AppDelegate.swift以及AppDelegate.h和AppDelegate.m,这些文件实现了应用程序委托(application delegate).
Cocoa Touch广泛地使用委托(delegate),委托是负责为其他对象处理特定任务的对象。通过应用程序委托,能够在某些预定义时间点为UIApplication类做一些工作。每个iOS应用程序都有且仅有一个UIApplication实例,它负责应用程序的运行循环,以及处理应用程序级的功能(比如把输入信息分发给恰当的控制器类)。UIApplication是UIKit的标准组成部分,主要在后台处理任务,所以一般来说不用管它。
 
在应用程序执行过程中的某些特定时间点,UIApplication会调用特定的委托方法(如果委托对象存在,并且实现了相应的委托方法)。
在这个文件顶部,可以看到应用程序委托实现了文档中列出的一个协议方法,即application(_,didFinishLaunchingWithOptions:)(Objective-C中为application:didFinishLaunchingWithOptions:).当程序完成所有的初始化工作,并且准备好与用户进行交互时,就会调用这个方法。这个方法经常用来创建要在应用程序运行的整个生命周期内活动的对象。
 
动态控件、静态控件和被动控件
用户界面控件共有三种基本模式:动态、静态(又叫非动态)和被动。
动态按钮:点击它们时会发生一些事情,通常是触发一段自己编写的事件代码。上例使用的按钮就是典型的动态控件。
被动控件:一些控件可以在被动状态下工作,仅用于存储用户输入的值,以备后续使用。这些控件不会触发任何操作方法,但是用户可以与之交互,并修改它们的值。被动控件的一个典型的例子是网页上的文本框。文本框本身不会触发任何代码,虽然可以在离开文本框时触发验证代码,但是网页上大多数文本框只是保存数据的容器。在点击提交按钮时可以把文本框的数据一起提交出去。
 
iOS设备上,大多数可用控件都可以通过这三种模式使用,并且几乎所有的控件都支持一种以上的模式,可以根据自己的需求选择合适的模式。
所有的iOS控件都是UIControl的子类,因此它们能够触发操作方法。
大多数控件都支持被动模式,并且所有控件都支持静态或者不可见模式。例如,使用某个控件时可能触发另一个静态控件成为动态控件。但是,包括按钮在内的一些控件,除了在动态模式下用来触发代码以外,实际上并没有其他用途。
 
iOS和Mac上的控件在行为上存在一些差异,下面是一些例子:
1、由于多点触控界面的引入,所有iOS控件都可以根据被触控的方式触发多个不同的操作方法。可以在用户点击按钮时触发一个操作方法,当用户手指在按钮上滑动时,在触发另一个操作方法。
2、可以在用户按下按钮时触发一个操作方法,在用户手指离开按钮时触发另一个操作方法。
3、可以让单个控件对单一事件调用多个操作方法。例如,可以让Touch Up Inside事件触发两个不同的操作方法,也就是说,当用户的手指离开按钮时这两个操作方法都会被调用。
在Xcode中选中Images.xcassets文件并点击编辑区左下角的加号按钮,这样将弹出一个选项菜单,选择New Image Set那项,就会出现一个新的位置来添加新的图片文件。给它取个唯一的名字,这样就可以在项目的任意位置引用它。 选中图标,调出属性检查器(Option + Command + 4),并在里面将图像的名字更改为apress_logo.
 
库面板顶部的第三个图标代表对象库。
 
图像视图中重要的设置就是位于检查器最顶部的Image属性,单击这个字段下拉框就会弹出一个列出了可用图像的菜单,其中包含添加到项目中资源目录的所有图像。选择之前添加的apress_logo图像。
 
我们使用的图像比容纳它的图像视图小很多。但是在Interface Builder面板上可以看到该图像被放大至完全填满了整个图像视图。原因在于,属性检查器中的Mode属性被设置为Scale To Fill(自适应缩放)。
虽然以这种方式使用图像和图像视图可以保证应用的正常运行,但是更好的方法是在运行前就做好图像缩放的工作,因为运行应用时,进行图像缩放需要消耗一些时间和处理器周期。
按下Command + = 或者选择Editor>Size to Fit Content,这样就可以将图像视图的大小自动调整到与其中的图像完全一致。如果使用Command + =没有效果,或者Size to Fit Content选项是灰色的,需要重新选择图像视图,稍微往边上拖动一点,然后再试一次。
 
 
设置视图属性:
 
检查器顶部显示的是特定于当前所选对象的属性,之后则是更为通用的属性(适用于所选对象的父类)。
在本例中,UIImageView的父类是UIView,所以下一部分的标签是View,其中包含了任何视图类都具有的属性。
 
Mode : 图像视图检查器中的第一个属性是名为Mode的弹出菜单。Mode菜单用于选择内容在视图中内部的显示方式。这决定了图像在视图内的对齐方式,以及是否缩放图像以适应视图大小。记住,选择任何导致图像缩放的选项都可能增加运行时的处理开销,因此最好避免使用这些选项,尽量在倒入图像之前就调整好它们的大小。如果希望以多种尺寸显示同一图像,最好在项目中导入该图像不同尺寸的多个副本,而不是强制让iOS设备在运行时对它们执行缩放。当然,在运行时也会有需要缩放或不得不缩放图像的情况,这里只是一般情况下的做法,不是硬性规定。
 
Tag:UIView的所有子类,包括所有视图和控件,都有一个tag属性。该属性只是一个数值,可以在这里设置,也可以在代码中设置。Tag是开发者使用的,系统永远不会设置或修改它的值。如果为某控件或视图设置了一个Tag值,那么这个值会一直不变,除非进行修改。
Tag是用于标识界面对象的一种与语言无关的方法。
Interaction: Interaction 部分 的两个复选框与用户交互有关。
第一个复选框User Interaction Enabled指定用户能否与当前对象进行交互。对于大多数控件来说,都应该选中这个复选框,否则控件永远不能触发操作方法。但是,图像视图默认不选中这个复选框,因为他们通常只用于静态信息的显示。
另一个复选框是Multiple Touch,它决定了当前控件能否接收多点触摸事件。多点触摸事件支持各种复杂的手势。比如许多iOS应用程序中用于缩放的双指捏合操作。
 
Alpha: 此选项需要格外小心。Alpha定义图像的透明度,就是图像背后内容的可见度。
Alpha是取值范围为0.0-1.0的浮点数,0.0是完全透明, 1.0是完全不透明。如果使用任何小于1.0的值,iOS设备就会将视图绘制成具有一定的透明度。即使图像背后没有任何内容,应用程序也会在运行时占用处理器周期来叠加半透明视图后面的空白区域。因此,除非有非常充分的理由,否则一般要将该值设置为1.0。
 
Background:用于确定视图的背景颜色。对于图像视图来说,只有当图像没有填满整个视图,或者显示在屏幕纵横比为4:3的ipad上,或者图像某些部分透明的情况下,这个属性才起作用。
 
Tint:可以让你指定所选视图的高光颜色。一些视图在绘制自身时会用到这个颜色。
 
Drawing:
第一个复选框为:Qpaque。这个复选框在默认情况下应该是选中的,如果没有,请单击选中它。Opaque选中时相当于告诉iOS当前视图的背后没有需要绘制的内容,同时,允许iOS的绘图方法通过一些优化来加速当前视图的绘制。
 
Hidden复选框的作用是选中它之后,用户就看不到这个对象了。
Clears Graphics Context:这一项基本不需要选中。如果选中它,iOS会在实际绘制对象之前使用透明的黑色绘制被对象覆盖的所有区域。考虑到性能问题,并且很少有这中需求,所以通常将其设置为关闭状态。
Clip Subviews:如果你的视图包含子视图,并且这些子视图没有完全包含在其父视图的边界内,那么这个复选框的值可以决定子视图的绘制方式。如果选中了Clip Subviews,那么只有位于父视图的边界内的子视图部分会被绘制出来,如果不选中,那么子视图就会被完全绘制出来,不管子视图是否超出了父视图的边界。考虑到性能,这个选项默认是关闭的。
Autoresize Subviews复选框,它告诉iOS在当前视图的大小发生变化时自动调整子视图的大小。
 
Stretching(拉伸):只有在屏幕上调整矩形视图大小导致重绘视图时,才需要拉伸。
该选项用于保持视图的外边缘(例如按钮的边框)不变,仅拉伸中间部分,而不是均匀拉伸视图的全部内容。
这里需要设置4个浮点值,用于指定一个矩形可拉伸区域的左上角坐标以及大小,这4个浮点数的取值范围都是0.0-1.0,代表整个视图大小的一部分。例如:如果希望每个边缘最外边的10%是不可拉伸的,那么就将X和Y都设为0.1,同时将Width和Height都设为0.8.
 
文本框
文本框是最复杂的iOS控件之一,同时也是最常用的控件之一。
文本框检查其设置:
第一部分最上方是Text属性,它旁边的两个控件可以让你控制文本框中显示的内容。上方的下拉列表按钮中有纯文本(Plain Text)和属性文本(Attributed Text, 可以包含各种字体和不同的属性)两种类型可供选择。
下方可以输入框可以设置文本框的默认值。输入的任何内容都将在应用程序启动时显示在文本框中,而不会是空白。
 
之后是一系列用于设置字体和字体颜色的控件。
Color弹出菜单分成两部分。右边的部分用于从一些预设的颜色中选择一种想要的颜色。
而左边的部分是一个调色板,可以在这里更精确地指定需要的颜色。
Font的设置分为三部分。
右边的控件可以用来增大或缩小文本大小(每次增大或缩小一个字号)。
左边部分可以用来手动编辑字体名称和字体大小。
最后,点击带T字样的图标可以打开一个弹出窗口,设置各种字体属性。
这五个按钮用于控制文本框中文本的对齐方式。
 
 
Placeholder(占位符),可以在这里输入一些文本,文本框的内容为空时,Placeholder的内容会以灰色文本显示在文本框中。如果空间不足,可以使用占位符来代替标签或者使用占位符来告诉用户应在这个字段中输入什么内容。
 
Background和Disabled,仅在需要定制文本框的外观时使用。多数情况下,完全不必要也不建议使用他们。
 
4个名为Border Style的按钮,用于更改文本框边框的绘制方式。
默认值(最右边的按钮)创建的文本框样式是iOS应用中最惯用的。
Clear Button弹出按钮,可以在这里设置何时出现清除按钮(clear button)。清除按钮是出现在文本框最右边的一个X形小按钮。清除按钮通常用在搜索框和其他需要频繁改变内容的字段中,需要持久 保存数据的文本框一般不包含清除按钮。
Clear when editing begins复选框指定用户触摸此字段时是否清除已有的文本。
 
Min Font Size可以用来设置文本框在显示文本时可以使用的最小字号。
Adjust to Fit复选框可以指定显示文本是否应随文本尺寸的变化而变化。如果选中,那么整个文本在视图中都是可见的,即使文本大于所分配的空间。这一项会与最小字号的设置协同工作。无论文本框大小如何,其中的文本都不会小于最小字号。
指定最小字号可以确保文本不会因为过小而影响可读性。
 
这部分用于定义使用此文本框时键盘的外观和行为。
Capitalization的值更改为Words。这可以保证每个输入的单词都会自动转换为首字母大写。
 
keyboard Type:选中Number Pad,在iPhone上用户所使用的键盘将只有数字。不需要为数字键盘设置Return Key,因为这种样式的键盘没有Return键。
在iPad上,选择Number Pad会在用户点击文本框调出全尺寸虚拟键盘时优先显示数字模式,不过用户也可以切换回字母输入。这一意味着在真实的应用程序中,还是要处理Number文本框的内容时验证用户实际输入的是不是有效数字。
 
Return Key弹出选项。Return键是虚拟键盘右下方的一个键,它的标签会根据用户正在进行的操作发生变化。
如果选中了Auto-enable Return Key复选框,那么在文本框内容为空时Return键将被禁用,直到至少在文本框中输入一个字符。
Secure Text Entry复选框指定是否在文本框中显示已输入的字符。如果此文本框用作一个密码字段,那么应该选中此复选框。
 
Control部分是用于设置继承UIControl的控件属性,但它们通常不适用于文本框(除了Enabled复选框),也不会影响文本框的外观。希望启用这些文本框,以便用户与之交互。
检查器最后一部分是View,它们继承自UIView类的属性。前面介绍过了。
 
添加约束:
在Interface Builder中,如果把一个视图拖动到另一个视图中,Xcode不会自动为它创建约束。因为布局系统需要完整的约束,所以编译应用程序的时候,Xcode会生成一系列默认约束,用于描述这种布局。
 
Xcode还可能根据同一个父视图中某个对象的一个或多个兄弟对象的位置自动创建约束,固定这个对象。这个自动行为也许不是你想要的,因此最好在应用程序编译之前就在Interface Builder中创建好整套约束。
 
我们实际上需要一整套约束在编译时告诉布局系统如何精确地管理所有视图和控件。这项工作很容易完成:选中所有的视图和控件,执行:Editor>Resolve Auto Layout Issues> Add Missing Constarints菜单项。完成这步后,可以看到一些蓝色细实线将所有的视图和控件彼此链接,并将其与容器视图连接。
每条实线代表一个约束,没有让Xcode在编译时生成约束,此时创建它们的好处在于可以根据需要修改每个约束。
还有一种方法可以为视图中所有的视图添加约束,方法是在文档略图中选中视图控制器并点击Editor>Resolve Auto Layout Issues > Add Missing Constrains菜单项。
只要使用了Editor>Resolve Auto Layout Issues > Add Missing Constrains菜单,就应该仔细地检查Xcode添加的约束。如果它没有按照你的预期起作用,就要删除它们自己手动添加约束。
 
创建文本框的输出接口:按住鼠标右键将视图中上面的文本框拖向ViewController实现文件源代码,拖到ViewController(Swift)或@interface(Objective-C)这一行下面,可以看到"Insert Outlet,Action, or Outlet Collection"。释放鼠标,就可以看到一个弹出窗口,然后就可以输入信息创建输出接口了并与文本框进行关联。
 
如果键盘没有在模拟器中出现,尝试点击Hardware > Keyboard>Toggle Software Keyboard菜单项。
 
iOS设备上的键盘是虚拟的,不是物理键盘,因此我们需要一些额外的步骤来确保用户完成输入后可以关闭键盘。用户按下Done按钮时,会产生一个Did End On Exit事件,此时需要让文本框交出控制权,以关闭键盘。为实现这个功能,需要在控制器类添加一个操作方法:
选中ViewController,在文件底部添加一下代码:
Swift:
@IBAction func textFieldDoneEditing(sender: UITextField) {
     sender.resignFirstResponder()
}
Objective-C
//ViewController.m
- (IBAction)textFieldDoneEditing:(id)sender {
     [sender resignFirstResponder];
}
 
再把这个操作方法与文本框关连起来。
 
第一响应者就是当前正在与用户交互的控件,在这个新方法中我们通知该控件放弃作为第一响应者的控制权,将其返回给用户之前操作的控件。一个文本框失去了第一响应者状态后,与之关连的键盘也将消失。
 
按下Option + Command + 6打开连接检查器。
 
并非所有的键盘布局都有Done按钮。可以实现触摸背景关闭键盘。
 
视图控制器有一个view属性,是从UIViewController继承来的。这个view属性对应于storyborad中的View。view属性指向storyboard中的一个UIView实例。这个实例是用户界面中所有元素的容器。它有时也称为容器视图(container view),因为它的主要用途是保存其他视图和控件。总而言之,
容器视图就是用户界面的背景。
 
实现触摸背景关闭键盘:
使用Interface Builder更改view所指向的对象所属的类,将它的底层类有UIView更改为UIControl。UIControl是UIView的子类,所以非常适用于将view属性链接到UIControl实例。(当一个类继承自另一个类时,子类其实是父类的一个更具体的版本,所以UIContol是一个UIView)。更改为UIControl类就可以获得触发操作方法的能力。
需要在控制器类中再添加一个操作方法,代码如下:
Swift:
@IBAction func backgroundTap(sender: UIControl) {
     nameField.resignFirstResponder()
     numberField.resignFirstResponder()
}
Objective-C:
//ViewController.m
- (IBAction) backgroundTap:(id)sender {
     [self.nameField resignFirstResponder];
     [self.numberField resignFirstResponder];
}
这个方法只是告诉两个文本框放弃第一响应者状态(如果处于该状态的话)。即使控件并非第一响应者,对其调用resignFirstResponder方法也是非常安全的,所以可以在这个两个文本框上都调用该方法,而不需要检查它们是否为第一响应者。
 
按下Option+Command+3打开身份检查器。在这里,可以更改storyboard中的任何对象实例的底层类。
所有能够触发操作方法的控件都是UIControl的子类,所以通过更改底层类,这个视图就可以触发操作方法。
按Option+Command+6调出关联检查器来验证这一点。
 
把Touch Down事件拖到View Controller图标上,然后选择backgroundTap操作方法。现在,触摸视图中没有动态控件的任何位置都将触发新的操作方法,这样就可以关闭键盘了。
添加滑动条:
使用检查器可以设置初始值,最小值为1,最大值为100,当前值是50.选中Events Continuous Update复选框,这样可以确保滑动条的值改变时可以触发一系列连续的时间。
由于标签中的“100”比“Label”短,Interface Builder会自动把标签的宽度减小。这种行为是自动的,但是仍然可以调整标签的大小,如果希望使用工具自动确定一个最合适的大小,可以按下Command+=或者选择Editor>Size to Fit Content
 
在文本略图中选中View Controller图标并点击Editor>Resolve Auto Layout Issues > Update Constraints菜单,Xcode会调整约束,这样屏幕上所有控件的位置将保持一致,而且活动视图的警告也会消失。
 
需要一个指向标签的输出接口,以便在滑动条滑动时能够更新标签的值,还需要一个操作方法,将在滑动条位置发生变化时被调用。
操作方法sliderChanged的实现:
Swift:
@IBAction func sliderChanged(sender: UISlider) {
     let progress = lroundf(sender.value)
     sliderLabel.text = "\(progress)"
}
Objective-C:
- (IBAction)sliderChanged: (UISlider *)sender {
     int proess = (int)lroundf(sender.value);
     self.sliderLabel.text = [NSString stringWithFormat:@"%d", progress];
}
这个方法的第一行是获取滑动条的当前值,将其四舍五入到最接近的整数,然后把这个整数赋给一个整型赋给一个整型变量。第二行代码是根据这个整型变量创建一个字符串,然后把这个字符串赋给标签。
这样就可以处理控制器对滑动条滑动作出的响应了,为了更好的一致性,需要保证用户触碰滑动条之前标签也能正确显示滑动条的值,在
viewDidLoad方法中添加如下代码:
Swift:
override func viewDidLoad() {
     super.viewDidLoad()
     sliderLabel.text = "50"
}
 
Objective-C:
- (void)viewDidLoad {
     [super viewDidLoad];
     //加载视图之后(通常从nib文件),进行一些额外设置
     self.sliderLabel.text = @"50";
}
这个方法会在应用程序加载storyboard的视图之后,显示在屏幕上之前执行。添加的这行代码能够保证用户立即看到正确的初始值。
 
运行程序可以看到,当显示的数值减少到只有一位数时,标签会在水平方向上向左收缩,变成100三位数时,标签会在水平方向上向右扩张。因为滑动条维护着一个约束,这个约束可以保证滑动条与标签之间的距离始终不变。其实这个约束是Iinterface Builder在帮助我们创建自适应式的图形用户界面时产生的一个副作用。这里是一个默认的约束在起作用,Interface Builder创建的某个约束是这些元素在水平方向的间距始终保持不变。
可以创建自己的约束来覆盖这种行为。回到Xcode,在storyboard中选中标签,然后从菜单中选择Editor>Pin>Width,这样就可以创建一个高优先级的约束。这个约束告诉布局系统"不要改变标签的宽度"。
 
开关、按钮和分段控件
开关是一种比较小的控件,只有开和关两种状态。
 
用鼠标右键按住分段控件并稍微向上移动,直到主视图的背景转为蓝色。释放鼠标并在弹出的菜单中选中Vertical Spacing to Top Layout Guide以固定分段控件与视图顶部之间的距离。
 
 
处理开关:鼠标右键按住左侧开关向左上角拖动后释放鼠标。按住Shift键并在弹出菜单中选则Leading Space to Container Margin 和Vertical Space to Top Layout Guide, 然后按下Add Constraints键以使用约束。
对另一个执行同样的操作,不过是向右上角移动,并选择Trailing Space to Container Margin 和Top Space to Top Layout Guide.
通过拖动来添加约束时,Xcode会根据你拖动的方向提供不同的选项。如果水平拖动,会出现让控件吸附父视图左右边缘的选项;如果垂直拖动,Xcode会认为你想要设置控件相对于父视图顶端或底部的位置。需要每个开关都有水平和垂直的约束,因此我们斜向拖动,告知Xcode同时需要水平以及垂直的选项。
 
为两个开关(Switch)添加的方法:
Swift:
@IBAction func switchChanged(sender: UISwitch) {
     let setting = sender.on
     leftSwitch.setOn(setting, animated: true)
     rightSwitch.setOn(setting, animated: true)
}
 
Objective-C
- (IBAction) switchChanged:(UISwitch *) sender {
     BOOL setting = sender.isOn;
     [self.leftSwitch setOn: setting animated: YES];
     [self.rightSwitch setOn : setting animated: YES];
}
 
用户按下任何一个开关都会调用switchChanged方法。在该方法中,简单地获取sender参数的属性值(sender代表被按下的开关), 然后使用这个值来设置两个开关。逻辑是:设置一个开关的值会同时改变另一个开关的值,让它们始终保持同步。
 
从iOS7开始,默认按钮的外观就非常简单,只是一段纯文本,没有轮廓、边框、背景颜色以及其他修饰。
iOS设备中的大多数按钮都是使用图像绘制的。
 
添加button图像,UIKit可以拉伸图像至任何你想要的尺寸大小。可拉伸图像是一个有趣的概念。可拉伸图像是可调整大小的图像,它知道如何智能地调整自身大小以维持恰当的外观。
对于这些按钮模板,我们不希望图像边缘跟其他部分一样被均匀拉伸。
边缘图像(edge inset)是一个图像的一部分(以像素为单位),不会被改变大小。希望边缘圆角能够保持原样,不随按钮尺寸的改变而改变,因此需要指定所有边缘不能被拉伸的区域范围。
 
选择start Slicing来切分图像。
 
 
一个UIButton按钮可以有多个状态,每个都有自己的文字与图像。之前我们只是设置了默认状态,所以把弹出菜单切换到Highlighted,这样就可以设定这个状态了。可以看到Background下拉框已经被清空。只需点击并选择blueButton就行。
 
可拉伸图像(stretchable image),是可调整大小的图像,它知道如何智能地调整自身大小以维持恰当外观。
 
控件状态(control state):
每个iOS控件都有如下4种状态,任何时候都处于并仅处于其中的一种状态。
1、普通(Default): 最常见的状态是默认的普通状态。控件在未处于其它状态时都为这种状态。
2、突出显示(Highlighted): 突出显示状态是控件正被使用时的状态。对于按钮来说,这表示用户手指正在按钮上。
3、禁用(Disabled):禁用状态是控件被关闭时的状态。要禁用控件,可以在Interface Builder中取消选中Enabled复选框,或者将控件的enable属性设置为NO。
4、选中(Selected):只有一部分控件支持选中状态。它通常用来指示该控件已启用或被选中。选中状态与突出显示状态类似,但控件可以在用户不在直接使用它时继续保持选中状态。
 
某些控件可以根据控件的不同状态接受不同的值。
 
为分段空间编写操作方法:
Swift:
@IBAction func toggleControls(sender: UISegmentedControl) {
     if sender.selectedSegmentIndex == 0 {
          leftSwitch.hidden = false
          rightSwitch.hidden = false
          doSomethingButton.hidden = true
     } else {
          leftSwitch.hidden = true
          rightSwitch.hidden = true
          doSomethingButton.hidden = false
     }
}
 
Objective-C:
- (IBAction)toggleControls: (UISegnmentedControl *) sender {
     // 0 == switches index
     if (sender.selectedSegmentIndex == 0) {
          self.leftSwitch.hidden = NO;
          self.rightSwitch.hidden = NO;
          self.doSomethingButton.hidden = YES;
     } else {
          self.leftSwitch.hidden = YES;
          self.rightSwitch.hidden = YES;
          self.doSomethingButton.hidden = NO;
     }
}
 
这段代码检查sender的selectedSegmentIndex属性,这样就可以知道当前选中的是分段控件的哪一部分。
第一部分的索引值是0
 
在属性检查器里标题为Background的颜色按钮可以打开OS X系统标准的颜色选取器。这个选取器的一个特点是可以选取屏幕上可见的任意颜色。
 
 
操作表单(action sheet) 和警告视图(alert)都用于用户提供反馈。
 
操作表单的作用是要求用户在两个以上选项之间作出选择。在Iphone上,操作表单从屏幕底部出现,显示一系列按钮供用户选择。
在iPad上,可以指定操作表单与另一个视图(一般是按钮)的相对位置。用户必须点击其中一个按钮之后才能继续使用应用程序。
操作表单通常用于用户确认有潜在危险的或者无法撤销的操作,比如删除对象。
 
警告视图以圆角矩形的形式出现在屏幕中央。与操作表单类似,警告视图也要求用户必须做出一个回应,然后才能继续使用应用程序。
警告视图通常用于通知用户发生一些重要的或者不寻常的事情。
 
要求用户必须先做出选择,然后才能继续使用的应用的视图称为模拟视图(modal view)
 
在ViewController的实现文件里,实现按钮的操作方法:
Switf:
@IBAction func buttonPressed(sender : UIButton) {
     let controller = UIAlertController(title: "Are You Sure?", 
                            message:nil, preferredStyle: .ActionSheet)
 
     let yesAction = UIAlertAction(title: "Yes, I'm sure!", style: .Destructive, handler: {
                                   action in 
                                   let msg = self.nameField.text.isEmpty
                                   ? "You can breathe easy, everything went OK."
                                   : "You can breathe easy, \(self.nameField.text),"
                                   + " everything went OK."
                                   let controller2 = UIAlertController(title: "Something was Done",
                                                                      message: msg, preferredStyle: .Alert)
                                   let cancelAction = UIAlertAction(title: "Phew!",
                                                                                style: .Cancel, handler: nil)
                                   controller2.addAction(cancelAction)
                                   self.presentViewController(controller2, animated: true, completion: nil)
                              })
 
               let noAction = UIAlertAction(title: "No way!", style: .Cancel, handler: nil)
               controller.addAction(yesAction)
               controller.addAction(noAction)
 
               if let ppc = controller.popoverPresentationController {
                    ppc.sourceView = sender
                    ppc.sourceRect  = sender.bounds
               }
               presentViewController(controller, animated: true, completion: nil)
}
 
Objective-c:
 
 
(IBAction)buttonPressed:( UIButton *)sender {
   UIAlertController *controller = [ UIAlertController alertControllerWithTitle : @"Are you Sure?"
                                                                       message: nil preferredStyle:UIAlertControllerStyleActionSheet ];
   UIAlertAction *yesAction = [ UIAlertAction actionWithTitle :@"Yes, I'm Sure!!"
                                                       style: UIAlertActionStyleDestructive
                                                       handler: ^( UIAlertAction *action) {
                                                         NSString * msg;
                                                         if ([ self. nameField. text length] > 0) {
                                                             msg = [NSString stringWithFormat : @"You can breathe easy, %@, everything went OK.",
                                                                    self. nameField. text];
                                                         } else {
                                                             msg = @"You can breathe easy, everything went OK." ;
                                                         }
                                                        
                                                         UIAlertController * controller2 = [UIAlertController alertControllerWithTitle: @"Something was Done"
                                                                                                                               message: msg
                                                                                                                        preferredStyle:UIAlertControllerStyleAlert ];
                                                         UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Phew!" style :UIAlertActionStyleCancel handler :nil ];
                                                        
                                                         [controller2 addAction: cancelAction];
                                                         [self presentViewController : controller2 animated: YES completion: nil ];
                                                     }];
  
   UIAlertAction *noAction = [ UIAlertAction actionWithTitle : @"No way!"
                                                      style: UIAlertActionStyleCancel handler :nil ];
   [controller addAction: yesAction];
   [controller addAction: noAction];
  
   UIPopoverPresentationController *ppc = controller. popoverPresentationController;
   if (ppc != nil) {
       ppc. sourceView = sender;
       ppc. sourceRect = sender. bounds;
   }
   [self presentViewController: controller animated: YES completion: nil];
 
}
 
解析这个方法:
首先操作方法中分配了一个UIAlertController对象进行初始化。
UIAlertController是视图控制器的子类,可以用来显示操作表单或警告视图。
   UIAlertController *controller = [ UIAlertController alertControllerWithTitle @"Are you Sure?"
                                                                       message: nil preferredStyle :UIAlertControllerStyleActionSheet ];
第一个参数是要显示的标题,这里提供的标题将显示在操作表单的顶部。
第二个参数是显示在标题下面的信息,字体小一些。
最后一个参数表示希望让视图控制器显示一个警告视图(UIAlertControllerStyle.Alert/UIAlertControllerStyleAlert)还是操作表单(UIAlertControllerStyle.ActionSheet/UIAlertControllerStyleActionSheet).
 
警告控制器默认不提供任何按钮,必须为每个添加到控制器中的按钮创建以恶UIAlertAction对象。
下面是为操作表单创建两个按钮的代码片段:
UIAlertAction *yesAction = [ UIAlertAction actionWithTitle :@"Yes, I'm Sure!!"
                                                       style: UIAlertActionStyleDestructive
                                                       handler: ^( UIAlertAction *action) {
                                                         NSString * msg;
                                                         if ([ self. nameField. text length] > 0) {
                                                             msg = [NSString stringWithFormat @"You can breathe easy, %@, everything went OK.",
                                                                    self. nameField. text];
                                                         } else {
                                                             msg = @"You can breathe easy, everything went OK." ;
                                                         }
                                                        
                                                         UIAlertController * controller2 = [UIAlertController alertControllerWithTitle : @"Something was Done"
                                                                                                                               message: msg
                                                                                                                        preferredStyle :UIAlertControllerStyleAlert ];
                                                         UIAlertAction *cancelAction = [UIAlertAction actionWithTitle :@"Phew!" style :UIAlertActionStyleCancel handler :nil ];
                                                        
                                                         [controller2 addAction : cancelAction];
                                                         [self presentViewController : controller2 animatedYES completion : nil ];
                                                     }];
  
   UIAlertAction *noAction = [ UIAlertAction actionWithTitle @"No way!"
                                                      styleUIAlertActionStyleCancel handler :nil ];
 
针对每个按钮, 指定标题、类型以及按钮按下时会调用的处理函数。共有三种类型可以选择
1、当按钮会触发破坏性的、危险的或不可逆的操作时,应该使用UIAlertActionStyle.Destructive(在Ovjective-C 中是UIAlertActionStyleDestructive), 例如删除或重写文件。这种类型的按钮标题会以红色粗体显示。
 
2、 如果是普通按钮(例如OK按钮), 触发的操作不是破坏性的, 可以使用UIAlertActionStyle.Default(在Objective-C中是UIAlertActionStyleDefault)。标题会使用蓝色一般字体显示。
 
3、Cancel 按钮可以使用UIAlertStyle.Cancel(Objective-C中是UIAlertStyleCancel).标题会使用蓝色粗体显示。
 
最后向控制器添加两个按钮:
   [controller addAction: yesAction];
   [controller addAction: noAction];
 
为了让警告视图或操作表单显示出来,需要让当前视图控制器来展示警告控制器。下面是展示操作表单的代码:
   UIPopoverPresentationController *ppc = controller. popoverPresentationController ;
   if (ppc != nil) {
       ppc. sourceView = sender;
       ppc. sourceRect = sender. bounds;
   }
   [ self presentViewController: controller animatedYES completionnil];
前5行是通过获取到警告控制器的悬浮展示控制器,并设置它的sourceView和sourceRect属性来设定操作表单会出现的位置。
最后通过调用视图控制器的presentViewController方法,将警告控制器作为展示的控制器以显示操作表单。
 
在iPhone上, 操作表单总是从屏幕底部弹出的。
在iPad上,它会以悬浮视图显示, 即一个小的圆角矩形, 有一个指向另一个视图(通常是触发显示的视图)的箭头。
可以看到, 悬浮视图指向Do Something按钮。 这是因为我们设置警告控制器的悬浮展示控制器的sourceView
属性指向那个按钮,而sourceRect属性为按钮的外型。
if (ppc != nil) {
       ppc. sourceView = sender;
       ppc. sourceRect = sender. bounds;
   }
要注意判断ppc是否存在。因为在iPhone上, 警告控制器无需使用悬浮视图来展示操作表单, 因此它的
popoverPresentationController属性的值为nil。 严格来说, 在Objective-C语言中这种判断是可选的,因为在Objective-C中允许向一个nil对象发送消息,不会对它进行任何处理。
不过这个判断可以是代码更明确:如果ppc的值为nil的话,就不需要设置悬浮视图控制器。
 
对于悬浮视图的位置,如果需要也可以更改:
方法是:设置悬浮展示控制器的permitttedArrowDirections属性,它可以限制悬浮视图箭头的方向。
下面是代码:
Swift:
if let ppc = controller.popoverPresentationController {
     ppc.sourceView = sender
     ppc.sourceRect = sender.bounds
     ppc.permittedArrowDirections = .Down
}
 
Objective-C:
    if (ppc != nil) {
        ppc.sourceView = sender;
        ppc.sourceRect = sender.bounds;
        ppc.permittedArrowDirections = UIPopoverArrowDirectionDown;
    }
iPad上少了一个“No Way!”按钮。在iPad上,警告控制器不会使用UIAlertStyle.Cancel(Objective-C中是UIAlertStyleCancel)类型的按钮,因为用户习惯了通过点击周围任意位置的方法来关闭悬浮视图。
 
NSString * msg;
if ([self.nameField.text length] > 0) {
   msg = [NSString stringWithFormat: @"You can breathe easy, %@, everything went OK.",
         self.nameField.text];
} else {
   msg = @"You can breathe easy, everything went OK.";
}
                                                         
UIAlertController * controller2 = [UIAlertController alertControllerWithTitle: @"Something was Done"
                                   message: msg
                                   preferredStyle:UIAlertControllerStyleAlert];
                                   UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Phew!" style:UIAlertActionStyleCancel handler:nil];
                                                         
                                  [controller2 addAction: cancelAction];
                                  [self presentViewController: controller2 animated: YES completion: nil]
在上面的代码中可以看到并没有去获取或设置警告控制器的悬浮展示控制器。这是因为在iPhone或iPad上,警告视图会出现在屏幕中央的小型圆角矩形视图中,所以不需要特意设置悬浮展示控制器。
posted @ 2016-03-27 01:58  三恒一书  阅读(616)  评论(0编辑  收藏  举报