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