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

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

 

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

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

  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/541688.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

连接fiddler后手机无法显示无网络

升级了fiddler到4.6版本&#xff0c;手机设置代理后提示无网络&#xff0c;试试以下解决方法&#xff1a; 1.fiddler升级后对应的.net framework也要升级&#xff0c;安装最新的.net framework 4.6&#xff0c;升级安装后&#xff0c;可以正确抓包啦 2.如果上述方法无效&#x…

android 图片叠加xml,Android实现图片叠加效果的两种方法

本文实例讲述了Android实现图片叠加效果的两种方法。&#xff0c;具体如下&#xff1a;效果图&#xff1a;第一种&#xff1a;第二种&#xff1a;第一种是通过canvas画出来的效果:public void first(View v) {// 防止出现Immutable bitmap passed to Canvas constructor错误Bit…

Win10系列:VC++ 定时器

计时器机制俗称"心跳"&#xff0c;表示以特定的频率持续触发特定事件和执行特定程序的机制。在开发Windows应用商店应用的过程中&#xff0c;可以使用定义在Windows::UI::Xaml命名空间中的DispatcherTimer类来创建计时器。DispatcherTimer类包含了如下的成员&#xf…

dbms系统 rdbms_DBMS与传统文件系统之间的区别

dbms系统 rdbmsIntroduction 介绍 DBMS and Traditional file system have some advantages, disadvantages, applications, functions, features, components and uses. So, in this article, we will discuss these differences, advantages, disadvantages and many other …

android 百度地图api密钥,Android百度地图开发获取秘钥之SHA1

最近在做一个关于百度地图的开发。不过在正式开发之前还必须要在百度地图API官网里先申请秘钥&#xff0c;而在申请秘钥的过程中&#xff0c;就需要获取一个所谓的SHA1值。如上所示&#xff0c;但是由于不是正式开发&#xff0c;所以以上的发布版和开发版的SHA1可以先填写相同。…

华为荣耀七能升级鸿蒙系统吗,华为鸿蒙系统来了,你知道哪些华为手机荣耀手机可以升级吗?...

从鸿蒙系统第一次开始登场&#xff0c;到现在慢慢有许多鸿蒙系统设备出现&#xff0c;手机市场的格局似乎又要升级变化了。科技树儿了解到&#xff0c;在某数码博主经过和相关人员的沟通核实之后&#xff0c;目前暂定的是搭载华为麒麟710芯片以上的机型&#xff0c;无论华为或荣…

Tcl与Design Compiler (十二)——综合后处理

本文如果有错&#xff0c;欢迎留言更正&#xff1b;此外&#xff0c;转载请标明出处 http://www.cnblogs.com/IClearner/ &#xff0c;作者&#xff1a;IC_learner 概述 前面也讲了一些综合后的需要进行的一些工作&#xff0c;这里就集中讲一下DC完成综合了&#xff0c;产生了…

四则运算网页版

一.设计思想&#xff1a; 1&#xff09;写出一个菜单界面&#xff0c;有两个选项一个是分数&#xff0c;一个是整数。 2&#xff09;而这两个标签后面则是转向其更详细的菜单&#xff0c;题目数量&#xff0c;有无括号&#xff0c;运算的项数等等详细功能&#xff0c;再点击这两…

分布式交换机配置备份和还原

1.备份和还原vSphere Distributed Switch配置 1.1导出 vSphere Distributed Switch 配置 可以将 vSphere Distributed Switch 和分布式端口组配置导出到某一文件。该文件保留有效的网络配置&#xff0c;使这些配置能够传输至其他环境。 步骤&#xff1a; 1) 在 vSphere Web Cli…

华为鸿蒙系统好在哪,华为鸿蒙2.0可以替代安卓吗,华为鸿蒙2.0优势在哪

在华为开发者大会上&#xff0c;华为消费业务CEO 余承东&#xff0c;正式发布鸿蒙OS2.0&#xff0c;并宣布华为鸿蒙OS将全面启用全场景生态&#xff0c;并将于2020年12月发布手机版。余承东还表示&#xff0c;明年&#xff0c;华为的智能手机将全面升级&#xff0c;以支持鸿蒙操…

