史上最好用的UITextview子类

gitDemo地址:https://github.com/laity1991/WKTextView

一.前言
  • 之前做项目封装textView时存在一些bug一直未解决:文本信息在用户粘贴过长超出设定的最大字数时、带emoji时截取显示半个或乱码(因为emoji在iOS中使用的是UTF16也就是占位符是8+8两个字节的,占的长度为2,因此在计算字数时一个表情就占了2)
  • 查了网上的一些封装demo,或是不太全或是存在一些小bug,在此我整理封装了一套WKTextView,方便大家使用和自己备用,主要解决问题:**
    1.添加了占位文本 ,类似于textField的placeholder  
    2.中,英文字符输入时限制。
    3.带emoji时截取显示半个或乱码字符处理。
    4.处理了用户在粘贴过来文本超出字数限制存在bug的情况**
  • ** 这里用到了我写的UIView的一个分类UIView+WKCategory (重写frame setter、getter方法),注释比较详细,不做过多赘述**
二 .WKTextView源码
  • *** WKTextView.h ***

    #import 
    @interface WKTextView : UITextView
    //文字
    @property(nonatomic,copy) NSString *myPlaceholder;
    //文字颜色
    @property(nonatomic,strong) UIColor *myPlaceholderColor;
    //最多输入字数
    @property (nonatomic, assign) NSInteger maxNum;
    ///右下角统计字数label
    @property (nonatomic, strong) UILabel *countLabel;
    @end
    
  • *** WKTextView.m ***
    #import "WKTextView.h"
    #import "UIView+WKCategory.h"
    @interface WKTextView()
    @property (nonatomic,weak) UILabel *placeholderLabel;
    @end

    @implementation WKTextView
    
     - (instancetype)initWithFrame:(CGRect)frame{
      self = [super initWithFrame:frame];
      if (self) {
      UILabel *placeholderLabel = [[UILabel alloc]init];
      [self addSubview:placeholderLabel];
      placeholderLabel.textColor = [UIColor lightGrayColor];
      self.placeholderLabel= placeholderLabel;
      UILabel *countLabel = [[UILabel alloc]init];
      [self addSubview:countLabel];
      countLabel.frame = CGRectMake(self.width - 60, self.height - 40, 60, 40);
      countLabel.textAlignment = NSTextAlignmentCenter;
      countLabel.textColor = [UIColor lightGrayColor];
      countLabel.text = @"0/100";
      self.countLabel = countLabel;
      ///设置边框
      self.layer.masksToBounds = YES;
      self.layer.cornerRadius = 4;
      self.layer.borderColor = [UIColor grayColor].CGColor;
      self.layer.borderWidth = 1;
      
      self.delegate = self;
      return self;
    }
    - (void)layoutSubviews{
        [super layoutSubviews];
        self.placeholderLabel.x = 8;
        self.placeholderLabel.y = 8;
        self.placeholderLabel.width = self.width - 
        2*self.placeholderLabel.x;
        ///根据占位文字myPlaceholder 算出占位Label的高度(宽度已定 高速自适应)
        CGSize maxSize = CGSizeMake(self.placeholderLabel.width, MAXFLOAT);
        self.placeholderLabel.height = [self.myPlaceholder boundingRectWithSize:maxSize 
        options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.placeholderLabel.font} context:nil].size.height;
    }
    
    ///箭头textView的输入文字变化 以此控制占位label的显示和隐藏
    - (void)textDidChange{
       //hasText是UITextView的属性 如果textView输入了文字就是
       YES 没有文字就是NO
        self.placeholderLabel.hidden = self.hasText;
    
    }
    
    #pragma mark - UITextViewDelegate
    
    - (BOOL)textView:(UITextView *)textView 
       shouldChangeTextInRange:(NSRange)range 
       replacementText:(NSString *)text{
       if ([text isEqualToString:@"\n"]) {
      [self resignFirstResponder];
      return NO;
     }
    UITextRange *selectedRange = [textView markedTextRange];
    //获取高亮部分
     UITextPosition *pos = [textView 
     positionFromPosition:selectedRange.start offset:0];
     //获取高亮部分内容
     //NSString * selectedtext = [textView 
     textInRange:selectedRange];
    
    //如果有高亮且当前字数开始位置小于最大限制时允许输入
      if (selectedRange && pos) {
      NSInteger startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.start];
      NSInteger endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selectedRange.end];
      NSRange offsetRange = NSMakeRange(startOffset, endOffset - startOffset);
      
      if (offsetRange.location < self.maxNum) {
          return YES;
      }
      else
      {
          return NO;
      }
     }
       NSString *comcatstr = [textView.text stringByReplacingCharactersInRange:range withString:text];
    
       NSInteger caninputlen = self.maxNum - comcatstr.length;
    
       if (caninputlen >= 0)
       {
      return YES;
        }
        else
        {
      NSInteger len = text.length + caninputlen;
      //防止当text.length + caninputlen < 0时,使得rg.length为一个非法最大正数出错
      NSRange rg = {0,MAX(len,0)};
      
      if (rg.length > 0)
      {
          NSString *s = @"";
          //判断是否只普通的字符或asc码(对于中文和表情返回NO)
          BOOL asc = [text canBeConvertedToEncoding:NSASCIIStringEncoding];
          if (asc) {
              s = [text substringWithRange:rg];//因为是ascii码直接取就可以了不会错
                   }
          else
          {
              __block NSInteger idx = 0;
              __block NSString  *trimString = @"";//截取出的字串
              //使用字符串遍历,这个方法能准确知道每个emoji是占一个unicode还是两个
              [text enumerateSubstringsInRange:NSMakeRange(0, [text length])
                                       options:NSStringEnumerationByComposedCharacterSequences
                                    usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {
                                        
                                        if (idx >= rg.length) {
                                            *stop = YES; //取出所需要就break,提高效率
                                            return ;
                                        }
                                        
                                        trimString = [trimString stringByAppendingString:substring];
                                        
                                        idx++;
                                    }];
              
              s = trimString;
          }
          //rang是指从当前光标处进行替换处理(注意如果执行此句后面返回的是YES会触发didchange事件)
          [textView setText:[textView.text stringByReplacingCharactersInRange:range withString:s]];
          //既然是超出部分截取了,哪一定是最大限制了。
          self.countLabel.text = [NSString stringWithFormat:@"%ld/%ld",(long)self.maxNum,(long)self.maxNum];
          
      }
      return NO;
       }
      }
    
      - (void)textViewDidChange:(UITextView *)textView{
        self.placeholderLabel.hidden = self.hasText;
         UITextRange *selectedRange = [textView markedTextRange];
        //获取高亮部分
        UITextPosition *pos = [textView positionFromPosition:selectedRange.start offset:0];
    
       //如果在变化中是高亮部分在变,就不要计算字符了
       if (selectedRange && pos) {
      return;
       }
    
       NSString  *nsTextContent = textView.text;
       NSInteger existTextNum = nsTextContent.length;
    
       if (existTextNum > self.maxNum)
       {
      //截取到最大位置的字符(由于超出截部分在should时被处理了所在这里这了提高效率不再判断)
      NSString *s = [nsTextContent substringToIndex:self.maxNum];
      
      [textView setText:s];
      }
      if (existTextNum > 100) {
       existTextNum = 100;
        }
       //不让显示负数
     self.countLabel.text = [NSString 
     stringWithFormat:@"%ld/%ld",MAX(0, existTextNum),
     (long)self.maxNum
                          ];
    
     }
     #pragma mark - setter
    
    - (void)setMaxNum:(NSInteger)maxNum{
       _maxNum = maxNum;
        self.countLabel.text = [NSString stringWithFormat:@"0/%ld",
       (long)_maxNum];
      }
    
    - (void)setMyPlaceholder:(NSString *)myPlaceholder{
          _myPlaceholder = myPlaceholder;
          self.placeholderLabel.text = _myPlaceholder;
          ///重新计算占位label frame
          [self setNeedsLayout];
        }
    
    - (void)setMyPlaceholderColor:(UIColor *)myPlaceholderColor{
              _myPlaceholderColor = myPlaceholderColor;
              self.countLabel.textColor = myPlaceholderColor;
              self.placeholderLabel.textColor = _myPlaceholderColor;
          }
    
      ///从写TextView setFont方法 使占位labe、TextView、文字统计
        Label Font一致
    - (void)setFont:(UIFont *)font{
              [super setFont:font];
              self.placeholderLabel.font = font;
              self.countLabel.font = font;
              ///重新计算占位label frame
              [self setNeedsLayout];
         }
        @end
    
  • *** Demo ***

       - (void)prepareForUI{
                ///添加到父控件
                WKTextView *textView = [[WKTextView alloc]initWithFrame:CGRectMake(50, 50, self.view.width - 100, 200)];
                ///设置文本输入框的占位字符
                textView.myPlaceholder = @"我是占位字符串...";
                textView.font = [UIFont systemFontOfSize:14];
                textView.maxNum = 100;
                [self.view addSubview:textView];
            }
    
  • 相关属性都暴露了出来,可以自行设置,效果如下:

史上最好用的UITextview子类_第1张图片
WKTextView.gif

你可能感兴趣的:(史上最好用的UITextview子类)