[這是第三篇]
導語:雖然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ù)增加。