史上最好用的UITextview子類

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

一.前言
  • 之前做項目封裝textView時存在一些bug一直未解決:文本信息在用戶粘貼過長超出設(shè)定的最大字數(shù)時、帶emoji時截取顯示半個或亂碼(因為emoji在iOS中使用的是UTF16也就是占位符是8+8兩個字節(jié)的,占的長度為2,因此在計算字數(shù)時一個表情就占了2)
  • 查了網(wǎng)上的一些封裝demo,或是不太全或是存在一些小bug,在此我整理封裝了一套WKTextView,方便大家使用和自己備用,主要解決問題:**
    1.添加了占位文本 ,類似于textField的placeholder  
    2.中,英文字符輸入時限制。
    3.帶emoji時截取顯示半個或亂碼字符處理。
    4.處理了用戶在粘貼過來文本超出字數(shù)限制存在bug的情況**
  • ** 這里用到了我寫的UIView的一個分類UIView+WKCategory (重寫frame setter、getter方法),注釋比較詳細,不做過多贅述**
二 .WKTextView源碼
  • *** WKTextView.h ***

    #import <UIKit/UIKit.h>
    @interface WKTextView : UITextView
    //文字
    @property(nonatomic,copy) NSString *myPlaceholder;
    //文字顏色
    @property(nonatomic,strong) UIColor *myPlaceholderColor;
    //最多輸入字數(shù)
    @property (nonatomic, assign) NSInteger maxNum;
    ///右下角統(tǒng)計字數(shù)label
    @property (nonatomic, strong) UILabel *countLabel;
    @end
    
  • *** WKTextView.m ***
    #import "WKTextView.h"
    #import "UIView+WKCategory.h"
    @interface WKTextView()<UITextViewDelegate>
    @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;
      ///設(shè)置邊框
      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;
        ///根據(jù)占位文字myPlaceholder 算出占位Label的高度(寬度已定 高速自適應(yīng))
        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];
     //獲取高亮部分內(nèi)容
     //NSString * selectedtext = [textView 
     textInRange:selectedRange];
    
    //如果有高亮且當前字數(shù)開始位置小于最大限制時允許輸入
      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為一個非法最大正數(shù)出錯
      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是指從當前光標處進行替換處理(注意如果執(zhí)行此句后面返回的是YES會觸發(fā)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;
        }
       //不讓顯示負數(shù)
     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、文字統(tǒng)計
        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)];
                ///設(shè)置文本輸入框的占位字符
                textView.myPlaceholder = @"我是占位字符串...";
                textView.font = [UIFont systemFontOfSize:14];
                textView.maxNum = 100;
                [self.view addSubview:textView];
            }
    
  • 相關(guān)屬性都暴露了出來,可以自行設(shè)置,效果如下:

WKTextView.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內(nèi)容