iOS:UIResponser控件的介绍(响应者)
@interface UIResponder : NSObject
方法:
//获取下一个响应者
- (UIResponder*)nextResponder;
//当它放弃对象响应者,可以设置自身成为第一响应者,默认为NO
- (BOOL)canBecomeFirstResponder;
//设置第一响应者
- (BOOL)becomeFirstResponder;
//如果一个对象可以放弃对象响应者就返回YES,默认返回YES
- (BOOL)canResignFirstResponder;
//放弃第一响应者
- (BOOL)resignFirstResponder;
//是否是第一响应者
- (BOOL)isFirstResponder;
//触摸开始
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
//触摸移动
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
//触摸结束
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
//触摸取消
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
//运动开始
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
//运动结束
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event ;
//运动取消
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event ;
//远程控制接收事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event ;
//处理命令事件
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender ;
//初始化并处理命令事件
- (id)targetForAction:(SEL)action withSender:(id)sender;
属性:
//公共的事件撤销管理者
@property(nonatomic,readonly) NSUndoManager *undoManager;
@end
默认情况下,程序的每一个window都有一个undo管理器,它是一个用于管理undo和redo操作的共享对象。然而,响应链上的任何对象的类都可以有自定义undo管理器。例如,UITextField的实例的自定义管理器在文件输入框放弃第一响应者状态时会被清理掉。当需要一个undo管理器时,请求会沿着响应链传递,然后UIWindow对象会返回一个可用的实例。
按键调节器枚举(快捷键)
typedef NS_OPTIONS(NSInteger, UIKeyModifierFlags) {
UIKeyModifierAlphaShift , //Alppha+Shift键
UIKeyModifierShift , //Shift键
UIKeyModifierControl , //Control键
UIKeyModifierAlternate , //Alt键
UIKeyModifierCommand , //Command键
UIKeyModifierNumericPad , //Num键
} ;
按键命令类:
@interface UIKeyCommand : NSObject <NSCopying, NSSecureCoding>
属性:
//输入字符串
@property (nonatomic,readonly) NSString *input;
//按键调节器
@property (nonatomic,readonly) UIKeyModifierFlags modifierFlags;
方法:
//按指定调节器键输入字符串并设置事件
+ (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags) modifierFlags action:(SEL)action;
@end
响应者类的按键命令类类目:
@interface UIResponder (UIResponderKeyCommands)
//组合快捷键命令(装有多个按键的数组)
@property (nonatomic,readonly) NSArray *keyCommands;
@end
NSObject类的标准编辑事件响应类类目
@interface NSObject(UIResponderStandardEditActions)
//剪贴
- (void)cut:(id)sender ;
//复制
- (void)copy:(id)sender ;
//粘贴
- (void)paste:(id)sender ;
//选择
- (void)select:(id)sender ;
//选择全部
- (void)selectAll:(id)sender ;
//删除
- (void)delete:(id)sender;
//从左到右写入字符串(居左)
- (void)makeTextWritingDirectionLeftToRight:(id)sender ;
//从右到左写入字符串(居右)
- (void)makeTextWritingDirectionRightToLeft:(id)sender ;
//切换字体为黑体(粗体)
- (void)toggleBoldface:(id)sender;
//切换字体为斜体
- (void)toggleItalics:(id)sender ;
//给文字添加下划线
- (void)toggleUnderline:(id)sender ;
//增加字体大小
- (void)increaseSize:(id)sender ;
//减小字体大小
- (void)decreaseSize:(id)sender;
@end
响应者类的类目:
@interface UIResponder (UIResponderInputViewAdditions)
属性:
//键盘输入视图(系统默认的,可以自定义)
@property (nonatomic, readonly, retain) UIView *inputView;
//弹出键盘时附带的视图
@property (nonatomic, readonly, retain) UIView *inputAccessoryView;
//键盘输入视图控制器
@property (nonatomic, readonly, retain) UIInputViewController *inputViewController ;
//弹出键盘时附带的视图的视图控制器
@property (nonatomic, readonly, retain) UIInputViewController *inputAccessoryViewController;
//文本输入模式
@property (nonatomic, readonly, retain) UITextInputMode *textInputMode ;
//文本输入模式标识
@property (nonatomic, readonly, retain) NSString *textInputContextIdentifier;
方法:
//根据设置的标识清除指定的文本输入模式
+ (void)clearTextInputContextIdentifier:(NSString *)identifier;
//重新刷新键盘输入视图
- (void)reloadInputViews;
@end
注意:
UITextFields和UITextView有一个inputAccessoryView的属性,当你想在键盘上展示一个自定义的view时,你就可以设置该属性。你设置的view就会自动和键盘keyboard一起显示了。
需要注意的是,你所自定义的view既不应该处在其他的视图层里,也不应该成为其他视图的子视图。其实也就是说,你所自定义的view只需要赋给属性inputAccessoryView就可以了,不要再做其他多余的操作。
我们在使用UITextView和UITextField的时候,可以通过它们的inputAccessoryView属性给输入时呼出的键盘加一个附属视图,通常是UIToolBar,用于回收键盘。
inputView就是显示键盘的view,如果重写这个view则不再弹出键盘,而是弹出自己的view.如果想实现当某一控件变为第一响应者时不弹出键盘而是弹出我们自定义的界面,那么我们就可以通过修改这个inputView来实现,比如弹出一个日期拾取器。
inputView不会随着键盘出现而出现,设置了InputView只会当UITextField或者UITextView变为第一相应者时显示出来,不会显示键盘了。设置了InputAccessoryView,它会随着键盘一起出现并且会显示在键盘的顶端。InutAccessoryView默认为nil.
按键输入箭头指向
UIKIT_EXTERN NSString *const UIKeyInputUpArrow
UIKIT_EXTERN NSString *const UIKeyInputDownArrow
UIKIT_EXTERN NSString *const UIKeyInputLeftArrow
UIKIT_EXTERN NSString *const UIKeyInputRightArrow
UIKIT_EXTERN NSString *const UIKeyInputEscape
响应者类的类目:
@interface UIResponder (ActivityContinuation)
//用户活动
@property (nonatomic, retain) NSUserActivity *userActivity ;
//更新用户活动
- (void)updateUserActivityState:(NSUserActivity *)activity;
//恢复用户活动
- (void)restoreUserActivityState:(NSUserActivity *)activity;
@end
注意:
支持User Activities
从iOS 8起,苹果为我们提供了一个非常棒的功能,即Handoff。使用这一功能,我们可以在一部iOS设备的某个应用上开始做一件事,然后在另一台iOS设备上继续做这件事。Handoff的基本思想是用户在一个应用里所做的任何操作都可以看作是一个Activity,一个Activity可以和一个特定iCloud用户的多台设备关联起来。在编写一个支持Handoff的应用时,会有以下三个交互事件:
- 为将在另一台设备上继续做的事创建一个新的User Activity;
- 当需要时,用新的数据更新已有的User Activity;
- 把一个User Activity传递到另一台设备上。
为了支持这些交互事件,在iOS 8后,UIResponder类新增了几个方法,我们在此不讨论这几个方法的实际使用,想了解更多的话,可以参考 iOS 8 Handoff 开发指南 。我们在此只是简单描述一下这几个方法。
在UIResponder中,已经为我们提供了一个userActivity属性,它是一个NSUserActivity对象。因此我们在UIResponder的子类中不需要再去声明一个userActivity属性,直接使用它就行。其声明如下:
@property(nonatomic, retain) NSUserActivity *userActivity
由UIKit管理的User Activities会在适当的时间自动保存。一般情况下,我们可以重写UIResponder类的updateUserActivityState:方法来延迟添加表示User Activity的状态数据。当我们不再需要一个User Activity时,我们可以设置userActivity属性为nil。任何由UIKit管理的NSUserActivity对象,如果它没有相关的响应者,则会自动失效。
另外,多个响应者可以共享一个NSUserActivity实例。
上面提到的updateUserActivityState:是用于更新给定的User Activity的状态。其定义如下:
- (void)updateUserActivityState:(NSUserActivity *)activity
子类可以重写这个方法来按照我们的需要更新给定的User Activity。我们需要使用NSUserActivity对象的addUserInfoEntriesFromDictionary:方法来添加表示用户Activity的状态。
在我们修改了User Activity的状态后,如果想将其恢复到某个状态,则可以使用以下方法:
- (void)restoreUserActivityState:(NSUserActivity *)activity
子类可以重写这个方法来使用给定User Activity的恢复响应者的状态。系统会在接收到数据时,将数据传递给application:continueUserActivity:restorationHandler:以做处理。我们重写时应该使用存储在user activity的userInfo字典中的状态数据来恢复对象。当然,我们也可以直接调用这个方法。
UIView类中一些用于触摸事件和手势的常用方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
注释:
1.返回nil,表明对任何事件不做响应
2.返回self,对任何事件都响应
3.返回一个特定的view,将事件传递给响应者
//判断触摸点point在那个区域中
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
注释:
判断点是否被当前视图处理
1.返回YES:所有的点全处理
2.返回NO:所有的点全部不处理
//将当前点或当前矩形区域的坐标转换到指定视图的坐标系
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
- (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;
- (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;
举例验证事件处理的传递链如下:
1.在故事板的控制器视图View中拖入三个子控件,它们处于同一层级,顺序依次分别为按钮button和两个视图view1、view2,设置不同的背景颜色,绿色的为view1、深红色的为view2、紫色的为button。
此时,对触摸事件的处理传递链依次分别是:
view2-->button-->view-->ViewControler-->UIWindow-->Application-->丢弃
view1-->view-->ViewControler-->UIWindow-->Application-->丢弃
view-->ViewController-->UIWindow-->Application-->丢弃
2.为view1和view2分别创建两个类myView1和myView2,并将它们的控件与类对应,同时给button在控制器类中关联事件。
(view1) (view2)
(button)
3.在ViewController.m文件中添加按钮事件的代码以重写触摸开始事件方法
//按钮事件处理
- (IBAction)buttonClicked:(UIButton *)sender { NSLog(@"点击了按钮"); }
//触摸开始事件处理
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"点击了白色的视图"); }
4.在子视图view1对应的类myView1.m文件中重写触摸开始事件处理,同时重写PointInside:withEvent:方法设置view1视图对触摸点事件是否处理。
//触摸开始事件处理
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"点击了绿色的视图"); }
//设置view1是否处理该触摸点事件
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return NO; }
解释:判断点是否被当前视图处理
1.返回YES:所有的点全处理
2.返回NO:所有的点全部不处理
5、在子视图view2对应的类myView2.m文件中重写触摸开始事件处理,同时重写hitTest:withEvent:方法设置view2视图对触摸点事件处理的传递链。
//触摸开始事件处理
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"点击了红色视图"); }
//设置view2视图对触摸点事件处理的传递链
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //查找button UIButton *btn; for (UIView *view in self.superview.subviews) { if (view.tag == 1) { btn = (UIButton *)view; break; } } //判断触摸点point在那个区域中 //1.将当前点的坐标转换为按钮btn的坐标系 CGPoint btnPoint = [self convertPoint:point toView:btn]; //2.再判断 if ([btn pointInside:btnPoint withEvent:event]) { return btn; } else if ([self pointInside:point withEvent:event]) { return self; } //正常的情况下 //return self.superview; return [super hitTest:point withEvent:event]; }
解释:事件响应的测试
1.返回nil,表明对任何事件不做响应
2.返回self,对任何事件都响应
3.返回一个特定的view,将事件传递给响应者
6.演示结果如下:
<1>点击view2红色区域(不与紫色button局域重合的地方)时,view2视图对触摸点事件进行了处理
2015-10-09 20:02:49.283 04-responser[5397:279978] 点击了红色视图
<2>点击button紫色局域(包括重合局域)时,button按钮对触摸点事件都进行了处理。原因:点击button与view2没有重合的按钮局域时,button重写了TouchesBegan: withEvent:方法,它会对此局域的触摸事件做处理;点击button与view2重合的按钮局域时,本来view2视图在button的前面,view2应该先做处理的,但是由于view2类重写了hitTest:withEvent:方法,使得view2不对此局域的事件做处理,而是传递给了指定的视图button做处理。
2015-10-09 20:13:48.385 04-responser[5397:279978] 点击了按钮
<3>点击view1绿色局域和其他的白色局域时,都是当前控制器的视图view做了处理。原因:虽然view1的类重写了TouchesBegan: withEvent:方法用来处理触摸事件,但是又由于它有重写了pointInside:withEvent:方法并且返回值为NO,使自己不再对自己局域的任何触摸点事件做处理,而是把此事件直接向后传递给了它的父类View进行了处理。同时,控制器类的视图空白局域事件也是由View进行处理的,所以输出结果如下所示。
2015-10-09 20:15:35.647 04-responser[5397:279978] 点击了白色的视图