[这是第三篇]
导语:虽然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;
}
- textView中的链接是可以点击的。
源码直通车:QSUseTextViewDemo
篇外:新增TextView的这些特性,是出于需求,后面如果还有其他需求,继续增加。