最近在做用户登录获取验证码时添加图形验证码功能,就是只有正确输入图形验证码才能收到后台发送的短信验证码。效果如下:

看起来虽然是个小功能,但是实际操作起来,会发现苹果给我们留下的坑,当然更多的是自己给自己挖的坑。本文就是解决这个小功能出现的最麻烦的一个坑。

先介绍图中四个输入框的业务逻辑:

1、每个输入框都不能手动点击成为第一响应者,只能通过键盘输入控制,也就是只能前进和后退;

2、输入框全部输入且正确就请求后台获取相应手机的短信验证码,请求失败则在视图中显示失败信息;

主要的功能就这两点,当然还有更细节的地方就不作考虑。然后在此过程中,发现了一个非常致命的坑:由于后台给的图形验证码是纯数字的,然后我定义的四个输入框限制了键盘类型都为数字键盘,然后问题是当输入框没有任何文字时,点击键盘的delete(✘)键没有任何效果,从代码的角度来讲就是textField的代理和点击事件中没有一个方法能够监听到这个回调,那么我所要实现的输入框无文字点击delete键使后一个textField成为第一响应者的功能就变得毫无可能。真是自作孽不可活啊,当然有很多方法可以直接跳过这个bug,比如换一个输入框的实现方式:只用一个textField,然后在textField视图上叠加四个label或是别的能显示文字的视图。或者还是原来的实现方式,只不过要自定义数字键盘(不建议这么做,系统的键盘做了很多特殊处理,如键盘优先级、通知方法等等,自己实现会花很多功夫)。当然,不止这些方法可以实现这个功能,只是作为程序员的我怎么能后避过眼前的bug呢?就是这样一个不服输的精神终于让我想到了一个惊为天人的实现技巧。

说明:本文bug只适用于系统数字键盘,普通键盘是完全不会出现的,其他键盘我未作测试,请看清本文意图。

接下来就来说明技巧的实现方式:

该技巧的精髓是亦幻亦真,蒙蔽用户的眼睛。

既然在textField无文字时无法监听到数字键盘的delete键,那么我另辟蹊径,始终让textField有文字,但是也不显示,那就是使用“ ”(一个空格字符串)来代替nil(空字符串)。当用户每输入一个数字,让下一个textField获取焦点,与此同时,给下一textField文字赋上空格字符串,那么该textField就同时具备了再次输入和监听键盘delete键的特性;当该textField点击了delete键时,让上一个textField获取焦点,并给其文字赋上空格字符串。如此循环往复,就能完成多个textField的焦点切换。但与此同时产生的问题,下一个textField在没有输入之前就已经有了空格字符串,当输入时,文字就不再居中而是往后偏移了一个字符的宽度。当然,这怎么能难得了我:原本的每个textField都是有焦点的光标闪动的,现在我让此光标不可见,然后在输入数字的同时将原来的“空格+数字”字符串替换为本次输入的数字就可以了。

废话有点多了,直接上代码。

首先我新建了一个类,继承UITextField,目的是拦截用户点击,是点击变得不可响应。

//
//  YTUnclickableTextField.m
//  分时租赁
//
//  Created by chips on 17/3/27.
//  Copyright © 2017年 柯其谱. All rights reserved.
//#import "YTUnclickableTextField.h"NSString * const YTUnclickableTextFieldSpace = @" ";@implementation YTUnclickableTextField- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {return nil;
}- (BOOL)becomeFirstResponder {self.text = YTUnclickableTextFieldSpace;return [super becomeFirstResponder];
}@end

该类重写了两个系统方法:第一个用于拦截用户点击以此确认点击的目标view,直接返回nil后该该textField的示例就无法响应点击事件,但焦点依然可以代码获取。有人就说了,直接将enabled或userInteractionEnabled属性设置为NO就可以了。我的回答是绝对不行,设置任何一个属性为NO不仅会导致不能响应用户点击事件,而且textField的焦点都无法获取,亲测。第一个方法只是在每一个textField获取焦点时给文本赋值为空格字符串,并将该字符串设为外部变量,好让图形验证码view作下一步判断。

接下来是重头,图形验证码自定义view类:

