美文网首页
UITextView UITextField 输入字数限制

UITextView UITextField 输入字数限制

作者: alpha_feng | 来源:发表于2017-11-02 18:00 被阅读0次

    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函数中把方法替换。

    相关文章

      网友评论

          本文标题:UITextView UITextField 输入字数限制

          本文链接:https://www.haomeiwen.com/subject/itabpxtx.html