iOS学习——(转)UIResponder详解
本文转载自:ios开发 之 UIResponder详解
我们知道UIResponder是所有视图View的基类,在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events)。我们知道UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,所以这些类都可以响应事件。当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。本文将详细介绍UIResponder类。
一 UIResponder.h文件注释版
首先,我们对UIResponder.h文件进行了研究和解释,并对各模块进行了注释添加,方便我们在阅读和学习时候的理解。在学习了UIView、NSObject的.h文件之后,我们发现这些基类的.h文件的组织架构基本类似,最初是定义该类中需要用到的一些枚举类型数据,然后对相应的协议进行定义,接着就是对本类进行定义,一些基本的属性和方法的定义,最后就是对本类做各种功能性的分类。
1 // 2 // UIResponder.h 3 4 #import <Foundation/Foundation.h> 5 #import <UIKit/UIKitDefines.h> 6 #import <UIKit/UIEvent.h> 7 8 NS_ASSUME_NONNULL_BEGIN 9 10 @class UIPress; 11 @class UIPressesEvent; 12 13 #pragma mark - UIResponderStandardEditActions协议定义 14 15 @protocol UIResponderStandardEditActions <NSObject> 16 @optional 17 /** 剪切事件 */ 18 - (void)cut:(nullable id)sender NS_AVAILABLE_IOS(3_0); 19 /** 复制事件 */ 20 - (void)copy:(nullable id)sender NS_AVAILABLE_IOS(3_0); 21 /** 粘贴事件 */ 22 - (void)paste:(nullable id)sender NS_AVAILABLE_IOS(3_0); 23 /** 选择事件 */ 24 - (void)select:(nullable id)sender NS_AVAILABLE_IOS(3_0); 25 /** 全选事件 */ 26 - (void)selectAll:(nullable id)sender NS_AVAILABLE_IOS(3_0); 27 /** 删除事件 */ 28 - (void)delete:(nullable id)sender NS_AVAILABLE_IOS(3_2); 29 /** 从左到右写入字符串(居左) */ 30 - (void)makeTextWritingDirectionLeftToRight:(nullable id)sender NS_AVAILABLE_IOS(5_0); 31 /** 从右到左写入字符串(居右) */ 32 - (void)makeTextWritingDirectionRightToLeft:(nullable id)sender NS_AVAILABLE_IOS(5_0); 33 /** 切换字体为黑体(粗体) */ 34 - (void)toggleBoldface:(nullable id)sender NS_AVAILABLE_IOS(6_0); 35 /** 切换字体为斜体 */ 36 - (void)toggleItalics:(nullable id)sender NS_AVAILABLE_IOS(6_0); 37 /** 给文字添加下划线 */ 38 - (void)toggleUnderline:(nullable id)sender NS_AVAILABLE_IOS(6_0); 39 40 /** 增加字体大小 */ 41 - (void)increaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0); 42 /** 减小字体大小 */ 43 - (void)decreaseSize:(nullable id)sender NS_AVAILABLE_IOS(7_0); 44 45 @end 46 47 #pragma mark - UIResponder类定义 48 49 NS_CLASS_AVAILABLE_IOS(2_0) @interface UIResponder : NSObject <UIResponderStandardEditActions> 50 51 #pragma mark - 响应者相关方法 52 53 /** 获取下一个响应者 */ 54 #if UIKIT_DEFINE_AS_PROPERTIES 55 @property(nonatomic, readonly, nullable) UIResponder *nextResponder; 56 #else 57 - (nullable UIResponder *)nextResponder; 58 #endif 59 60 /** 是否允许成为第一响应者。默认返回NO */ 61 #if UIKIT_DEFINE_AS_PROPERTIES 62 @property(nonatomic, readonly) BOOL canBecomeFirstResponder; 63 #else 64 - (BOOL)canBecomeFirstResponder; 65 #endif 66 /** 设置成为第一响应者 */ 67 - (BOOL)becomeFirstResponder; 68 69 /** 是否允许放弃第一响应者。默认返回YES */ 70 #if UIKIT_DEFINE_AS_PROPERTIES 71 @property(nonatomic, readonly) BOOL canResignFirstResponder; 72 #else 73 - (BOOL)canResignFirstResponder; 74 #endif 75 /** 设置放弃第一响应者 */ 76 - (BOOL)resignFirstResponder; 77 78 /** 判断对象是否是第一响应者 */ 79 #if UIKIT_DEFINE_AS_PROPERTIES 80 @property(nonatomic, readonly) BOOL isFirstResponder; 81 #else 82 - (BOOL)isFirstResponder; 83 #endif 84 85 #pragma mark - 触摸相关方法,一般用于响应屏幕触摸 86 /** 手指按下时响应 */ 87 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; 88 /** 手指移动时响应 */ 89 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; 90 /** 手指抬起时响应 */ 91 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; 92 /** 取消(意外中断, 如:电话, 系统警告窗等) */ 93 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; 94 /** 3DTouch响应(iOS9.1后使用) */ 95 - (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1); 96 97 #pragma mark - 深按相关方法,一般用于遥控器按键响应 98 /** 手指按压开始时响应 */ 99 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); 100 /** 手指按压位置移动时响应 */ 101 - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); 102 /** 手指抬起接受按压时响应 */ 103 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); 104 /** 按压取消(意外中断, 如:电话, 系统警告窗等) */ 105 - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); 106 107 #pragma mark - 加速相关方法,一般用于摇一摇、运动事件监听等 108 /** 开始加速 */ 109 - (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0); 110 /** 结束加速 */ 111 - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0); 112 /** 加速取消(意外中断, 如:电话, 系统警告窗等) */ 113 - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0); 114 115 /** 远程控制事件 */ 116 - (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0); 117 118 /** 返回UIMenuController需要显示的控件(如:复制,粘贴等) */ 119 - (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0); 120 121 /** 返回响应的操作目标对象 */ 122 - (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0); 123 124 /** 获取响应链就近共享撤消管理 */ 125 @property(nullable, nonatomic,readonly) NSUndoManager *undoManager NS_AVAILABLE_IOS(3_0); 126 127 @end 128 129 /** 快捷主键枚举 */ 130 typedef NS_OPTIONS(NSInteger, UIKeyModifierFlags) { 131 UIKeyModifierAlphaShift = 1 << 16, //!< Alpha+Shift键. 132 UIKeyModifierShift = 1 << 17, //!< Shift键. 133 UIKeyModifierControl = 1 << 18, //!< Control键. 134 UIKeyModifierAlternate = 1 << 19, //!< Alt键. 135 UIKeyModifierCommand = 1 << 20, //!< Command键. 136 UIKeyModifierNumericPad = 1 << 21, //!< Num键. 137 } NS_ENUM_AVAILABLE_IOS(7_0); 138 139 #pragma mark - 快捷键对象 140 141 NS_CLASS_AVAILABLE_IOS(7_0) @interface UIKeyCommand : NSObject <NSCopying, NSSecureCoding> 142 143 /** 初始化对象 */ 144 - (instancetype)init NS_DESIGNATED_INITIALIZER; 145 /** 初始化对象 */ 146 - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 147 148 /** 获取快捷辅键(如快捷命令【Command+A】中的 A 键) */ 149 @property (nonatomic,readonly) NSString *input; 150 /** 获取快捷主键(如快捷命令【Command+A】中的 Command 键) */ 151 @property (nonatomic,readonly) UIKeyModifierFlags modifierFlags; 152 /** 显示给用户的快捷键标题 */ 153 @property (nullable,nonatomic,copy) NSString *discoverabilityTitle NS_AVAILABLE_IOS(9_0); 154 155 /** 创建一个快捷键命令 */ 156 + (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action; 157 158 /** 创建一个快捷键命令 */ 159 + (UIKeyCommand *)keyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)modifierFlags action:(SEL)action discoverabilityTitle:(NSString *)discoverabilityTitle NS_AVAILABLE_IOS(9_0); 160 161 @end 162 163 #pragma mark - 响应快捷命令 164 165 @interface UIResponder (UIResponderKeyCommands) 166 /** 返回快捷键命令数组 */ 167 @property (nullable,nonatomic,readonly) NSArray<UIKeyCommand *> *keyCommands NS_AVAILABLE_IOS(7_0); 168 @end 169 170 @class UIInputViewController; 171 @class UITextInputMode; 172 @class UITextInputAssistantItem; 173 174 #pragma mark - 输入视图 175 176 @interface UIResponder (UIResponderInputViewAdditions) 177 178 /** 键盘输入视图(系统默认的,可以自定义) */ 179 @property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView NS_AVAILABLE_IOS(3_2); 180 /** 弹出键盘时附带的视图 */ 181 @property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2); 182 183 /** 输入助手配置键盘的快捷方式栏时使用 */ 184 @property (nonnull, nonatomic, readonly, strong) UITextInputAssistantItem *inputAssistantItem NS_AVAILABLE_IOS(9_0) __TVOS_PROHIBITED __WATCHOS_PROHIBITED; 185 186 /** 键盘输入视图控制器 */ 187 @property (nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController NS_AVAILABLE_IOS(8_0); 188 /** 弹出键盘时附带的视图的视图控制器 */ 189 @property (nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController NS_AVAILABLE_IOS(8_0); 190 191 /** 文本输入模式 */ 192 @property (nullable, nonatomic, readonly, strong) UITextInputMode *textInputMode NS_AVAILABLE_IOS(7_0); 193 194 /** 文本输入模式标识 */ 195 @property (nullable, nonatomic, readonly, strong) NSString *textInputContextIdentifier NS_AVAILABLE_IOS(7_0); 196 /** 根据设置的标识清除指定的文本输入模式 */ 197 + (void)clearTextInputContextIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(7_0); 198 199 /** 重新刷新键盘输入视图 */ 200 - (void)reloadInputViews NS_AVAILABLE_IOS(3_2); 201 202 @end 203 204 /** 特殊快捷辅键定义 */ 205 UIKIT_EXTERN NSString *const UIKeyInputUpArrow NS_AVAILABLE_IOS(7_0); //!< 上按键. 206 UIKIT_EXTERN NSString *const UIKeyInputDownArrow NS_AVAILABLE_IOS(7_0); //!< 下按键. 207 UIKIT_EXTERN NSString *const UIKeyInputLeftArrow NS_AVAILABLE_IOS(7_0); //!< 左按键. 208 UIKIT_EXTERN NSString *const UIKeyInputRightArrow NS_AVAILABLE_IOS(7_0); //!< 右按键 209 UIKIT_EXTERN NSString *const UIKeyInputEscape NS_AVAILABLE_IOS(7_0); //!< Esc按键. 210 211 #pragma mark - 响应者活动 212 213 @interface UIResponder (ActivityContinuation) 214 /** 用户活动 */ 215 @property (nullable, nonatomic, strong) NSUserActivity *userActivity NS_AVAILABLE_IOS(8_0); 216 /** 更新用户活动 */ 217 - (void)updateUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0); 218 /** 恢复用户活动 */ 219 - (void)restoreUserActivityState:(NSUserActivity *)activity NS_AVAILABLE_IOS(8_0); 220 @end 221 222 NS_ASSUME_NONNULL_END
二 UIResponder的使用
2.1 通过响应者链查找视图的视图控制器
通过响应链查找视图控制器,nextResponder获取下一个响应者,响应者顺序为:
/** * 查找视图的视图控制器 * * @param view 视图 * * @return 返回视图的控制器 */ - (UIViewController *)getControllerFromView:(UIView *)view { // 遍历响应者链。返回第一个找到视图控制器 UIResponder *responder = view; while ((responder = [responder nextResponder])){ if ([responder isKindOfClass: [UIViewController class]]){ return (UIViewController *)responder; } } // 如果没有找到则返回nil return nil; }
2.2 设置与取消第一响应者
UIView默认不允许设置为第一响应者,因此设置UIView为第一响应者需要重写canBecomeFirstResponder方法并返回YES。 设置为第一响应者后,对象则可以接受远程控制事件进行处理(如耳机线控)。UITextField、UITextView成为第一响应者后会弹出输入键盘,取消第一响应者则会隐藏输入键盘。
1 // ZMFirstResponderView.m 2 3 #import "ZMFirstResponderView.h" 4 5 @implementation ZMFirstResponderView 6 7 /** 演示设置为第一响应者 */ 8 - (void)setBecomeFirstResponder { 9 // 判断对象是否已经是第一响应者 10 if ([self isFirstResponder]) { 11 return; 12 } 13 // 判断对象是否允许成为第一响应者 14 if ([self canBecomeFirstResponder]) { 15 // 设置成为第一响应者 16 [self becomeFirstResponder]; 17 } 18 } 19 20 /** 演示放弃第一响应者 */ 21 - (void)setResignFirstResponder { 22 // 判断对象是否不是第一响应者 23 if (![self isFirstResponder]) { 24 return; 25 } 26 // 判断对象是否允许放弃第一响应者 27 if ([self canResignFirstResponder]) { 28 // 设置放弃第一响应者 29 [self resignFirstResponder]; 30 } 31 } 32 33 /** 重写方法,允许对象成为第一响应者 */ 34 - (BOOL)canBecomeFirstResponder { 35 return YES; 36 } 37 38 @end
2.3 触摸相关方法,一般用于响应屏幕触摸
/** 手指按下时响应 */ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [super touchesBegan:touches withEvent:event]; NSLog(@"--->手指按下时响应"); } /** 手指移动时响应 */ - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [super touchesMoved:touches withEvent:event]; NSLog(@"--->手指移动时响应"); } /** 手指抬起时响应 */ - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [super touchesEnded:touches withEvent:event]; NSLog(@"--->手指抬起时响应"); } /** 触摸取消(意外中断, 如:电话, Home键退出等) */ - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [super touchesCancelled:touches withEvent:event]; NSLog(@"--->取消触摸响应"); }
2.4 加速相关方法,一般用于摇一摇、运动事件监听等
/** 开始加速 */ - (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) { [super motionBegan:motion withEvent:event]; NSLog(@"--->开始加速"); } /** 结束加速 */ - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) { [super motionEnded:motion withEvent:event]; NSLog(@"--->结束加速"); } /** 加速取消(意外中断, 如:电话, Home键退出等) */ - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0) { [super motionCancelled:motion withEvent:event]; NSLog(@"--->加速取消"); }
2.5 远程控制方法,一般用于耳机线控
耳机线控要注意三点要素:
- 启动接受远程事件:[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
- 设置成为第一响应者(UIViewController,AppDelegate中不需要设置)
- 获取音频的控制权
// ZMAudioView.m #import "ZMAudioView.h" #import <AVFoundation/AVFoundation.h> @implementation ZMAudioView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 1 启动接受远程事件 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; //2 设置成为第一响应者 [self becomeFirstResponder]; // 3 播放一段静音文件,使APP获取音频的控制权 NSURL *audioURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"mute_60s" ofType:@"mp3"]]; AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:nil]; [audioPlayer play]; } return self; } /** 允许对象成为第一响应者 */ - (BOOL)canBecomeFirstResponder { return YES; } /** 远程控制事件响应 */ - (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent { NSLog(@"--->耳机线控响应"); } - (void)dealloc { // 停止接受远程事件 [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; // 放弃第一响应者 [self resignFirstResponder]; } @end
2.6 在UILabel中实现长按菜单(复制、粘贴等)
为UILabel添加长按菜单需要注意几点:
- 启用用户交互:self.userInteractionEnabled = YES;
- 在显示菜单之前设置对象成为第一响应者(UIViewController,AppDelegate中不需要设置)
- 返回菜单需要显示的按钮,并重写实现对应方法
- 注册长按手势,显示菜单
// ZMMenuLabel.m #import "ZMMenuLabel.h" @implementation ZMMenuLabel - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 启用用户交互 self.userInteractionEnabled = YES; // 添加长按手势 UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMenu:)]; longPressGesture.minimumPressDuration = 0.2; [self addGestureRecognizer:longPressGesture]; } return self; } /** 允许对象成为第一响应者 */ - (BOOL)canBecomeFirstResponder { return YES; } /** 长按响应 */ - (void)longPressMenu:(UILongPressGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan) { // 设置成为第一响应者 [self becomeFirstResponder]; // 显示菜单 UIMenuController *menuCtrl = [UIMenuController sharedMenuController]; [menuCtrl setTargetRect:self.frame inView:self.superview]; [menuCtrl setMenuVisible:YES animated:YES]; } } /** 返回需要显示的菜单按钮 */ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // 只显示复制、粘贴按钮 if (action == @selector(copy:) || action == @selector(paste:)) { return YES; } return NO; } /** 实现复制方法 */ - (void)copy:(id)sender { UIPasteboard *paste = [UIPasteboard generalPasteboard]; paste.string = self.text; } /** 实现粘贴方法 */ - (void)paste:(id)sender { UIPasteboard *paste = [UIPasteboard generalPasteboard]; self.text = paste.string; } @end
2.7 使用NSUndoManager实现画板撤销/重做功能
实现撤销/重做注意以下几点:
- 在调用方法时需要添加注册一个对应的撤销方法
- 撤销/ 重做只需要调用undoManager中的相应方法即可
- 如果需要多个动作一起撤销则需要标记分组
1 /** ==============ZMDrawingBoardView.h文件=================== */ 2 3 #import <UIKit/UIKit.h> 4 5 /** 画板View */ 6 @interface ZMDrawingBoardView : UIView 7 8 @end 9 10 11 /** 划线Model */ 12 @interface ZMLineModel : NSObject 13 14 @property (nonatomic) CGPoint begin; 15 @property (nonatomic) CGPoint end; 16 17 @end 18 19 20 /** ==============ZMDrawingBoardView.m文件=================== */ 21 22 #import "ZMDrawingBoardView.h" 23 24 /** 画板View */ 25 @interface ZMDrawingBoardView () 26 27 @property (nonatomic, strong) ZMLineModel *currentLine; 28 @property (nonatomic, strong) NSMutableArray<ZMLineModel *> *toucheArray; 29 30 @end 31 32 @implementation ZMDrawingBoardView 33 34 35 - (instancetype)initWithFrame:(CGRect)frame 36 { 37 self = [super initWithFrame:frame]; 38 if (self) { 39 [self initSubView]; 40 self.backgroundColor = [UIColor whiteColor]; 41 self.toucheArray = [NSMutableArray array]; 42 } 43 return self; 44 } 45 46 /** 绘制画板 */ 47 - (void)drawRect:(CGRect)rect { 48 // 获得上下文 49 CGContextRef context = UIGraphicsGetCurrentContext(); 50 // 设置样式 51 CGContextSetLineCap(context, kCGLineCapSquare); 52 // 设置宽度 53 CGContextSetLineWidth(context, 5.0); 54 // 设置颜色 55 CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]); 56 57 for (ZMLineModel *line in self.toucheArray) { 58 // 开始绘制 59 CGContextBeginPath(context); 60 // 移动画笔到起点 61 CGContextMoveToPoint(context, line.begin.x, line.begin.y); 62 // 添加下一点 63 CGContextAddLineToPoint(context, line.end.x, line.end.y); 64 // 绘制完成 65 CGContextStrokePath(context); 66 } 67 } 68 69 /** 划线开始 */ 70 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 71 { 72 // 标记开始撤销分组 73 [self.undoManager beginUndoGrouping]; 74 75 for (UITouch *touch in touches) { 76 // 记录起始点 77 CGPoint locTouch = [touch locationInView:self]; 78 _currentLine = [[ZMLineModel alloc] init]; 79 _currentLine.begin = locTouch; 80 _currentLine.end = locTouch; 81 } 82 83 } 84 85 /** 划线移动 */ 86 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 87 { 88 for (UITouch *touch in touches) { 89 // 添加线条 90 CGPoint locTouch = [touch locationInView:self]; 91 _currentLine.end = locTouch; 92 [self addLine:_currentLine]; 93 // 当前线条 94 _currentLine = [[ZMLineModel alloc] init]; 95 _currentLine.begin = locTouch; 96 _currentLine.end = locTouch; 97 } 98 } 99 100 /** 划线结束 */ 101 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 102 { 103 // 结束标记撤销分组 104 [self.undoManager endUndoGrouping]; 105 } 106 107 /** 添加划线 */ 108 - (void)addLine:(ZMLineModel *)line 109 { 110 // 添加划线并重绘画板 111 [self.toucheArray addObject:line]; 112 [self setNeedsDisplay]; 113 // 注册撤销方法 114 [[self.undoManager prepareWithInvocationTarget:self] removeLine:line]; 115 } 116 117 /** 移除划线 */ 118 - (void)removeLine:(ZMLineModel *)line 119 { 120 if ([self.toucheArray containsObject:line]) { 121 // 移除划线并重绘画板 122 [self.toucheArray removeObject:line]; 123 [self setNeedsDisplay]; 124 // 注册撤销方法 125 [[self.undoManager prepareWithInvocationTarget:self] addLine:line]; 126 } 127 } 128 129 /** 撤销按钮点击响应 */ 130 - (void)undoButtonAction:(id)sender { 131 if ([self.undoManager canUndo]) { 132 [self.undoManager undo]; 133 } 134 } 135 136 /** 重做按钮点击响应 */ 137 - (void)redoButtonAction:(id)sender { 138 if ([self.undoManager canRedo]) { 139 [self.undoManager redo]; 140 } 141 } 142 143 /** 初始化子控件 */ 144 - (void)initSubView { 145 // 撤销按钮 146 UIButton *undoButton = [UIButton buttonWithType:UIButtonTypeSystem]; 147 undoButton.frame = CGRectMake(0, 64, 70, 50); 148 [undoButton setTitle:@"undo撤销" forState:UIControlStateNormal]; 149 [undoButton sizeToFit]; 150 [undoButton addTarget:self action:@selector(undoButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 151 [self addSubview:undoButton]; 152 // 重做按钮 153 UIButton *redoButton = [UIButton buttonWithType:UIButtonTypeSystem]; 154 redoButton.frame = CGRectMake(CGRectGetWidth(self.frame)-70, 64, 70, 50); 155 [redoButton setTitle:@"redo重做" forState:UIControlStateNormal]; 156 [redoButton sizeToFit]; 157 [redoButton addTarget:self action:@selector(redoButtonAction:) forControlEvents:UIControlEventTouchUpInside]; 158 [self addSubview:redoButton]; 159 } 160 161 @end
2.8 自定义快捷键
自定义快捷键需要注意两点:
- 设置对象成为第一响应者(UIViewController,AppDelegate中不需要设置)
- 重写keyCommands返回快捷命令组合
// ZMKeyCommandView.m #import "ZMKeyCommandView.h" @implementation ZMKeyCommandView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 设置成为第一响应者 [self becomeFirstResponder]; } return self; } /** 允许对象成为第一响应者 */ - (BOOL)canBecomeFirstResponder { return YES; } /** 返回快捷命令数组 */ -(NSArray<UIKeyCommand *> *)keyCommands { return @[ [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndEscapeKey:) discoverabilityTitle:@"自定义[Shift+Esc]快捷键"], [UIKeyCommand keyCommandWithInput:@"a" modifierFlags:UIKeyModifierShift action:@selector(pressedShiftAndAKey:) discoverabilityTitle:@"自定义[Shift+A]快捷键"] ]; } /** Shift+Esc快捷命令响应 */ -(void)pressedShiftAndEscapeKey:(UIKeyCommand *)keyCommand { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:keyCommand.discoverabilityTitle message:[NSString stringWithFormat:@"按下快捷辅键:[%@]", keyCommand.input] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil]; [alertView show]; } /** Shift+A快捷命令响应 */ -(void)pressedShiftAndAKey:(UIKeyCommand *)keyCommand { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:keyCommand.discoverabilityTitle message:[NSString stringWithFormat:@"按下快捷辅键:[%@]", keyCommand.input] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil]; [alertView show]; } @end
2.9 自定义UITextField输入键盘
1 // ZMCustomInputView.m 2 3 #import "ZMCustomInputView.h" 4 5 #define MAIN_SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width //!< 屏幕的Width 6 7 @interface ZMCustomInputView () 8 9 @property (nonatomic, strong) UITextField *textField; 10 @property (nonatomic, strong) UIView *customInputView; 11 @property (nonatomic, strong) UIToolbar *customAccessoryView; 12 13 @end 14 15 @implementation ZMCustomInputView 16 17 - (instancetype)initWithFrame:(CGRect)frame 18 { 19 self = [super initWithFrame:frame]; 20 if (self) { 21 // 添加TextField 22 [self addSubview:self.textField]; 23 } 24 return self; 25 } 26 27 /** 懒加载textField */ 28 - (UITextField *)textField { 29 if (!_textField) { 30 // 初始化textField 31 _textField = [[UITextField alloc]initWithFrame:CGRectMake(50, 100, MAIN_SCREEN_WIDTH - 100, 30)]; 32 _textField.borderStyle = UITextBorderStyleRoundedRect; 33 _textField.placeholder = @"测试"; 34 // 设置自定义键盘View 35 _textField.inputView = self.customInputView; 36 _textField.inputAccessoryView = self.customAccessoryView; 37 } 38 return _textField; 39 } 40 41 /** 懒加载customInputView */ 42 - (UIView *)customInputView { 43 if (!_customInputView) { 44 _customInputView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, MAIN_SCREEN_WIDTH, 220)]; 45 _customInputView.backgroundColor = [UIColor lightGrayColor]; 46 UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 100, MAIN_SCREEN_WIDTH, 40)]; 47 label.textAlignment = NSTextAlignmentCenter; 48 label.text = @"自定义inputView"; 49 [_customInputView addSubview:label]; 50 } 51 return _customInputView; 52 } 53 54 /** 懒加载customAccessoryView */ 55 - (UIToolbar *)customAccessoryView { 56 if (!_customAccessoryView) { 57 _customAccessoryView = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, MAIN_SCREEN_WIDTH, 40)]; 58 _customAccessoryView.barTintColor = [UIColor orangeColor]; 59 UIBarButtonItem *space = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 60 UIBarButtonItem *done = [[UIBarButtonItem alloc]initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(done)]; 61 [_customAccessoryView setItems:@[space, space, done]]; 62 } 63 return _customAccessoryView; 64 } 65 66 /** 响应完成按钮 */ 67 - (void)done { 68 [self.textField resignFirstResponder]; 69 } 70 71 72 @end