iOS實錄3:擴展UITextView的特性

[這是第三篇]

導語:雖然app大部分時間在展示數據,但是有些場景下,app也是數據輸入的入口。此種場景下,UITextField 和UITextView用得就比較多。這里重點說一下TextView。結合之前遇到的需求,定制了一個TextView,增加了一些新特性。

新增的特性

在實際開發(fā)中,遇到些需求,UITextView原來的特性不夠用,需要自己定制一些特性。我采用的方式是創(chuàng)建一個UITextView子類QSTextView。為QSTextView添加若干特性,這些特性有

1、支持placeholder。這個在業(yè)務中出現頻率比較高。(好奇iOS默認的UITextView不支持這個小特性)。
2、輸入時自適應高度變化,高度增長方向可控制。這個在編輯大段文本情況下遇到比較多。
3、設置邊框:無(默認)、虛線、實線。
4、輸入文本時候,自動識別url,并可以點擊打開url。

一、支持placeholder的實現

1、在QSTextView中定義了一個placeholderView
- (UITextView *)placeholderView{

    if (!_placeholderView) {
        _placeholderView = [[UITextView alloc] init];
        _placeholderView.scrollEnabled = NO;
        _placeholderView.showsHorizontalScrollIndicator = NO;
        _placeholderView.showsVerticalScrollIndicator = NO;
        _placeholderView.userInteractionEnabled = NO;
        _placeholderView.font = self.font;
        _placeholderView.textColor = [UIColor lightGrayColor];
        _placeholderView.backgroundColor = [UIColor clearColor];
        [self addSubview:_placeholderView];
  }
  return _placeholderView;

}

2、控制placeholderView的顯隱和frame
//實現和隱藏
self.placeholderView.hidden = self.text.length > 0;

//frame變化
- (void)adjustPlaceholderFrame{

    CGFloat height = ceil(self.placeholderFont.lineHeight + self.textContainerInset.top + self.textContainerInset.bottom);
    self.placeholderView.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.frame.size.width, height);
}
3、我們也支持設置placeholde的文本、顏色和字體
#pragma mark - 占位placeholder相關的屬性
/**
 *  占位文字
 */
@property (nonatomic, strong) NSString *placeholder;

 /**
 *  占位文字顏色
 */
@property (nonatomic, strong) UIColor *placeholderColor;

 /**
 占位文字字體
 */
@property (nonatomic, strong) UIFont *placeholderFont;

二、輸入時自適應高度變化,高度增長方向可控制

1、添加高度相關的屬性
/**
 QSTextView的增長方向
 */
typedef NS_ENUM(NSInteger,QSTextViewGrowDirection){
    QSTextViewGrowDirectionDown = 0, //向下,默認
    QSTextViewGrowDirectionUp = 1,   //向上
};

#pragma mark - 高度相關的屬性

/**
 增長方向
 */
@property (nonatomic,assign)QSTextViewGrowDirection growDirection;

/**
 *  textView最大行數
 */
@property (nonatomic, assign) NSUInteger maxNumberOfLines;

/**
 *  textView的高度
 */
@property (nonatomic, assign,readonly) NSInteger textViewHeight;
2、監(jiān)聽文本變化,更新高度
- (void)textDidChange{

    // 占位文字是否顯示
    self.placeholderView.hidden = self.text.length > 0;
    if (self.placeholderView.hidden != YES) {
        [self adjustPlaceholderFrame];
    }

    //獲取輸入文本
    if (CHECK_VALID_DELEGATE(self.qsDelegate, @selector(textView:textChange:))) {
        [self.qsDelegate textView:self textChange:self.text];
    }

    //計算當前view的高度
    NSInteger calcHeight = ceilf([self sizeThatFits:CGSizeMake(self.bounds.size.width, MAXFLOAT)].height);

    if (_textViewHeight != calcHeight && calcHeight >= _originHeight) { // 高度不一樣,就改變了高度
    
        //更新高度
        _textViewHeight = calcHeight;
    
        if (calcHeight > _maxTextViewHeight && _maxTextViewHeight > 0) {
            self.scrollEnabled = YES;  //計算高度大于最大高度,才可以滾動
        }else{
            self.scrollEnabled = NO;   //不需要滾動
            [self p_adjustFrameWithHeight:calcHeight];
        }

        if (CHECK_VALID_DELEGATE(self.qsDelegate, @selector(textView:textViewHeightChange:))) {
        
            [self.qsDelegate textView:self textViewHeightChange:calcHeight];
        }
    }
}

//根據高度增長方向控制frame
- (void)p_adjustFrameWithHeight:(CGFloat)height{

    CGRect originFrame = self.frame;
    originFrame.size = CGSizeMake(originFrame.size.width, height);

    if (self.growDirection == QSTextViewGrowDirectionUp) {
        //修改y
        originFrame.origin = CGPointMake(originFrame.origin.x, CGRectGetMaxY(self.frame) - height);
    }

    self.frame = originFrame;
    [self.superview layoutIfNeeded];
}

三、設置邊框:無(默認)、虛線、實線

思路:定義一個CAShapeLayer實例borderLayer,添加到TextView上去,重寫layoutSubviews,根據邊框樣式修改borderLayer的屬性,充分利用貝塞爾曲線來為borderLayer繪制線條,如:(帶圓角的)虛線、(帶圓角的)實線等,當然我們也支持設置邊框的顏色(borderColor)和寬度(borderWidth),圓角(cornerRadius)

//邊框的枚舉類型
 typedef NS_ENUM(NSInteger,QSTextViewBorderLineStyle) {

    QSTextViewBorderLineStyleNone = 0,   //無邊框線
    QSTextViewBorderLineStyleSolid = 1, //實線
    QSTextViewBorderLineStyleDash = 2,   //虛線
    //... 可以繼續(xù)擴展
};

