iOS输入框字符输入约束

1. 有这样一个需求

a. 在公司项目开发中涉及到多国语言,但是在项目里产品司理要求约束一些国家语言里的特殊符号输入。

b. 只答应输入26个英文字母、英文标点符号产品经理工资一般多少

2. 那我该怎么去完成呢?

1. 一般计划

经过UITextV产品iew、UITextField的delegate办法:

// UITextView的署理办法
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
// TextField的署理办法
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;

判别针对当前输入的字符回来YES或者NO来判别是否答应字符输入

2. 改善计划

上述计划需要在View、VC中分别完产品成署理办法,榜首工作量会产品生命周期比较大、第二也不易于后期的保护所以有没有更产品经理是做什么好的解决办枚举类型中的元素都有一个整型值法呢?

a. 经过对-(BOOL)textField:(UITextField *)textField shouldCha产品经理证书ngeCharactersInRange:(NSRange)range repl输入框测试用例acementStri产品经理是不是销售ng:(NSString *)string办法断点,查看函数调用栈发现,UIKit结构会在调用该署理办法前调用 -(BOOL)_delegateShouldChangeCha成员变量ractersInTextStorageRange:(NSRange)range replacementStri枚举类型enum用法ng:text delegateCares:(BOOL)care ;

b. 所以可以对UITextField这个办法进行hook经过method-swizzling;

c. 同时经过类相关技能(Associat输入框不显示ed)增加两个成员变量

// 枚举类型,设置当前UITextField支持的字符调集可经过|运算完成多个字符调集的支持
@property(nonatomic,assign)AvailableCharacterSet availableCharacterSet;
// UITextField 目标经过完成该办法,经过回来YES或者NO可以用以判别是否答应输入某个字符/字符串
@property**(**nonatomic,copy)BOOL (^isAvailableBlock)(NSString *text,BOOL result);

d. 在hook的办产品生命周期法里,经过avail成员变量和局部变量区别ableCharacterSet输入框变小了怎么恢复、i输入框变小了怎么恢复sAvail成员变量和局部变量ableBlock的设置判别是否答应某个字符的输入

3. 完成代码如下:

a. 支持的枚举值

#ifndef LimitInput_h
#define LimitInput_h
typedef NS_OPTIONS(NSUInteger, AvailableCharacterSet) {
    AvailableCharacterSetAll                = 0,   //一切字符
    AvailableCharacterSetLowerCaseLetters   = 1<<0,//小写字母 [a-z]
    AvailableCharacterSetUpperCaseLetter    = 1<<1,//大写字母 [A-Z]
    AvailableCharacterSetNumber             = 1<<2,//数字 [0-9]
    AvailableCharacterSetDecimalPad         = 1<<3,//带小数点的数字
    AvailableCharacterSetEnglishPunctuation = 1<<4,//英文标点符号
};
#endif /* LimitInput_h */

b. NSString+LimitI产品经理是不是销售nput类包含不同类型的字符调集,用正则输入框表达式的方式判别输入的字符是否在设置的字符调集内

