123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- //
- // YMCaptchaTextView.m
- // MSYOUPAI
- //
- // Created by YoMi on 2023/5/5.
- //
- #import "YMCaptchaTextView.h"
- #import "YMCaptchaTextField.h"
- typedef NS_ENUM(NSInteger, YMCaptchaTextChangeType) {
- YMCaptchaTextChangeType_NoChange,
- YMCaptchaTextChangeType_Insert,
- YMCaptchaTextChangeType_Delete,
- };
- @interface YMCaptchaTextView () <UICollectionViewDataSource, UICollectionViewDelegate, UITextFieldDelegate>
- {
- NSInteger _oldLength;
- BOOL _ifNeedBeginEdit;
- }
- @property (nonatomic, assign) NSInteger codeLength;
- @property (nonatomic, strong) UITapGestureRecognizer *tap;
- @property (nonatomic, strong) YMCaptchaTextField *textField;
- @property (nonatomic, strong) UICollectionView *mainCollectionView;
- @property (nonatomic, strong) NSMutableArray <NSString *> *valueArr;
- @property (nonatomic, strong) NSMutableArray <YMCaptchaTextCellProperty *> *cellPropertyArr;
- @end
- @implementation YMCaptchaTextView
- - (instancetype)initWithFrame:(CGRect)frame{
- if (self = [super initWithFrame:frame]) {
- [self initDefaultValue];
- [self addNotificationObserver];
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)coder{
- if (self = [super initWithCoder:coder]) {
- [self initDefaultValue];
- [self addNotificationObserver];
- }
-
- return self;
- }
- - (instancetype)init{
- if (self = [super init]) {
- [self initDefaultValue];
- [self addNotificationObserver];
- }
-
- return self;
- }
- - (instancetype _Nullable )initWithCodeLength:(NSInteger)codeLength{
- if (self = [super init]) {
- [self initDefaultValue];
- [self addNotificationObserver];
- self.codeLength = codeLength;
- }
-
- return self;
- }
- - (void)dealloc{
- [self removeNotificationObserver];
- }
- #pragma mark - Notification Observer
- - (void)addNotificationObserver{
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
- }
- - (void)removeNotificationObserver {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- - (void)applicationWillResignActive:(NSNotification *)notification{
- // 触发home按下,光标动画移除
- }
- - (void)applicationDidBecomeActive:(NSNotification *)notification{
- // 重新进来后响应,光标动画重新开始
- [self reloadAllCell];
- }
- #pragma mark - You can inherit
- - (void)initDefaultValue{
- _oldLength = 0;
- self.ifNeedSecurity = NO;
- self.securityDelay = 0.3;
- self.codeLength = 4;
- self.ifNeedCursor = YES;
- self.keyBoardType = UIKeyboardTypeNumberPad;
- self.textType = YMCaptchaTextType_Number;
- self.customInputRegex = @"";
- self.backgroundColor = [UIColor clearColor];
- _valueArr = [NSMutableArray new];
- _ifNeedBeginEdit = NO;
- }
- #pragma mark - LoadAndPrepareView
- - (void)loadAndPrepareView{
- [self loadAndPrepareViewWithBeginEdit:YES];
- }
- - (void)loadAndPrepareViewWithBeginEdit:(BOOL)beginEdit{
- if (_codeLength<=0) {
- NSAssert(NO, @"请输入大于0的验证码位数");
- return;
- }
-
- [self generateCellPropertyArr];
-
- // mainCollectionView
- if (!self.mainCollectionView || ![self.subviews containsObject:self.mainCollectionView]) {
- [self addSubview:self.mainCollectionView];
- [self.mainCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.edges.mas_equalTo(UIEdgeInsetsZero);
- }];
- }
-
- // textField
- if (!self.textField || ![self.subviews containsObject:self.textField]) {
- [self addSubview:self.textField];
- [self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
- make.width.height.mas_equalTo(0);
- make.left.top.mas_equalTo(0);
- }];
- }
-
- // tap
- if (self.tap.view != self) {
- [self addGestureRecognizer:self.tap];
- }
-
- if (![self.textField.text isEqualToString:self.customCellProperty.originValue]) {
- self.textField.text = self.customCellProperty.originValue;
- [self textDidChange:self.textField];
- }
-
- if (beginEdit) {
- [self beginEdit];
- }
- }
- - (void)generateCellPropertyArr{
- [self.cellPropertyArr removeAllObjects];
- for (int i = 0; i < self.codeLength; i++) {
- [self.cellPropertyArr addObject:[self.customCellProperty copy]];
- }
- }
- #pragma mark - code Length 调整
- - (void)resetCodeLength:(NSInteger)codeLength beginEdit:(BOOL)beginEdit{
- if (codeLength <= 0) {
- NSAssert(NO, @"请输入大于0的验证码位数");
- return;
- }
-
- self.codeLength = codeLength;
- [self generateCellPropertyArr];
- [self clearAllWithBeginEdit:beginEdit];
- }
- #pragma mark - Reload Input View
- - (void)reloadInputString:(NSString *_Nullable)value{
- if (![self.textField.text isEqualToString:value]) {
- self.textField.text = value;
- [self baseTextDidChange:self.textField manualInvoke:YES];
- }
- }
- #pragma mark - UITextFieldDelegate
- - (void)textFieldDidBeginEditing:(UITextField *)textField{
- _ifNeedBeginEdit = YES;
-
- if (self.ifClearAllInBeginEditing && self.textValue.length == self.codeLength) {
- [self clearAll];
- }
-
- if (self.textEditStatusChangeblock) {
- self.textEditStatusChangeblock(YMCaptchaTextEditStatus_BeginEdit);
- }
-
- [self reloadAllCell];
- }
- - (void)textFieldDidEndEditing:(UITextField *)textField{
- _ifNeedBeginEdit = NO;
-
- if (self.textEditStatusChangeblock) {
- self.textEditStatusChangeblock(YMCaptchaTextEditStatus_EndEdit);
- }
-
- [self reloadAllCell];
- }
- #pragma mark - TextViewEdit
- - (void)beginEdit{
- if (![self.textField isFirstResponder]) {
- [self.textField becomeFirstResponder];
- }
- }
- - (void)endEdit{
- if ([self.textField isFirstResponder]) {
- [self.textField resignFirstResponder];
- }
- }
- - (void)clearAll{
- [self clearAllWithBeginEdit:YES];
- }
- - (void)clearAllWithBeginEdit:(BOOL)beginEdit{
- _oldLength = 0;
- [_valueArr removeAllObjects];
- self.textField.text = @"";
- [self allSecurityClose];
- [self reloadAllCell];
- [self triggerBlock];
-
- if (beginEdit) {
- [self beginEdit];
- }
- }
- #pragma mark - UITextFieldDidChange
- - (void)textDidChange:(UITextField *)textField {
- [self baseTextDidChange:textField manualInvoke:NO];
- }
- /**
- * 过滤输入内容
- */
- - (NSString *)filterInputContent:(NSString *)inputStr {
-
- NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:inputStr];
- if (self.textType == YMCaptchaTextType_Number) {
-
- /// 纯数字
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^0-9]" options:0 error:nil];
- [regex replaceMatchesInString:mutableStr options:0 range:NSMakeRange(0, [mutableStr length]) withTemplate:@""];
- } else if (self.textType == YMCaptchaTextType_Normal) {
-
- /// 不处理
- nil;
- } else if (self.textType == YMCaptchaTextType_Regex) {
-
- /// 自定义正则
- if (self.customInputRegex.length > 0) {
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:self.customInputRegex options:0 error:nil];
- [regex replaceMatchesInString:mutableStr options:0 range:NSMakeRange(0, [mutableStr length]) withTemplate:@""];
- }
- }
-
- return [mutableStr copy];
- }
- /**
- * textDidChange基操作
- * manualInvoke:是否为手动调用
- */
- - (void)baseTextDidChange:(UITextField *)textField manualInvoke:(BOOL)manualInvoke {
-
- __weak typeof(self) weakSelf = self;
- NSString *verStr = textField.text;
-
- //有空格去掉空格
- verStr = [verStr stringByReplacingOccurrencesOfString:@" " withString:@""];
- verStr = [self filterInputContent:verStr];
-
- //自定义处理
- if (self.textCustomProcessblock) {
- verStr = self.textCustomProcessblock(verStr);
- }
-
- if (verStr.length >= _codeLength) {
- verStr = [verStr substringToIndex:_codeLength];
- [self endEdit];
- }
- textField.text = verStr;
-
- // 判断删除/增加
- YMCaptchaTextChangeType captchaTextChangeType = YMCaptchaTextChangeType_NoChange;
- if (verStr.length > _oldLength) {
- captchaTextChangeType = YMCaptchaTextChangeType_Insert;
- }else if (verStr.length < _oldLength){
- captchaTextChangeType = YMCaptchaTextChangeType_Delete;
- }
-
- // _valueArr
- if (captchaTextChangeType == YMCaptchaTextChangeType_Delete) {
- [self setSecurityShow:NO index:_valueArr.count-1];
- [_valueArr removeLastObject];
-
- }else if (captchaTextChangeType == YMCaptchaTextChangeType_Insert){
- if (verStr.length > 0) {
- if (_valueArr.count > 0) {
- [self replaceValueArrToAsteriskWithIndex:_valueArr.count - 1 needEqualToCount:NO];
- }
- // NSString *subStr = [verStr substringWithRange:NSMakeRange(verStr.length - 1, 1)];
- // [strongSelf.valueArr addObject:subStr];
- [_valueArr removeAllObjects];
-
- [verStr enumerateSubstringsInRange:NSMakeRange(0, verStr.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [strongSelf.valueArr addObject:substring];
- }];
-
- if (self.ifNeedSecurity) {
- if (manualInvoke) {
- // 处理所有秘文
- [self delaySecurityProcessAll];
- }else {
- // 只处理最后一个秘文
- [self delaySecurityProcessLastOne];
- }
- }
- }
- }
- [self reloadAllCell];
-
- _oldLength = verStr.length;
-
- if (captchaTextChangeType != YMCaptchaTextChangeType_NoChange) {
- [self triggerBlock];
- }
- }
- #pragma mark - Control security show
- - (void)setSecurityShow:(BOOL)isShow index:(NSInteger)index{
- if (index < 0) {
- NSAssert(NO, @"index必须大于等于0");
- return;
- }
-
- YMCaptchaTextCellProperty *cellProperty = self.cellPropertyArr[index];
- cellProperty.ifShowSecurity = isShow;
- }
- - (void)allSecurityClose{
- [self.cellPropertyArr enumerateObjectsUsingBlock:^(YMCaptchaTextCellProperty * _Nonnull cellProperty, NSUInteger idx, BOOL * _Nonnull stop) {
- if (cellProperty.ifShowSecurity == YES) {
- cellProperty.ifShowSecurity = NO;
- }
- }];
- }
- - (void)allSecurityOpen{
- [self.cellPropertyArr enumerateObjectsUsingBlock:^(YMCaptchaTextCellProperty * _Nonnull cellProperty, NSUInteger idx, BOOL * _Nonnull stop) {
- if (cellProperty.ifShowSecurity == NO) {
- cellProperty.ifShowSecurity = YES;
- }
- }];
- }
- #pragma mark - Trigger block
- - (void)triggerBlock{
- if (self.textDidChangeblock) {
- BOOL isFinished = _valueArr.count == _codeLength ? YES : NO;
- self.textDidChangeblock(_textField.text, isFinished);
- }
- }
- #pragma mark - Asterisk 替换密文
- /**
- * 替换密文
- * needEqualToCount:是否只替换最后一个
- */
- - (void)replaceValueArrToAsteriskWithIndex:(NSInteger)index needEqualToCount:(BOOL)needEqualToCount{
- if (!self.ifNeedSecurity) {
- return;
- }
-
- if (needEqualToCount && index != _valueArr.count - 1) {
- return;
- }
-
- [self setSecurityShow:YES index:index];
- }
- #pragma mark 延时替换最后一个密文
- - (void)delaySecurityProcessLastOne{
- __weak __typeof(self)weakSelf = self;
- [self delayAfter:self.securityDelay dealBlock:^{
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- if (strongSelf.valueArr.count > 0) {
- [strongSelf replaceValueArrToAsteriskWithIndex:strongSelf.valueArr.count-1 needEqualToCount:YES];
- dispatch_async(dispatch_get_main_queue(), ^{
- [strongSelf reloadAllCell];
- });
- }
- }];
- }
- #pragma mark 延时替换所有一个密文
- - (void)delaySecurityProcessAll{
- __weak __typeof(self)weakSelf = self;
- [self.valueArr enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- [strongSelf replaceValueArrToAsteriskWithIndex:idx needEqualToCount:NO];
- }];
-
- [self reloadAllCell];
- }
- #pragma mark - DelayBlock
- - (void)delayAfter:(CGFloat)delayTime dealBlock:(void (^)(void))dealBlock{
- dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime *NSEC_PER_SEC));
- dispatch_after(timer, dispatch_get_main_queue(), ^{
- if (dealBlock) {
- dealBlock();
- }
- });
- }
- #pragma mark - UICollectionViewDataSource
- - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
- return _codeLength;
- }
- - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
- id tempCell = [self customCollectionView:collectionView cellForItemAtIndexPath:indexPath];
-
- if ([tempCell isKindOfClass:[YMCaptchaTextCell class]]) {
-
- YMCaptchaTextCell *cell = (YMCaptchaTextCell *)tempCell;
- cell.ifNeedCursor = self.ifNeedCursor;
-
- // CellProperty
- YMCaptchaTextCellProperty *cellProperty = self.cellPropertyArr[indexPath.row];
- cellProperty.index = indexPath.row;
-
- NSString *currentPlaceholderStr = nil;
- if (_placeholderText.length > indexPath.row) {
- currentPlaceholderStr = [_placeholderText substringWithRange:NSMakeRange(indexPath.row, 1)];
- cellProperty.cellPlaceholderText = currentPlaceholderStr;
- }
-
- // setOriginValue
- NSUInteger focusIndex = _valueArr.count;
- if (_valueArr.count > 0 && indexPath.row <= focusIndex - 1) {
- [cellProperty setMyOriginValue:_valueArr[indexPath.row]];
- }else{
- [cellProperty setMyOriginValue:@""];
- }
-
- cell.captchaTextCellProperty = cellProperty;
-
- if (_ifNeedBeginEdit) {
- cell.selected = indexPath.row == focusIndex ? YES : NO;
- }else{
- cell.selected = NO;
- }
- }
-
- return tempCell;
- }
- - (void)reloadAllCell{
- [self.mainCollectionView reloadData];
- NSUInteger focusIndex = _valueArr.count;
- /// 最后一个
- if (focusIndex == self.codeLength) {
- [self.mainCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:focusIndex - 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:YES];
- } else {
- [self.mainCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:focusIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
- }
- }
- #pragma mark - Qiuck set
- - (void)quickSetSecuritySymbol:(NSString *)securitySymbol{
- if (securitySymbol.length != 1) {
- securitySymbol = @"✱";
- }
-
- self.customCellProperty.securitySymbol = securitySymbol;
- }
- #pragma mark - You can rewrite
- - (UICollectionViewCell *)customCollectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- YMCaptchaTextCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:YMCaptchaTextCellID forIndexPath:indexPath];
- return cell;
- }
- #pragma mark - Setter & Getter
- - (UITapGestureRecognizer *)tap{
- if (!_tap) {
- _tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(beginEdit)];
- }
-
- return _tap;
- }
- - (YMCaptchaTextFlowLayout *)captchaTextFlowLayout{
- if (!_captchaTextFlowLayout) {
- _captchaTextFlowLayout = [YMCaptchaTextFlowLayout new];
- _captchaTextFlowLayout.itemSize = CGSizeMake(42, 47);
- }
-
- return _captchaTextFlowLayout;
- }
- - (UICollectionView *)mainCollectionView{
- if (!_mainCollectionView) {
- _mainCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.captchaTextFlowLayout];
- _mainCollectionView.showsHorizontalScrollIndicator = NO;
- _mainCollectionView.backgroundColor = [UIColor clearColor];
- _mainCollectionView.delegate = self;
- _mainCollectionView.dataSource = self;
- _mainCollectionView.layer.masksToBounds = YES;
- _mainCollectionView.clipsToBounds = YES;
- [_mainCollectionView registerClass:[YMCaptchaTextCell class] forCellWithReuseIdentifier:YMCaptchaTextCellID];
- }
-
- return _mainCollectionView;
- }
- - (void)setCodeLength:(NSInteger)codeLength{
- _codeLength = codeLength;
- self.captchaTextFlowLayout.itemNum = codeLength;
- }
- - (void)setKeyBoardType:(UIKeyboardType)keyBoardType{
- _keyBoardType = keyBoardType;
- self.textField.keyboardType = keyBoardType;
- }
- - (YMCaptchaTextField *)textField{
- if (!_textField) {
- _textField = [YMCaptchaTextField new];
- // _textView.alpha = 0.1;
- // _textView.tintColor = [UIColor clearColor];
- // _textView.backgroundColor = [UIColor clearColor];
- // _textView.textColor = [UIColor clearColor];
- _textField.delegate = self;
- [_textField addTarget:self action:@selector(textDidChange:) forControlEvents:UIControlEventEditingChanged];
- }
- return _textField;
- }
- - (void)setTextContentType:(UITextContentType)textContentType{
- _textContentType = textContentType;
-
- _textField.textContentType = textContentType;
- }
- - (YMCaptchaTextCellProperty *)customCellProperty{
- if (!_customCellProperty) {
- _customCellProperty = [YMCaptchaTextCellProperty new];
- }
-
- return _customCellProperty;
- }
- - (NSMutableArray<YMCaptchaTextCellProperty *> *)cellPropertyArr{
- if (!_cellPropertyArr) {
- _cellPropertyArr = [NSMutableArray new];
- }
- return _cellPropertyArr;
- }
- - (NSString *)textValue{
- return _textField.text;
- }
- @synthesize inputAccessoryView = _inputAccessoryView;
- - (void)setInputAccessoryView:(UIView *)inputAccessoryView{
- _inputAccessoryView = inputAccessoryView;
- self.textField.inputAccessoryView = _inputAccessoryView;
- }
- - (UIView *)inputAccessoryView{
- return _inputAccessoryView;
- }
- - (void)setIfNeedSecurity:(BOOL)ifNeedSecurity{
- _ifNeedSecurity = ifNeedSecurity;
-
- if (ifNeedSecurity == YES) {
- [self allSecurityOpen];
- }else{
- [self allSecurityClose];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- [self reloadAllCell];
- });
- }
- @end
|