iOS实录3:扩展UITextView的特性

[这是第三篇]

导语:虽然app大部分时间在展示数据,但是有些场景下,app也是数据输入的入口。此种场景下,UITextField 和UITextView用得就比较多。这里重点说一下TextView。结合之前遇到的需求,定制了一个TextView,增加了一些新特性。

新增的特性

在实际开发中,遇到些需求,UITextView原来的特性不够用,需要自己定制一些特性。我采用的方式是创建一个UITextView子类QSTextView。为QSTextView添加若干特性,这些特性有

1、支持placeholder。这个在业务中出现频率比较高。(好奇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、监听文本变化,更新高度
- (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,   //虚线
    //... 可以继续扩展
};

//在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 *)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;
}
iOS实录3:扩展UITextView的特性_第1张图片
初始态.png
iOS实录3:扩展UITextView的特性_第2张图片
编辑中(未超出限定行数.png
iOS实录3:扩展UITextView的特性_第3张图片
编辑中(超出限定行数).png
  • textView中的链接是可以点击的。

源码直通车:QSUseTextViewDemo

篇外:新增TextView的这些特性,是出于需求,后面如果还有其他需求,继续增加。

你可能感兴趣的:(iOS实录3:扩展UITextView的特性)