zoukankan      html  css  js  c++  java
  • 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 远程控制方法,一般用于耳机线控

    耳机线控要注意三点要素:

    1. 启动接受远程事件:[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    2. 设置成为第一响应者(UIViewController,AppDelegate中不需要设置)
    3. 获取音频的控制权
    //  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添加长按菜单需要注意几点:

    1. 启用用户交互:self.userInteractionEnabled = YES;
    2. 在显示菜单之前设置对象成为第一响应者(UIViewController,AppDelegate中不需要设置)
    3. 返回菜单需要显示的按钮,并重写实现对应方法
    4. 注册长按手势,显示菜单
    //  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实现画板撤销/重做功能

    实现撤销/重做注意以下几点:

    1. 在调用方法时需要添加注册一个对应的撤销方法
    2. 撤销/ 重做只需要调用undoManager中的相应方法即可
    3. 如果需要多个动作一起撤销则需要标记分组
      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 自定义快捷键

    自定义快捷键需要注意两点:

    1. 设置对象成为第一响应者(UIViewController,AppDelegate中不需要设置)
    2. 重写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

      

  • 相关阅读:
    Node.js:事件循环
    Node.js:回调函数
    Node.js:REPL(交互式解释器)
    Node.js:NPM 使用介绍
    Node.js:创建第一个应用
    Node.js:安装配置
    Node.js:教程
    Node.js:目录
    Node.js:template
    虚拟化之xenserver
  • 原文地址:https://www.cnblogs.com/mukekeheart/p/8462314.html
Copyright © 2011-2022 走看看