//
//  PicVerifyCodeView.m
//  分时租赁
//
//  Created by chips on 17/3/24.
//  Copyright © 2017年 柯其谱. All rights reserved.
//#import "PicVerifyCodeView.h"
#import "YTUnclickableTextField.h"
#import "YTHttpTool.h"
#import "Masonry.h"static NSInteger const kPicVerifyCodeNumber = 4;@interface PicVerifyCodeView () <UITextFieldDelegate>/** 请求图形验证码图片的url字符串 */
@property (nonatomic, copy) NSString *imageUrlString;
/** 验证码错误label */
@property (nonatomic, strong) UILabel *errorLabel;
/** 图形验证码imageView */
@property (nonatomic, strong) UIImageView *verifyCodeImageView;
/** 再生成图形验证码button */
@property (nonatomic, strong) UIButton *regenerateButton;@end@implementation PicVerifyCodeView#pragma mark - setter and getter
- (NSMutableArray<YTUnclickableTextField *> *)textFields {if (_textFields == nil) {_textFields = [NSMutableArray array];}return _textFields;
}#pragma mark - Construction method
- (instancetype)initWithFrame:(CGRect)frame tel:(NSString *)tel delegate:(id<PicVerifyCodeViewDelegate>)delegate {if (self = [super initWithFrame:frame]) {self.backgroundColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.8];[self setupSubviews];self.imageUrlString = [NSString stringWithFormat:@"%@:%@/Account/ValidateCode?Tel=%@", YTHttpToolURLString, YTHttpToolPort, tel];[self generateVerCode];self.tel = tel;self.delegate = delegate;}return self;
}#pragma mark - Setup
- (void)setupSubviews {UIView *view = [[UIView alloc]init];[self addSubview:view];view.backgroundColor = [UIColor whiteColor];view.layer.cornerRadius = 10;CGFloat cancelImageViewW = 16;CGFloat margin = 16;UIImageView *cancelImageView = [[UIImageView alloc]init];[view addSubview:cancelImageView];cancelImageView.image = [UIImage imageNamed:@"chacha"];cancelImageView.userInteractionEnabled = YES;UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapCancelImageView:)];[cancelImageView addGestureRecognizer:tap];CGFloat labelH = 30;UILabel *label = [[UILabel alloc]init];[view addSubview:label];label.text = @"请输入图形验证码";label.textAlignment = NSTextAlignmentCenter;label.font = [UIFont systemFontOfSize:18];UILabel *errorLabel = [[UILabel alloc]init];[view addSubview:errorLabel];self.errorLabel = errorLabel;errorLabel.textColor = [UIColor redColor];errorLabel.textAlignment = NSTextAlignmentCenter;errorLabel.font = [UIFont systemFontOfSize:12];UIView *picView = [[UIView alloc]init];[view addSubview:picView];UIButton *button = [[UIButton alloc]init];self.regenerateButton = button;[view addSubview:button];button.backgroundColor = AppStyleColor;[button setImage:[UIImage imageNamed:@"sx"] forState:UIControlStateNormal];[button addTarget:self action:@selector(clickRegenerateButton) forControlEvents:UIControlEventTouchUpInside];UIImageView *picImageView = [[UIImageView alloc]init];self.verifyCodeImageView = picImageView;[picView addSubview:picImageView];UIView *textFieldsView = [[UIView alloc]init];[view addSubview:textFieldsView];for (int i = 0; i < kPicVerifyCodeNumber; i++) {YTUnclickableTextField *textField = [[YTUnclickableTextField alloc]init];[self.textFields addObject:textField];[textFieldsView addSubview:textField];textField.textAlignment = NSTextAlignmentCenter;textField.keyboardType = UIKeyboardTypeNumberPad;textField.tintColor = [UIColor clearColor];[[self class]setupBorderColor:textField];textField.layer.cornerRadius = 5;textField.layer.borderWidth = 1;textField.delegate = self;[textField addTarget:self action:@selector(editingChangedWith:) forControlEvents:UIControlEventEditingChanged];if (i == 0) {[textField becomeFirstResponder];textField.layer.borderColor = AppStyleColor.CGColor;}}[view mas_makeConstraints:^(MASConstraintMaker *make) {make.leading.equalTo(super.mas_leading).with.offset(40);make.top.equalTo(super.mas_top).with.offset(130);make.centerX.equalTo(super.mas_centerX);make.height.equalTo(view.mas_width).with.dividedBy(1.3);}];[cancelImageView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(view.mas_top).with.offset(margin);make.trailing.equalTo(view.mas_trailing).with.offset(-margin);make.width.mas_equalTo(cancelImageViewW);make.height.equalTo(cancelImageView.mas_width);}];[label mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(cancelImageView.mas_bottom);make.leading.equalTo(view.mas_leading);make.trailing.equalTo(view.mas_trailing);make.height.mas_equalTo(labelH);}];[errorLabel mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(label.mas_bottom);make.height.mas_equalTo(30);make.leading.equalTo(label.mas_leading);make.trailing.equalTo(label.mas_trailing);}];CGFloat picMargin = 16;[picView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(errorLabel.mas_bottom).with.offset(8);make.leading.equalTo(view.mas_leading).with.offset(cancelImageViewW+margin);make.trailing.equalTo(cancelImageView.mas_leading);make.bottom.equalTo(textFieldsView.mas_top).offset(-picMargin);}];[button mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(picView.mas_top);make.trailing.equalTo(picView.mas_trailing);make.bottom.equalTo(picView.mas_bottom);make.width.equalTo(button.mas_height);}];[picImageView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(picView.mas_top);make.leading.equalTo(picView.mas_leading);make.trailing.equalTo(button.mas_leading);make.bottom.equalTo(picView.mas_bottom);}];[textFieldsView mas_makeConstraints:^(MASConstraintMaker *make) {make.height.equalTo(picView.mas_height);make.leading.equalTo(picView.mas_leading);make.trailing.equalTo(picView.mas_trailing);make.bottom.equalTo(view.mas_bottom).with.offset(-picMargin);}];CGFloat textFieldInset = 10;WeakSelf[self.textFields enumerateObjectsUsingBlock:^(YTUnclickableTextField * _Nonnull textField, NSUInteger idx, BOOL * _Nonnull stop) {[textField mas_makeConstraints:^(MASConstraintMaker *make) {make.top.equalTo(textFieldsView.mas_top);make.height.equalTo(textField.mas_width);if (idx == 0) {make.leading.equalTo(textFieldsView.mas_leading);make.trailing.equalTo(weakSelf.textFields[idx+1].mas_leading).with.offset(-textFieldInset);} else if (idx == self.textFields.count-1) {make.width.equalTo(weakSelf.textFields.firstObject.mas_width);make.trailing.equalTo(textFieldsView.mas_trailing);} else {make.width.equalTo(weakSelf.textFields.firstObject.mas_width);make.trailing.equalTo(weakSelf.textFields[idx+1].mas_leading).with.offset(-textFieldInset);}}];}];
}#pragma mark - Event response
- (void)tapCancelImageView:(UITapGestureRecognizer *)sender {[self removeFromSuperview];
}- (void)clickRegenerateButton {[self generateVerCode];
}- (void)editingChangedWith:(UITextField *)sender {if (![sender isFirstResponder]) {return;}if (!sender.text.length) {sender.text = YTUnclickableTextFieldSpace;} else {[self.textFields enumerateObjectsUsingBlock:^(YTUnclickableTextField * _Nonnull textField, NSUInteger idx, BOOL * _Nonnull stop) {if (sender == textField) {//最后一个输入框获取焦点if (idx == self.textFields.count-1) {//获取完整的图形验证码NSMutableString *verCode = [NSMutableString string];for (UITextField *tf in self.textFields) {[verCode appendString:tf.text];}//将self和完整输入的验证码传入delegateif ([self.delegate respondsToSelector:@selector(textFieldsDidEndEditing:verCode:)]) {[self.delegate textFieldsDidEndEditing:self verCode:verCode];}} else {[self.textFields[idx+1] becomeFirstResponder];[[self class] setupBorderColor:textField];[[self class] setupBorderColor:self.textFields[idx+1]];}*stop = YES;}}];}
}#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {if (!textField.isFirstResponder) {return NO;}if (string.length) {textField.text = nil;} else {[self.textFields enumerateObjectsUsingBlock:^(YTUnclickableTextField * _Nonnull iTextField, NSUInteger idx, BOOL * _Nonnull stop) {if (iTextField == textField) {if (idx > 0 && [iTextField.text isEqualToString:YTUnclickableTextFieldSpace]) {[self.textFields[idx-1] becomeFirstResponder];[[self class] setupBorderColor:textField];[[self class] setupBorderColor:self.textFields[idx-1]];}//消除错误label文字if (self.errorLabel.text) {[self showErrorCodeText:nil];}*stop = YES;}}];}return YES;
}#pragma mark - Private method
+ (void)setupBorderColor:(UITextField *)textField {textField.layer.borderColor = textField.isFirstResponder ? AppStyleColor.CGColor : [UIColor lightGrayColor].CGColor;
}- (void)generateVerCode {self.verifyCodeImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.imageUrlString]]];
}#pragma mark - Public method
- (void)showErrorCodeText:(NSString *)text {self.errorLabel.text = text;
}@end

