Quartz 2D 手势锁绘制

1、绘制手势锁

  • 具体实现代码见 GitHub 源码 QExtension

  • QTouchLockView.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @interface QTouchLockView : UIView

    /// 提示信息框
    @property (nonatomic, strong) UILabel *alertLabel;

    /**
    * 创建手势锁视图控件,获取滑动手势结果
    *
    * @param frame 手势锁视图控件的位置尺寸
    * @param result 滑动手势结果,YES 成功,NO 失败
    *
    * @return 手势锁视图控件
    */
    + (instancetype)q_touchLockViewWithFrame:(CGRect)frame
    pathResult:(void (^)(BOOL isSucceed, NSString *result))result;

    @end
  • QTouchLockView.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    #import "NSString+Hash.h"

    @interface QTouchLockView ()

    /// 存放选中的按钮
    @property (nonatomic, strong) NSMutableArray *selectedArray;

    /// 当前被选中的按钮
    @property (nonatomic, assign) CGPoint currentPoint;

    /// 滑动手势结果
    @property (nonatomic, copy) void (^resultBlock)(BOOL, NSString *);

    @end

    @implementation QTouchLockView

    /// 创建手势锁界面,获取滑动结果
    + (instancetype)q_touchLockViewWithFrame:(CGRect)frame
    pathResult:(void (^)(BOOL isSucceed, NSString *result))result {

    QTouchLockView *touchLockView = [[self alloc] init];

    CGRect tmpFrame = frame;
    tmpFrame.size.height = frame.size.width;

    touchLockView.frame = tmpFrame;
    touchLockView.resultBlock = result;

    return touchLockView;
    }

    /// 初始化界面
    - (instancetype)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {

    self.backgroundColor = [UIColor whiteColor];

    // 添加提示信息框
    self.alertLabel = [[UILabel alloc] init];
    self.alertLabel.textAlignment = NSTextAlignmentCenter;
    self.alertLabel.textColor = [UIColor redColor];
    self.alertLabel.backgroundColor = [UIColor clearColor];
    self.alertLabel.numberOfLines = 1;
    self.alertLabel.adjustsFontSizeToFitWidth = YES;
    [self addSubview:self.alertLabel];

    // 添加按钮
    for (int i = 0; i < 9; i++){

    UIButton *btn = [[UIButton alloc] init];

    NSString *bundlePath = [[[NSBundle mainBundle] resourcePath]
    stringByAppendingPathComponent:@"QTouchLockView.bundle"];

    UIImage *normalImage = [UIImage imageWithContentsOfFile:
    [bundlePath stringByAppendingPathComponent:@"gesture_node_normal"]];
    UIImage *selectedImage = [UIImage imageWithContentsOfFile:
    [bundlePath stringByAppendingPathComponent:@"gesture_node_selected"]];
    UIImage *highlightedImage = [UIImage imageWithContentsOfFile:
    [bundlePath stringByAppendingPathComponent:@"gesture_node_highlighted"]];

    [btn setBackgroundImage:normalImage forState:UIControlStateNormal];
    [btn setBackgroundImage:selectedImage forState:UIControlStateSelected];
    [btn setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];

    // 设置 tag 值,设置按钮对应的密码值
    btn.tag = i + 1;

    // 关闭按钮的交互,响应触摸事件
    btn.userInteractionEnabled = NO;

    [self addSubview:btn];
    }
    }
    return self;
    }

    /// 布局控件
    - (void)layoutSubviews {
    [super layoutSubviews];

    // 设置按钮的 frame
    for (int i = 0; i < self.subviews.count - 1; i++) {

    // 列数
    NSInteger cols = 3;

    // 设置按钮尺寸
    CGFloat W = self.bounds.size.width;
    CGFloat H = self.bounds.size.height;
    CGFloat btnW = W / 5;
    CGFloat btnH = H / 5;

    // 计算按钮的 x 坐标值
    NSUInteger col = i % cols;
    CGFloat btnX = col * btnW * 2;

    // 计算按钮的 y 坐标值
    NSUInteger row = i / cols;
    CGFloat btnY = row * btnH * 2;

    // 设置按钮的 frame
    UIButton *btn = self.subviews[i + 1];
    btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
    }

    // 设置提示信息框的 frame
    self.alertLabel.frame = CGRectMake(0, -50, self.bounds.size.width, 30);
    }

    /// 触摸开始
    - (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸起始点位置
    CGPoint startPoint = [touches.anyObject locationInView:self];

    // 获取其 button
    UIButton *button = nil;
    for (UIButton *btn in self.subviews) {

    // 设置触摸按钮的灵敏度
    CGRect frame = btn.frame;
    CGRect tmpFrame = CGRectMake(frame.origin.x + frame.size.width / 4,
    frame.origin.y + frame.size.height / 4,
    frame.size.width / 2,
    frame.size.height / 2);

    // 判断某点在不在其 frame 上
    if (CGRectContainsPoint(tmpFrame, startPoint)) {
    button = btn;
    }
    }

    // 选中此 button
    if (button && button.selected == NO) {
    button.selected = YES;
    [self.selectedArray addObject:button];
    }
    }

    /// 触摸移动
    - (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸点位置
    CGPoint touchPoint = [touches.anyObject locationInView:self];

    // 获取其 button
    UIButton *button = nil;
    for (UIButton *btn in self.subviews) {

    // 设置触摸按钮的灵敏度
    CGRect frame = btn.frame;
    CGRect tmpFrame = CGRectMake(frame.origin.x + frame.size.width / 4,
    frame.origin.y + frame.size.height / 4,
    frame.size.width / 2,
    frame.size.height / 2);

    // 判断某点在不在其 frame 上
    if (CGRectContainsPoint(tmpFrame, touchPoint)) {
    button = btn;
    }
    }

    // 选中此 button
    if (button && button.selected == NO) {
    button.selected = YES;
    [self.selectedArray addObject:button];
    } else {
    self.currentPoint = touchPoint;
    }

    // 刷新视图
    [self setNeedsDisplay];
    }

    /// 触摸结束
    - (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event {

    if (self.selectedArray.count < 4) {

    // 触摸点数过少
    if (self.resultBlock) {
    self.resultBlock(NO, @"请至少连续连接四个点");
    }

    for (UIButton *btn in self.selectedArray) {

    btn.highlighted = YES;
    btn.selected = NO;
    }

    // 延迟
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
    (int64_t)(0.5 * NSEC_PER_SEC)),
    dispatch_get_main_queue(), ^{

    [self clearPath];
    });

    } else {

    // 触摸完成
    if (self.resultBlock) {

    // 获取触摸结果
    NSMutableString *path = [NSMutableString string];
    for (UIButton *btn in self.selectedArray) {
    [path appendFormat:@"%ld", btn.tag];
    }

    // 对滑动获取的密码值进行 MD5 加密
    NSString *md5Path = [path q_md5String];

    self.resultBlock(YES, md5Path);
    }

    [self clearPath];
    }
    }

    /// 触摸取消
    - (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event {

    [self touchesEnded:touches withEvent:event];
    }

    /// 绘制贝塞尔连接线
    - (void)drawRect:(CGRect)rect {

    if (self.selectedArray.count == 0) {
    return;
    }

    UIBezierPath *path = [UIBezierPath bezierPath];
    path.lineWidth = 5;
    path.lineJoinStyle = kCGLineCapRound;
    [[UIColor colorWithRed:1 green:0 blue:0 alpha:0.5] set];

    for (int i = 0; i < self.selectedArray.count; i++) {

    UIButton *btn = self.selectedArray[i];

    // 如果是第一个按钮,则将其曲线的起点放在其按钮上,否则则进行连线
    if (i == 0) {
    [path moveToPoint:btn.center];
    } else {
    [path addLineToPoint:btn.center];
    }
    }

    // 如果不满足上述条件,按钮不存在则连接到临时点
    [path addLineToPoint:self.currentPoint];
    [path stroke];
    }

    /// 清除连接线
    - (void)clearPath {

    // 取消选中按钮
    for (UIButton *btn in self.selectedArray) {
    btn.highlighted = NO;
    btn.selected = NO;
    }

    // 清空选中按钮
    [self.selectedArray removeAllObjects];

    // 刷新视图
    [self setNeedsDisplay];
    }

    /// 懒加载
    - (NSMutableArray *)selectedArray {
    if (_selectedArray == nil) {
    _selectedArray = [NSMutableArray array];
    }
    return _selectedArray;
    }

    @end
  • ViewController.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    // 设置 frame
    CGFloat margin = 50;
    CGFloat width = self.view.bounds.size.width - margin * 2;
    CGRect frame = CGRectMake(margin, 200, width, width);

    // 创建手势锁视图界面,获取滑动结果
    QTouchLockView *touchLockView = [QTouchLockView q_touchLockViewWithFrame:frame
    pathResult:^(BOOL isSucceed, NSString * _Nonnull result) {

    // 处理手势触摸结果
    [self dealTouchResult:result isSucceed:isSucceed];
    }];

    [self.view addSubview:touchLockView];

    - (void)dealTouchResult:(NSString *)result isSucceed:(BOOL)isSucceed {

    // 处理手势触摸结果

    if (isSucceed) {

    // 判读密码是否存在
    NSUserDefaults *df = [NSUserDefaults standardUserDefaults];

    if ([df objectForKey:@"touchLock"] == nil) {

    // 设置手势锁

    [self.passWordArrM addObject:result];

    if (self.passWordArrM.count == 1) {
    self.touchLockView.alertLabel.text = @"请再设置一次";
    }

    if (self.passWordArrM.count == 2) {
    if ([self.passWordArrM[0] isEqualToString:self.passWordArrM[1]]) {

    // 存储密码
    [df setValue:self.passWordArrM[0] forKey:@"touchLock"];
    [df synchronize];

    self.touchLockView.alertLabel.text = @"手势密码设置成功";

    } else {

    // 两次滑动结果不一致
    [self.passWordArrM removeAllObjects];

    self.touchLockView.alertLabel.text = @"两次滑动的结果不一致,请重新设置";
    }
    }

    } else {

    // 解锁

    if ([result isEqualToString:[df objectForKey:@"touchLock"] ]) {
    self.touchLockView.alertLabel.text = @"解锁成功";
    } else {
    self.touchLockView.alertLabel.text = @"密码不正确,请重试";
    }
    }

    } else {

    // 滑动点数过少
    self.touchLockView.alertLabel.text = result;
    }
    }
  • 效果

    Quartz2D95 Quartz2D96

文章目录
  1. 1. 1、绘制手势锁
隐藏目录