UITextView UITextField 输入字数限制

2017-11-02  本文已影响0人  alpha_feng

UITextView和UITextField没有自带的输入字数限制功能。
如果需要实现这个功能,一般的做法有两种,delegate和监控Notification。

先看看UITextViewDelegate,UITextField也是类似的。

@protocol UITextViewDelegate <NSObject, UIScrollViewDelegate>

@optional

- (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
- (BOOL)textViewShouldEndEditing:(UITextView *)textView;

- (void)textViewDidBeginEditing:(UITextView *)textView;
- (void)textViewDidEndEditing:(UITextView *)textView;

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
- (void)textViewDidChange:(UITextView *)textView;

- (void)textViewDidChangeSelection:(UITextView *)textView;

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithURL:inRange:forInteractionType: instead");
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithTextAttachment:inRange:forInteractionType: instead");

@end

初步来看,会选用以下这两个回调方法做处理。

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
- (void)textViewDidChange:(UITextView *)textView;

选了第一个,以下的代码,在iOS 10上运行,没问题,so easy~~~

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
    if (textView.text.length + text.length > kLimitCount) {
        NSLog(@"%@",[NSString stringWithFormat:@"最高支持输入%d个字",kLimitCount]);
        if (textView.text.length > 0) {
            [self.textViewTipsLabel setHidden:YES];
        } else {
            [self.textViewTipsLabel setHidden:NO];
        }
        return NO;
    }
    return YES;
}

但是too young too sample,在iOS 9上出问题了,如果使用iOS 9输入中文,在选择中文时,不会调用shouldChangeTextInRange的回调。
呵呵~~~ 这个应该是apple的问题吧,有问题就要解决啊,所以同时需要在textViewDidChange中也需要做处理。

- (void)textViewDidChange:(UITextView *)textView {
    if (textView.text.length > kLimitCount) {
        // 超出限制
        textView.text = [textView.text substringToIndex:kLimitCount];
        NSLog(@"%@",[NSString stringWithFormat:@"最高支持输入%d个字",kLimitCount]);
    }
    
    if (textView.text.length > 0) {
        [self.textViewTipsLabel setHidden:YES];
    } else {
        [self.textViewTipsLabel setHidden:NO];
    }
}

textViewTipsLabel是一个提示的label,因为UITextView没有placeholder。

OK,大概的解决方案就完整了,如果工程中大量用到UITextView UITextField 输入字数限制,可以写一个分类实现,(不推荐使用继承的方式)

分类中监控自己的delegate显然不是很好(自己回调自己,然后又要回调出去)。所以考虑用NSNotificationCenter。UITextView相关的Notification,有以下几个,

UIKIT_EXTERN NSNotificationName const UITextViewTextDidBeginEditingNotification;
UIKIT_EXTERN NSNotificationName const UITextViewTextDidChangeNotification;
UIKIT_EXTERN NSNotificationName const UITextViewTextDidEndEditingNotification;

UITextView的分类UITextView+InputLimit只能监控UITextViewTextDidChangeNotification了。
同时分类中增加两个属性(分类是可以增加属性的,具体的方法网上很多)。
inputLimit_placeholderLabel:占位的label,做提示用,可以不显示。
inputLimit_limitCount:现在字符长度,方便使用。
外部类只需要关系这两个属性就好了,在设置属性时或做相应的处理,
以下是部分关键代码:

- (void)setInputLimit_placeholderLabel:(UILabel *)inputLimit_placeholderLabel {
    if (!inputLimit_placeholderLabel && inputLimit_placeholderLabel != self.inputLimit_placeholderLabel) {
        // 移除旧的placeholderLabel
        [self.inputLimit_placeholderLabel removeFromSuperview];
        // 添加新的
        [self addSubview:inputLimit_placeholderLabel];
        objc_setAssociatedObject(self, PlaceholderLabelKey, inputLimit_placeholderLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

- (void)setInputLimit_limitCount:(NSUInteger)inputLimit_limitCount {
    if (inputLimit_limitCount == self.inputLimit_limitCount) {
        return;
    }
    if (self.inputLimit_limitCount == 0 && inputLimit_limitCount > 0) {
        // 从0到不是0,需要注册通知
        [self addInputLimit];
    }
    if (inputLimit_limitCount == 0) {
        // limitCount为0,不需要监控了
        [self removeInputLimit];
    }
    objc_setAssociatedObject(self, LimitCountKey, @(inputLimit_limitCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(void)textViewDidChange:(NSNotification *)notification {
    
#if DEBUG
    NSCAssert(self.inputLimit_limitCount > 0, @"监控了UITextViewTextDidChangeNotification的消息,但是没有设置limitCount的大小");
#endif
    
    UITextView *textView = (UITextView *)notification.object;
    
    // 不是当前textView和没有限制limitCount时,不做处理
    if (textView == self && self.inputLimit_limitCount != 0) {
        if (textView.text.length > self.inputLimit_limitCount) {
            // 超出限制
            textView.text = [textView.text substringToIndex:self.inputLimit_limitCount];
            // 发送消息
            [[NSNotificationCenter defaultCenter] postNotificationName:UITextViewInputLimitBeyondNotification object:self];
            #if DEBUG
            NSLog(@"%@",[NSString stringWithFormat:@"最高支持输入%lu个字",(unsigned long)self.inputLimit_limitCount]);
            #endif
        }
        
        if (textView.text.length > 0) {
            [self.inputLimit_placeholderLabel setHidden:YES];
        } else {
            [self.inputLimit_placeholderLabel setHidden:NO];
        }
    }
    
}

好,到这里,基本已经完事了。这里有一点一开始很困惑就是分类中在设置字符长度注册(addObserver)了NSNotificationCenter,必须有个地方需要移除(removeObserver)。如果不移除,UITextView释放时候,NSNotificationCenter发送通知会不会造成崩溃。
有验证过在ARC的环境下(现在都是ARC的代码了吧。。。)不需要移除。原因是UITextView本身的dealloc,
会调用removeObserver的方法。具体的验证这里不细讲了。
如果要移除可以考虑重写分类中的dealloc,这样比较麻烦需要在load函数中把方法替换。

上一篇下一篇

猜你喜欢

热点阅读