跳过以上繁杂的布局代码,只看textField的事件响应(编辑改变监听不是手动点击)方法editingChangedWith:和代理方法textField:shouldChangeCharactersInRange: :,设置这两个方法的目的是分别负责textField焦点的前进后退。

代码就不多加解释了,如若感兴趣或者有疑问可在下方评论,我会一一作出解答。

转载于:https://www.cnblogs.com/keqipu/p/6632269.html

iOS开发之解决系统数字键盘无文字时delete键无法监听的技巧相关推荐

  1. 用系统数字键盘更容易实现验证码等的校验

    我是iOS开发.在开发新的需求时,有对验证码的校验.以此为例,来说说我是如何校验的. 首先,我从用户体验入手.需求中,1.验证码要求是纯数字,且为六位.2.当验证码框为nil时,下一步按钮不能使用.3 ...

  2. 移动端开发input标签调用数字键盘

    先上代码: <input id="pp" type="number" maxlength="6" pattern="[0-9 ...

  3. iOS 开发之调用系统铃声以及震动

    iOS 开发之调用系统铃声以及震动 @interface AlarmClass : NSObject {SystemSoundID soundID; }//调用震动 -(void)systemShak ...

  4. iOS开发中解决第三方静态库符号冲突的终极方案

    iOS开发中解决第三方静态库符号冲突的终极方案 背景 在iOS开发的时候,经常会使用各种第三方静态库,这些库内部可能会打包了相同的第三方库.那么在链接的时候就会发生符号冲突. 例如:A厂商提供的lib ...

  5. PDF:解决从PDF中复制文字时出现的空方框问题

    PDF:解决从PDF中复制文字时出现的空方框问题 目录 解决问题 解决思路 解决问题 解决从PDF中复制文字时出现的空方框问题 解决思路 将该pdf文档另存为html格式,然后打开html文件,复制文 ...

  6. 基于目标追踪算法、web、gui开发的程序,可实时监控画面、检测目标、监听电脑配置

    基于目标追踪算法.web.gui开发的程序,可实时监控画面.检测目标.监听电脑配置,此项目由软件+网页设计而成,请看项目展示.

  7. uniapp - [完美解决] 手机数字键盘没有小数点,当 input 输入框的 type 属性设置 number 后,手机系统的软键盘无法输入小数点和符号问题(此方案 uniapp 全端全平台适用)

    效果图 正常在uniapp项目中,用户想要输入数字或金额时,通常都会将 <input> 的 type 属性设置为 number.但是问题来了,可能在苹果IOS手机.小程序上.个别安卓机上就 ...

  8. vue解决ios不能自动唤起手机数字键盘问题

    最近工作中,需要自动唤起手机数字键盘,大家都知道有个属性是v-focus. 发现在浏览器中是完全没有问题的,打包后,在苹果手机上不可以自动唤起,而在安卓手机上可以.让我很是费解,上网查,得到的答案都是 ...

  9. 自学 iOS 开发的一些经验 - 转自无网不剩的博客

    不知不觉作为 iOS 开发也有两年多的时间了,记得当初看到 OC 的语法时,愣是被吓了回去,隔了好久才重新耐下心去啃一啃.啃了一阵,觉得大概有了点概念,看到 Cocoa 那么多的 Class,又懵了, ...