#import <Foundation/Foundation.h>
#import "LimitInput.h"
NS_ASSUME_NONNULL_BEGIN
@interface NSString (LimitInput)
-(BOOL)isValidWithAvailableCharacterSet:(AvailableCharacterSet)set;
@end
NS_ASSUME_NONNULL_END
#import "NSString+LimitInput.h"
static NSSet* lowerCaseLetters(){
    static NSSet *_lowerCaseLetters = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *lowerCaseLetters = @[
            @"q", @"w", @"e", @"r", @"t", @"y", @"u", @"i", @"o", @"p",
            @"a", @"s", @"d", @"f", @"g", @"h", @"j", @"k", @"l",
            @"z", @"x", @"c", @"v", @"b", @"n", @"m"];
        _lowerCaseLetters = [NSSet setWithArray:lowerCaseLetters];
    });
    return _lowerCaseLetters;
}
static NSSet* upperCaseLetter(){
    static NSSet *_upperCaseLetter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *upperCaseLetter = @[
            @"Q", @"W", @"E", @"R", @"T", @"Y", @"U", @"I", @"O", @"P",
            @"A", @"S", @"D", @"F", @"G", @"H", @"J", @"K", @"L",
            @"Z", @"X", @"C", @"V", @"B", @"N", @"M"];
        _upperCaseLetter = [NSSet setWithArray:upperCaseLetter];
    });
    return _upperCaseLetter;
}
static NSSet* number(){
    static NSSet *_number = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *number = @[
            @"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"0"
        ];
        _number = [NSSet setWithArray:number];
    });
    return _number;
}
static NSSet* decimalPad(){
    static NSSet *_decimalPad = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSMutableArray *mu_decimalPad = NSMutableArray.new;
        [mu_decimalPad addObject:number().allObjects];
        [mu_decimalPad addObject:@"."];
        NSArray *decimalPad = mu_decimalPad;
        _decimalPad = [NSSet setWithArray:decimalPad];
    });
    return _decimalPad;;
}
static NSSet* englishPunctuation(){
    static NSSet *_englishPunctuation = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *englishPunctuation = @[
            @"`",
            @"~" ,
            @"!",
            @"@",
            @"#",
            @"$",
            @"%",
            @"^",
            @"&" ,
            @"*",
            @"(",
            @")",
            @"_" ,
            @"\-",
            @"+",
            @"=",
            @"\{",
            @"}",
            @"\[",
            @"\]",
            @"\\",
            @"|",
            @"<" ,
            @">" ,
            @",",
            @".",
            @"/",
            @"?",
            @";",
            @":",
            @"'",
            @""",
            @" ",
            @"\n"
        ];
        _englishPunctuation = [NSSet setWithArray:englishPunctuation];
    });
    return _englishPunctuation;
}
static NSPredicate *getPredicateWithCharacterSet(AvailableCharacterSet set){
    static NSMutableDictionary<NSNumber*,NSPredicate*> *_inputLimitPredicates = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _inputLimitPredicates = NSMutableDictionary.new;
    });
    NSPredicate *predicate = _inputLimitPredicates[@(set)];
    if (predicate) {
        return predicate;
    }
    if (set == AvailableCharacterSetAll) {
        return nil;
    }
    NSMutableSet *muset = NSMutableSet.new;
    if (set&AvailableCharacterSetLowerCaseLetters){
        [muset addObjectsFromArray:lowerCaseLetters().allObjects];
    }
    if (set&AvailableCharacterSetUpperCaseLetter){
        [muset addObjectsFromArray:upperCaseLetter().allObjects];
    }
    if (set&AvailableCharacterSetNumber){
        [muset addObjectsFromArray:number().allObjects];
    }
    if (set&AvailableCharacterSetDecimalPad) {
        [muset addObjectsFromArray:decimalPad().allObjects];
    }
    if (set&AvailableCharacterSetEnglishPunctuation){
        [muset addObjectsFromArray:englishPunctuation().allObjects];
    }
    NSString *exp = [NSString stringWithFormat:@"[%@]*",[[muset allObjects] componentsJoinedByString:@""]];
    _inputLimitPredicates[@(set)] = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",exp];
    return _inputLimitPredicates[@(set)];
}
@implementation NSString (LimitInput)
-(BOOL)isValidWithAvailableCharacterSet:(AvailableCharacterSet)set{
    if (set == AvailableCharacterSetAll) {
        return YES;
    }
    return [getPredicateWithCharacterSet(set) evaluateWithObject:self];
}
@end

c. UITextField+LimitInput 完成员变量和静态变量的区别成对办法的hook,增加完成字符是否答应输入的逻辑

#import <UIKit/UIKit.h>
#import "LimitInput.h"
@interface UITextField (Category)
@property(nonatomic,assign)AvailableCharacterSet availableCharacterSet;
/**
 该block可阻拦到当前输入的文本是否可用,并且可以更改不可用文本为可用文本
 */
@property(nonatomic,copy)BOOL (^isAvailableBlock)(NSString *text,BOOL result);
@end
#import "UITextField+LimitInput.h"
#import <objc/runtime.h>
#import "NSString+LimitInput.h"
static char UITextFieldAvailableCharacterSetKey;
static char UITextFieldIsAvailableBlockKey;
@implementation UITextField (Category)
+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodExchange];
    });
}
-(AvailableCharacterSet)availableCharacterSet{
    return [objc_getAssociatedObject(self, &UITextFieldAvailableCharacterSetKey) integerValue];
}
-(void)setAvailableCharacterSet:(AvailableCharacterSet)availableCharacterSet{
    objc_setAssociatedObject(self, &UITextFieldAvailableCharacterSetKey, @(availableCharacterSet), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL (^)(NSString *, BOOL))isAvailableBlock{
    return objc_getAssociatedObject(self, &UITextFieldIsAvailableBlockKey);
}
-(void)setIsAvailableBlock:(BOOL (^)(NSString *, BOOL))isAvailableBlock{
    objc_setAssociatedObject(self, &UITextFieldIsAvailableBlockKey, isAvailableBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
+(void)methodExchange{
    SEL textFiledSEL = NSSelectorFromString(@"_delegateShouldChangeCharactersInTextStorageRange:replacementString:delegateCares:");
    if ([UITextField.new respondsToSelector:textFiledSEL] == NO) {
        return;
    }
    Method textFiledMethod = class_getInstanceMethod(UITextField.class, textFiledSEL);
    Method exTextFiledMethod = class_getInstanceMethod([self class], @selector(_ex_delegateShouldChangeCharactersInTextStorageRange:replacementString:delegateCares:));
    method_exchangeImplementations(textFiledMethod, exTextFiledMethod);
}
-(BOOL)_ex_delegateShouldChangeCharactersInTextStorageRange:(NSRange)range replacementString:text delegateCares:(BOOL *)cares{
    BOOL result = [self _ex_delegateShouldChangeCharactersInTextStorageRange:range replacementString:text delegateCares:cares];
    if (result == YES && self.availableCharacterSet != AvailableCharacterSetAll) {
        result = [text isValidWithAvailableCharacterSet:self.availableCharacterSet];
        if (self.isAvailableBlock) {
            return self.isAvailableBlock(text,result);
        }
        return result;
    }
    return result;
}
@end

UITextView也可以依照这个逻辑,去完成字符输入的约束。

输入框怎么调大小:如有误,请我们多多产品设计指教十分感谢输入框