前言
相信大家在ios移动应用开发中,经常会遇到这样的需求:给输入框限定输入格式,比如最多只能输入11个电话数字、只能输入小于指定值的价格格式(同时最多两位小数)等等。我看了大家基本上是实现UITextFieldDelegate协议的textField:shouldChangeCharactersInRange:replacementString:方法。但我发现很少对这种常见的需求实现进行封装。试想如果在开发中,一个view中有很多个UITextfield,而且还是需要不同的输入格式要求,那么我想在代理方法textField:shouldChangeCharactersInRange:replacementString:中的实现还是比较繁琐的,需要各种判断。
实现思路
为了解决上述问题,我想封装一个可以定制任何格式的UITextfileld 子类。
我准备也在代理方法textField:shouldChangeCharactersInRange:replacementString:里面进行字符判断,来控制输入框能否编辑。但为了更好的定制化,需要运用不同的正则表达式来匹配格式。同时这些判断逻辑我会写在的UITextfileld子类 --- BSLimitFormTextFild里面。具体步骤如下:
- 创建UITextfileld子类BSLimitFormTextFild,添加正则表达式文本的属性regex,用于控制各式各样的输入要求
#import <UIKit/UIKit.h>
static NSString *limitRegexPrice = @"^(\\d{0,5})(\\.[0-9]{0,2})?$"; // 最大值为 99999.99的价格正则表达式
@interface BSLimitFormTextFild : UITextField
@property (nonatomic, copy) NSString *regex; /**< 输入框内容的正则表达式 */
@end
- 在BSLimitFormTextFild里面实现UITextfileld的代理方法textField:shouldChangeCharactersInRange:replacementString:,通过返回YES or NO来控制是否能够输入。
#import "BSLimitFormTextFild.h"
#import <objc/runtime.h>
@interface BSLimitFormTextFild() <UITextFieldDelegate>
@end
@implementation BSLimitFormTextFild
#pragma mark - Life Cycle && Initialize
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.delegate = self;
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.delegate = self;
}
#pragma mark - private action
- (BOOL)predicateWithRegex:(NSString *)regex
text:(NSString *)text
textField:(UITextField *)textField {
NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex];
BOOL originMatch = YES;
if (textField.text.length) {
originMatch = [pred evaluateWithObject:textField.text];
}
if (originMatch) {
//本来是符合规则的
return [pred evaluateWithObject:text];
}else{
//本来不符合规则的话就清空(比如外部对该textFild set 一个不合规的text,当点击开始编辑时就要清空)
textField.text = nil;
return YES;
}
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *toBeString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (!toBeString.length) {
return YES;
}
if (self.regex) {
return [self predicateWithRegex:self.regex text:toBeString textField:textField];
} else {
return YES;
}
}
- 最后,可能在使用BSLimitFormTextFild的时候,外部设置了delegate,比如用来监听UITextField的开始编辑、结束编辑的操作的话,这时会失效以上那些功能;所以我重写了- (void)setDelegate:(id<UITextFieldDelegate>)delegate 方法,如果外部设置了新delegate,又没有使用到textField:shouldChangeCharactersInRange:replacementString:方法的话,就自动给新代理动态添加该方法,如果用到了就不做任何操作。具体代码如下:
- (void)setDelegate:(id<UITextFieldDelegate>)delegate {
[super setDelegate:delegate];
if (delegate != self) {
if (![delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) {
// 代理没有实现该代理方法,就动态帮他实现
// b表示反悔bool值; b@:表示没有参数; b@:@表示有一个参数(文档:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
class_addMethod(delegate.class, @selector(textField:shouldChangeCharactersInRange:replacementString:), class_getMethodImplementation(self.class, @selector(textField:shouldChangeCharactersInRange:replacementString:)), "b@:@:@:@");
} else {
// 外部实现了该代理方法,就由外部决定是否可以编辑
}
}
}
demo演示
#import "ViewController.h"
#import "BSLimitFormTextFild.h"
@interface ViewController () <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet BSLimitFormTextFild *nomalTF;
@property (weak, nonatomic) IBOutlet BSLimitFormTextFild *priceTF;
@property (weak, nonatomic) IBOutlet BSLimitFormTextFild *integerTF;
@property (weak, nonatomic) IBOutlet BSLimitFormTextFild *limitCountTF;
@property (weak, nonatomic) IBOutlet BSLimitFormTextFild *customTF;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 只能输入价格
self.priceTF.regex = limitRegexPrice;
// 只能输入整数
self.integerTF.regex = limitRegexInteger;
// 最多只能输入11位整数
self.limitCountTF.regex = limitRegexIntegerCount;
self.customTF.delegate = self;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *toBeString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (toBeString.length > 2) {
NSLog(@"最多只能输入两个字");
return NO;
} else {
return YES;
}
}
结语
demo源码我放在github上了https://github.com/LvBisheng/BSLimitFormTextFild和cocopods了(pod 'BSLimitFormTextFild', '~>1.0.0'),欢迎大家指导交流...