最新文章

  1. 找到那些氪金大佬,然后榨干他们丨AIの特殊技能
  2. Coding:在数组中查找具有给定总和的对
  3. 计算机主机中网卡的作用,计算机硬件组成及作用
  4. MyBatis-Plus_断言
  5. tia v15 添加项目_硬技能,TIA 博途软件界面的介绍
  6. 前端—每天5道面试题(2)
  7. Sqlite大数据量查询优化比较-转
  8. PowerDesigner注意事项
  9. javascript的caller,callee,call,apply
  10. InvokeRequired和Invoke
  11. 优米网:20部电影,哈佛商学院学生必看   下载地址
  12. 第三届上海大学生网络安全
  13. 搭建自己的wiki知识管理系统
  14. 2021数学建模国赛A题思路
  15. pg数据库开启远程连接_PostgreSQL 允许远程访问设置的操作
  16. Oracle AWR 阙值影响历史执行计划
  17. VS2017 如何连接mysql数据库依赖的驱动msi
  18. REDSHIFT学习笔记-渲染设置2_AOVOpt
  19. IFR报告显示过去五年全球工业机器人销量翻番
  20. 面向考研的数据结构板子

热门文章

  1. 无法检索文件服务器,无服务器快速无法检索pdf文件(base64编码)
  2. 叶金荣mysql教程_mysql优化--叶金荣老师讲座笔记
  3. eclipse java的jvm匹配_eclipse设置jvm
  4. linux多台主机对比文件大小,Linux主机df和du出来的文件和磁盘大小不相同
  5. php 图片合成,PHP中多张图片合成一张图片例子
  6. android 双层饼图_python:给表格加上双层饼图,让同事的图表黯然失色
  7. macbook配置java环境变量_配置mac上Java环境变量
  8. 萧县机器人_全国总决赛第一名!萧县杨楼的这位学生厉害了
  9. java局部变量简述
  10. 不允许输入特殊字符的正则表达式_JavaScript正则表达式常用技巧