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.pswp.cn/news/541688.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/541688.shtml
英文地址,請注明出處:http://en.pswp.cn/news/541688.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c ++查找字符串_C ++結構| 查找輸出程序| 套裝1

c 查找字符串Program 1: 程序1&#xff1a; #include <iostream>#include <math.h>using namespace std;struct st {int A NULL;int B abs(EOF EOF);} S;int main(){cout << S.A << " " << S.B;return 0;}Output: 輸出&#xff1a…

二級c語言加油,二級C語言 備考指南及常見問題(2013版)

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓3、關于上機操作部分的復習最好買一本上機題庫方面的教材&#xff0c;或打印、閱讀南開百題之類的電子文檔。配合上機模擬軟件(無紙化考試軟件)&#xff0c;上機練習是必須的。上機軟件一般有100套題多一點&#xff0c;每套有程序填…

開放定址散列表

再散列之后散列函數要重新計算。 // kaifangliaobiao.cpp : 定義控制臺應用程序的入口點。 //使用平方探測解決沖突問題時&#xff0c;散列表至少空一半時&#xff0c;總能插入一個新的元素#include "stdafx.h" #include<iostream> using namespace std;#ifnde…

合并兩個鏈表數據結構c語言,合并兩個鏈表.

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓#include #define N1 10#define N2 10struct list{int date ;struct list *next;};main(){struct list *p1,*p2,*p3,*p4,*head,*head1,*head2,*p;int n0;head1head2NULL;p1p2(struct list *)malloc(sizeof(struct list));p1->da…

c ++查找字符串_C ++結構| 查找輸出程序| 套裝2

c 查找字符串Program 1: 程序1&#xff1a; #include <iostream>using namespace std;int main(){typedef struct{int A;char* STR;} S;S ob { 10, "india" };S* ptr;ptr &ob;cout << ptr->A << " " << ptr->STR[2];…

連接fiddler后手機無法顯示無網絡

升級了fiddler到4.6版本&#xff0c;手機設置代理后提示無網絡&#xff0c;試試以下解決方法&#xff1a; 1.fiddler升級后對應的.net framework也要升級&#xff0c;安裝最新的.net framework 4.6&#xff0c;升級安裝后&#xff0c;可以正確抓包啦 2.如果上述方法無效&#x…

android 人臉解鎖 鎖屏動畫,人臉保護鎖(人臉識別鎖屏)

這是一款十分炫酷的鎖屏工具&#xff0c;還記得電影中的特工所用的人臉識別鎖嗎&#xff1f;這款應用也能讓你過過癮&#xff01;人臉識別鎖屏安卓版是一款用人臉做密碼來打開手機屏保鎖的一個APP。不僅可以作屏保鎖&#xff0c;也可以單獨保護某些重要程序不被偷窺,例如查看短…

dbms_排名前50位的DBMS面試問答

dbms1) What are the drawbacks of the file system which is overcome on the database management system? 1)在數據庫管理系統上克服的文件系統有哪些缺點&#xff1f; Ans: Data redundancy & isolation, difficulty in accessing data, data isolation, and integri…

linux時間

CST代表中國標準時間rtc實時時鐘linux主要有兩種時間硬件時鐘 clock系統時鐘 date修改時間 date 03300924必須是兩位或者 date -s 2017:03:30將系統時間同步到硬件時間 hwclock -w將硬件時間同步到系統時間 hwclock -s轉載于:https://blog.51cto.com/12372297/1911608

查找Python中給定字符串的所有排列

Python itertools Module Python itertools模塊 "itertools" are an inbuilt module in Python which is a collection of tools for handling iterators. It is the most useful module of Python. Here, a string is provided by the user and we have to print a…

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可以先填寫相同。…

單位矩陣的逆| 使用Python的線性代數

Prerequisites: 先決條件&#xff1a; Defining a Matrix 定義矩陣 Identity Matrix 身份矩陣 There are matrices whose inverse is the same as the matrices and one of those matrices is the identity matrix. 有些矩陣的逆與矩陣相同&#xff0c;并且這些矩陣之一是單位…

華為榮耀七能升級鴻蒙系統嗎,華為鴻蒙系統來了,你知道哪些華為手機榮耀手機可以升級嗎?...

從鴻蒙系統第一次開始登場&#xff0c;到現在慢慢有許多鴻蒙系統設備出現&#xff0c;手機市場的格局似乎又要升級變化了。科技樹兒了解到&#xff0c;在某數碼博主經過和相關人員的溝通核實之后&#xff0c;目前暫定的是搭載華為麒麟710芯片以上的機型&#xff0c;無論華為或榮…

day5-shutil模塊

一、簡述 我們在日常處理文件時&#xff0c;經常用到os模塊&#xff0c;但是有的時候你會發現&#xff0c;像拷貝、刪除、打包、壓縮等文件操作&#xff0c;在os模塊中沒有對應的函數去操作&#xff0c;下面我們就來講講高級的 文件、文件夾、壓縮包 處理模塊&#xff1a;shuti…

matlab中now函數_now()方法以及JavaScript中的示例

matlab中now函數JavaScript now()方法 (JavaScript now() method) now() method is a Date class method, it is used to current time in milliseconds, it returns the total number of milliseconds since 01st January 1970, 00:00:00 UTC. now()方法是Date類的一種方法&am…

android 集成x5內核時 本地沒有,騰訊瀏覽服務-接入文檔

三、SDK集成步驟1. 第一步下載 SDK jar 包放到工程的libs目錄下&#xff0c;將源碼和XML里的系統包和類替換為SDK里的包和類&#xff0c;具體對應如下&#xff1a;系統內核SDK內核android.webkit.ConsoleMessagecom.tencent.smtt.export.external.interfaces.ConsoleMessageand…

java vector_Java Vector sureCapacity()方法與示例

java vector向量類別sureCapacity()方法 (Vector Class ensureCapacity() method) ensureCapacity() method is available in java.util package. sureCapacity()方法在java.util包中可用。 ensureCapacity() method is used to ensure the capacity of this Vector when requi…