//在layoutSubviews方法中添加邊框的特性
- (void)layoutSubviews{

    self.borderLayer.hidden = (self.borderLineStyle == QSTextViewBorderLineStyleNone ? YES : NO);

    if (!self.borderLayer.hidden) {
    
        self.borderLayer.strokeColor = _borderColor.CGColor ? _borderColor.CGColor : [UIColor blackColor].CGColor;
        self.borderLayer.lineWidth = _borderWidth > 0 ? _borderWidth : 1;
    
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:_cornerRadius];
        self.borderLayer.path = path.CGPath;
    }

    if (self.borderLineStyle == QSTextViewBorderLineStyleDash) {
    
        self.borderLayer.lineDashPattern = @[@5, @5];
    
    }else if(self.borderLineStyle == QSTextViewBorderLineStyleSolid){
    
        self.borderLayer.lineDashPattern = nil;
    }else{
        //定制其他邊框線
    }
   [super layoutSubviews];
}

四、輸入文本時候,自動識別url,并可以點擊打開url

思路:利用NSDataDetector來實現URL的識別工作,實現touchesBegan方法來獲取點擊文本位置,從而獲取點擊的urlString。

/**
 識別URL
 */
- (void) detectorUrlPattern {

    NSDataDetector *dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];
    NSArray *resultArray = [dataDetector matchesInString:self.textStorage.string options:NSMatchingReportProgress range:NSMakeRange(0, self.textStorage.string.length)];

    //清除
    [self.urlModels removeAllObjects];
    self.textColor = _originTextColor;

    for (NSTextCheckingResult *result in resultArray) {
    
        NSString *urlString = [self.textStorage.string substringWithRange:result.range];
        if ([urlString hasPrefix:@"http://"] || [urlString hasPrefix:@"https://"]) {
        
            QSTextViewUrlModel *urlModel = [[QSTextViewUrlModel alloc]initWithUrlString:urlString urlRange:result.range];
            [self.urlModels addObject:urlModel];
        
            NSMutableAttributedString *urlAttributedString = [[NSMutableAttributedString alloc]initWithString:urlString];
            [urlAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, urlString.length)];
            [urlAttributedString addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, urlString.length)];
            [self.textStorage replaceCharactersInRange:result.range withAttributedString:urlAttributedString];
        }
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{

    CGPoint point = [[touches anyObject] locationInView:self];
    [self.urlModels enumerateObjectsUsingBlock:^(QSTextViewUrlModel *urlModel, NSUInteger idx, BOOL * _Nonnull stop) {
    
        self.selectedRange = urlModel.urlRange;
        NSArray *selectionRects = [self selectionRectsForRange:self.selectedTextRange];
        for (UITextSelectionRect *textSelectionRect in selectionRects) {
            if (CGRectContainsPoint(textSelectionRect.rect, point)) {
                NSLog(@"click address%@",urlModel.urlString);
                if (CHECK_VALID_DELEGATE(self.qsDelegate, @selector(textView:openClickUrl:))) {
                    [self.qsDelegate textView:self openClickUrl:[NSURL URLWithString:urlModel.urlString]];
                }
                *stop = YES;
                break;
            }
        }
    }];
}

五、使用實例

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //....
    [self.view addSubview:self.textView];
     //....
}

- (QSTextView *)textView{

    if (!_textView) {
    
        _textView = [[QSTextView alloc]initWithFrame:CGRectMake(15, 150, SCREEN_WIDTH - 30, 36)];
        _textView.font = [UIFont systemFontOfSize:16];
        _textView.maxNumberOfLines = 2;
        _textView.backgroundColor = [UIColor clearColor];
        _textView.textColor = [UIColor blackColor];

        //占位文本
        _textView.placeholder = @"別憋著,說兩句吧。";
        _textView.placeholderFont = [UIFont systemFontOfSize:15];
        _textView.placeholderColor = [UIColor greenColor];
    
        //高度增長方向
        _textView.growDirection = QSTextViewGrowDirectionUp;
    
        //邊框
        _textView.borderLineStyle = QSTextViewBorderLineStyleDash;
        _textView.borderColor = [UIColor redColor];
        _textView.borderWidth = 2.0f;
        _textView.cornerRadius = 2.0f;
    
        //支持識別和點擊url
        _textView.canDetectUrl = YES;
    
        //代理
        _textView.qsDelegate = self;
    }
    return _textView;
}
初始態(tài).png
編輯中(未超出限定行數.png
編輯中(超出限定行數).png
  • textView中的鏈接是可以點擊的。

源碼直通車:QSUseTextViewDemo

篇外:新增TextView的這些特性,是出于需求,后面如果還有其他需求,繼續(xù)增加。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發(fā)現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,180評論 4 61
  • 不早不晚的下班時間,回到家,泡了面吃,洗了衣服。 陽臺沙發(fā),一盞燭光,音樂,和放空的心情。 晚上,《最后一公里》推...
    清楚明白閱讀 247評論 0 0
  • 親愛的,我就快到了 不知道從何時起,關于火車,我最多的去向就是你的所在地,合肥,上海,南京。不管幾點的我都坐過,因...
    庸者的救贖閱讀 739評論 8 5
  • 開學前跟閨蜜聚會,她跟我說她好慌,我問她慌什么,她說她今天去學吉他,然后她的老師問她什么時候分手啊,大學異地一...
    鬼梔閱讀 502評論 0 1
  • 二年級的獎懲制度搞不起來,和小朋友商量,說取消這個制度,大家表示同意。要想一個新的制度真的好難哦。 今天給一年級的...
    栗子嘻嘻閱讀 114評論 0 0