html5画分形图形,2.5 绘制透明图形 - HTML5 Canvas 实战

对于需要图形分层的应用&#xff0c;经常需要处理透明度。本节&#xff0c;我们将学习如何使用全局透明度设置图形的透明度。图2-5 绘制透明图形绘制步骤按照以下步骤&#xff0c;在一个不透明的矩形之上&#xff0c;绘制一个透明的圆&#xff1a;1. 定义2D画布上下文&#xff…

html5录音功能代码,recorder.js 基于 HTML5 实现录音功能

recorder.js 基于 HTML5 实现录音功能2020-06-23 01:49:56recorder.jsmicrophone基于HTML5的录音功能&#xff0c;输出格式为mp3文件。前言完全依赖H5原生API所涉及的API&#xff1a;WebRTC、AudioContext、Worker、Video/Audio API、Blob、URL兼容性Chrome、FF、Edge、QQ、360…

html5页面引入jquery,如何在javascript中引入jQuery?

jquery是一个用来代替JavaScript来快捷书写前端脚本语言的库&#xff0c;jquery可以大大的简化复杂的js代码&#xff0c;使开发人员专注于实现页面的效果。jquery的导入方式有两种&#xff0c;一种是本地导入&#xff0c;一种是从超链接导入。方式一&#xff1a;本地导入我们可…

湖南省普通招生2021高考成绩查询,湖南省2021八省联考成绩可查,附查询入口及往年分数线...

原标题&#xff1a;湖南省2021八省联考成绩可查&#xff0c;附查询入口及往年分数线湖南省2021年八省联考新高考适应性考试成绩公布&#xff0c;这次大家考的如何呢&#xff1f;此次成绩排名对于考生择校及志愿填报有一定的参考意义&#xff0c;小盒一时间收集整理相关消息&…

Ubuntu抛弃了Untiy转向Gnome,美化之路怎么办?不用怕咱一步一步大变身!

跨平台系列汇总&#xff1a;http://www.cnblogs.com/dunitian/p/4822808.html#linux 常用软件安装系统软件卸载&#xff1a;http://www.cnblogs.com/dunitian/p/6670560.html 1.下载UnityGnome版本 https://wiki.ubuntu.com/UbuntuGNOME/GetUbuntuGNOME 2.打开终端 or CtrlAltT…

html木桶布局,CSS3如何实现图片木桶布局?(附代码)

本篇文章给大家通过代码示例介绍一下使用CSS3实现图片木桶布局的方法。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对大家有所帮助。高度相同&#xff0c;而宽度不一样的布局&#xff0c;称之为木桶布局。它有几个鲜明的特点&#xff1a; 每行的图片…

万用表怎么测量电池容量_万用表检测光电耦合器的常用技巧

光电耦合器又称光耦合器或光耦&#xff0c;它属于较新型的电子产品&#xff0c;已经广泛应用在彩色电视机、彩色显示器、计算机、音视频等各种控制电路中。光电耦合器的构成和原理常见的光电耦合器有 4 脚直插和 6 脚两种&#xff0c;它们的典型实物外形和电路符号如图 3-4所示…

hanlp 训练模型_LTP 4.0!单模型完成6项自然语言处理任务

来源|哈工大SCIR语言技术平台&#xff08;Language Technology Platform, LTP&#xff09;是哈工大社会计算与信息检索研究中心&#xff08;HIT-SCIR&#xff09;历时多年研发的一整套高效、高精度的中文自然语言处理开源基础技术平台。该平台集词法分析&#xff08;分词、词性…

typescript 学习

typescript将在不久的将来从前端大一统的趋势中脱颖而出成为主流编译器。学习ts对前端开发人员来说是不可或缺的。同时&#xff0c;也要抓紧学习es2015/6/7。ts和es6并不是对立的。而是相辅相成的。ts的竞争和打击对象实质上是babel…… 官方资料 # 官方地址&#xff1a; https…

计算机中央处理器cpu_中央处理器(CPU)| 计算机科学组织

计算机中央处理器cpu中央处理器(CPU) (Central Processing Unit (CPU)) The CPU is the brain of the computer system. It works as an administrator of a system. CPU是计算机系统的大脑。 它以系统管理员的身份工作。 All the operations within the system are supervised…