键盘事件
在涉及到表单输入的界面中,我们通常需要监听一些键盘事件,并根据实际需要来执行相应的操作。如,键盘弹起时,要让我们的UIScrollView自动收缩,以能看到整个UIScrollView的内容。为此,在UIWindow.h中定义了如下6个通知常量,来配合键盘在不同时间点的事件处理:
- UIKeyboardWillShowNotification // 键盘显示之前
- UIKeyboardDidShowNotification // 键盘显示完成后
- UIKeyboardWillHideNotification // 键盘隐藏之前
- UIKeyboardDidHideNotification // 键盘消息之后
- UIKeyboardWillChangeFrameNotification // 键盘大小改变之前
- UIKeyboardDidChangeFrameNotification // 键盘大小改变之后
- 这几个通知的object对象都是nil。而userInfo字典都包含了一些键盘的信息,主要是键盘的位置大小信息,我们可以通过使用以下的key来获取字典中对应的值:
- // 键盘在动画开始前的frame
- let UIKeyboardFrameBeginUserInfoKey: String
- // 键盘在动画线束后的frame
- let UIKeyboardFrameEndUserInfoKey: String
- // 键盘的动画曲线
- let UIKeyboardAnimationCurveUserInfoKey: String
- // 键盘的动画时间
- let UIKeyboardAnimationDurationUserInfoKey: String
在此,我感兴趣的是键盘事件的调用顺序和如何获取键盘的大小,以适当的调整视图的大小。
从定义的键盘通知的类型可以看到,实际上我们关注的是三个阶段的键盘的事件:显示、隐藏、大小改变。在此我们设定两个UITextField,它们的键盘类型不同:一个是普通键盘,一个是数字键盘。我们监听所有的键盘事件,并打印相关日志(在此就不贴代码了),直接看结果。
1) 当我们让textField1获取输入焦点时,打印的日志如下:
- keyboard will change
- keyboard will show
- keyboard did change
- keyboard did show
2) 在不隐藏键盘的情况下,让textField2获取焦点,打印的日志如下:
- keyboard will change
- keyboard will show
- keyboard did change
- keyboard did show
3) 再收起键盘,打印的日志如下:
- keyboard will change
- keyboard will hide
- keyboard did change
- keyboard did hide
从上面的日志可以看出,不管是键盘的显示还是隐藏,都会发送大小改变的通知,而且是在show和hide的对应事件之前。而在大小不同的键盘之间切换时,除了发送change事件外,还会发送show事件(不发送hide事件)。
另外还有两点需要注意的是:
如果是在两个大小相同的键盘之间切换,则不会发送任何消息
如果是普通键盘中类似于中英文键盘的切换,只要大小改变了,都会发送一组或多组与上面2)相同流程的消息
了解了事件的调用顺序,我们就可以根据自己的需要来决定在哪个消息处理方法中来执行操作。为此,我们需要获取一些有用的信息。这些信息是封装在通知的userInfo中,通过上面常量key来获取相关的值。通常我们关心的是UIKeyboardFrameEndUserInfoKey,来获取动画完成后,键盘的frame,以此来计算我们的scroll view的高度。另外,我们可能希望scroll view高度的变化也是通过动画来过渡的,此时UIKeyboardAnimationCurveUserInfoKey和UIKeyboardAnimationDurationUserInfoKey就有用了。
我们可以通过以下方式来获取这些值:
- if let dict = notification.userInfo {
- var animationDuration: NSTimeInterval = 0
- var animationCurve: UIViewAnimationCurve = .EaseInOut
- var keyboardEndFrame: CGRect = CGRectZero
- dict[UIKeyboardAnimationCurveUserInfoKey]?.getValue(&animationCurve)
- dict[UIKeyboardAnimationDurationUserInfoKey]?.getValue(&animationDuration)
- dict[UIKeyboardFrameEndUserInfoKey]?.getValue(&keyboardEndFrame)
- ......
- }
实际上,userInfo中还有另外三个值,只不过这几个值从iOS 3.2开始就已经废弃不用了。所以我们不用太关注。
***说下表单。一个表单界面看着比较简单,但交互和UI总是能想出各种方法来让它变得复杂,而且其实里面设计到的细节还是很多的。像我们金融类的App,通常都会涉及到大量的表单输入,所以如何做好,还是需要花一番心思的。空闲时,打算总结一下,写一篇文章。
#p#
零碎
自定义UIPickerView的行
UIPickerView的主要内容实际上并不多,主要是一个UIPickerView类和对应的UIPickerViewDelegate,UIPickerViewDataSource协议,分别表示代理和数据源。在此不细说这些,只是解答我们遇到的一个小需求。
通常,UIPickerView是可以定义多列内容的,比如年、月、日三列,这些列之间相互不干扰,可以自已滚自己的,不碍别人的事。不过,我们有这么一个需求,也是有三列,但这三列需要一起滚。嗯,这个就需要另行处理了。
- 在UIPickerViewDelegate中,声明了下面这样一个代理方法:
- - (UIView *)pickerView:(UIPickerView *)pickerView
- viewForRow:(NSInteger)row
- forComponent:(NSInteger)component
- reusingView:(UIView *)view
我们通过这个方法就可以来自定义行的视图。时间不早,废话就不多说了,直接上代码吧:
- - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
- PickerViewCell *pickerCell = (PickerViewCell *)view;
- if (!pickerCell) {
- NSInteger column = 3;
- pickerCell = [[PickerViewCell alloc] initWithFrame:(CGRect){CGPointZero, [UIScreen mainScreen].bounds.size.width, 45.0f} column:column];
- }
- [pickerCell setLabelTexts:@[...]];
- return pickerCell;
- }
我们定义了一个PickerViewCell视图,里面根据我们的传入的column参数来等分放置column个UILabel,并通过setLabelTexts来设置每个UILabel的文本。当然,我们也可以在PickerViewCell去定义UILabel的外观显示。就是这么简单。
不过,还有个需要注意的就是,虽然看上去是显示了3列,但实际上是按1列来处理的,所以下面的实现应该是返回1:
- - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
- return 1;
- }
Constructing an object of class type ‘**’ with a metatype value must use a ‘required’ initializer.
Swift中”[AnyObject]? does not have a member named generator” 问题的处理
有个小需求,需要遍历当前导航控制器栈的所有ViewController。UINavigationController类自身的viewControllers属性返回的是一个[AnyObject]!数组,不过由于我的导航控制器本身有可能是nil,所以我获取到的ViewController数组如下:
- var myViewControllers: [AnyObject]? = navigationController?.viewControllers
- 获取到的myViewControllers是一个[AnyObject]?可选类型,这时如果我直接去遍历myViewControllers,如下代码所示
- for controller in myViewControllers {
- ...
- }
编译器会报错,提示如下:
- [AnyObject]? does not have a member named "Generator"
实际上,不管是[AnyObject]?还是其它的诸如[String]?类型,都会报这个错。其原因是可选类型只是个容器,它与其所包装的值是不同的类型,也就是说[AnyObject]是一个数组类型,但[AnyObject]?并不是数组类型。我们可以迭代一个数组,但不是迭代一个非集合类型。
在stackoverflow上有这样一个有趣的比方,我犯懒就直接贴出来了:
To understand the difference, let me make a real life example: you buy a new TV on ebay, the package is shipped to you, the first thing you do is to check if the package (the optional) is empty (nil). Once you verify that the TV is inside, you have to unwrap it, and put the box aside. You cannot use the TV while it's in the package. Similarly, an optional is a container: it is not the value it contains, and it doesn't have the same type. It can be empty, or it can contain a valid value.
所有,这里的处理应该是:
- if let controllers = myViewControllers {
- for controller in controllers {
- ......
- }
- }