在网上找了很多有关计算文本文字和文本高度的方法,但是显示在textview上时,计算的文本每一行的文字和实际显示的不一样,在此记录一下这几天踩的坑。主要还是在textview的设置上有一些影响因素
在这儿也分享一篇对我有帮助的文章 UITextView输入时高度自适应&文本边距的设置
先看任务
/**
* 目的:给文本添加更多,点击更多,显示更多内容
*
* 操作:一段文本,首次显示,最多出现3行。若超过3行,则出现更多按钮,点击更多,显示全部内容。
* 如果内容超过10行,则显示10行,其余内容"..."处理。显示更多内容后,“更多”按钮变为“收起”!
*
* 思考:
* 1、如果小于3行,直接显示内容
* 2、如果大于3行,显示3行,则需要显示“更多按钮”
* 3、如果大于3行,小于10行,显示最多10行,直接添加“收起”按钮即可
* 4、如果大于10行,显示最多10行,需要添加“...”和“收起”按钮
*/
效果
更多.png 收起.png
相关控件
#import "ViewController.h"
#import <CoreText/CoreText.h>
@interface ViewController ()<UITextViewDelegate>
@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, copy) NSString *topicString;
@property (nonatomic, copy) NSString *textString;
@property (nonatomic, assign) BOOL showMore;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor blackColor];
CGFloat screenSizeH = [UIScreen mainScreen].bounds.size.height;
CGFloat screenSizeW = [UIScreen mainScreen].bounds.size.width;
self.textView.frame = CGRectMake(15, screenSizeH-300, screenSizeW-110, 250);
[self.view addSubview:self.textView];
[self showTestText:_showMore];
}
关键方法
/**
* 目的:给文本添加更多,点击更多,显示更多内容
*
* 操作:一段文本,首次显示,最多出现3行。若超过3行,则出现更多按钮,点击更多,显示全部内容。
* 如果内容超过10行,则显示10行,其余内容"..."处理。显示更多内容后,“更多”按钮变为“收起”!
*
* 思考:
* 1、如果小于3行,直接显示内容
* 2、如果大于3行,显示3行,则需要显示“更多按钮”
* 3、如果大于3行,小于10行,显示最多10行,直接添加“收起”按钮即可
* 4、如果大于10行,显示最多10行,需要添加“...”和“收起”按钮
*/
- (void)showTestText:(BOOL)showMore {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:4]; // 设置行间距
// 需要显示的富文本,用这个文本去计算每行需要显示的内容和需要的frame
NSMutableAttributedString *contentAttributeString = [[NSMutableAttributedString alloc] init];
// 标题富文本
NSMutableAttributedString *topciAttributeString = [[NSMutableAttributedString alloc] initWithString:self.topicString];
[topciAttributeString addAttributes:@{
NSParagraphStyleAttributeName:paragraphStyle,
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightBold],
NSForegroundColorAttributeName:[UIColor orangeColor],
NSLinkAttributeName:@"topic://",
} range:NSMakeRange(0, topciAttributeString.length)];
[contentAttributeString appendAttributedString:topciAttributeString];
// 内容富文本
NSMutableAttributedString *textAttributeString = [[NSMutableAttributedString alloc] initWithString:self.textString];
[textAttributeString addAttributes:@{
NSParagraphStyleAttributeName:paragraphStyle,
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
NSForegroundColorAttributeName:[UIColor grayColor],
} range:NSMakeRange(0, textAttributeString.length)];
[contentAttributeString appendAttributedString:textAttributeString];
// 根据textview的宽度或者指定的宽度计算需要显示的高度
CGSize contentSize = [contentAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
options:NSStringDrawingTruncatesLastVisibleLine
| NSStringDrawingUsesLineFragmentOrigin
| NSStringDrawingUsesFontLeading
context:nil].size;
NSString *originString = [NSString stringWithFormat:@"%@%@",self.topicString,self.textString];
NSString *memoString = [self getSeparatedLinesFromText:originString
maxLines:showMore ? 10 : 3
largeLines:10
attStr:contentAttributeString
rect:CGRectMake(0, 0, contentSize.width, 10000)];
NSMutableAttributedString *memoAttributeString = [[NSMutableAttributedString alloc] initWithString:memoString];
NSRange topicRange = [memoString rangeOfString:self.topicString];
[memoAttributeString addAttributes:@{
NSParagraphStyleAttributeName:paragraphStyle,
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightBold],
NSForegroundColorAttributeName:[UIColor orangeColor],
NSLinkAttributeName:@"topic://",
} range:topicRange];
NSRange textRange = NSMakeRange(topicRange.length, memoString.length-topicRange.length);
[memoAttributeString addAttributes:@{
NSParagraphStyleAttributeName:paragraphStyle,
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
NSForegroundColorAttributeName:[UIColor grayColor],
} range:textRange];
// 然后再 根据textview的宽度或者指定的宽度 重新 计算 计算出的字符 需要显示的高度
contentSize = [memoAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
options:NSStringDrawingTruncatesLastVisibleLine
| NSStringDrawingUsesLineFragmentOrigin
| NSStringDrawingUsesFontLeading
context:nil].size;
if (contentSize.height > 23 * 3 || ![memoString isEqualToString:originString]) {
NSMutableAttributedString *moreAttributeString = [[NSMutableAttributedString alloc] initWithString:showMore ? @"收起" : @"更多"];
[moreAttributeString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, moreAttributeString.length)];
[moreAttributeString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular]
range:NSMakeRange(0, moreAttributeString.length)];
[moreAttributeString addAttribute:NSForegroundColorAttributeName value:[UIColor orangeColor] range:NSMakeRange(0, moreAttributeString.length)];
[moreAttributeString addAttribute:NSLinkAttributeName value:@"more://" range:NSMakeRange(0, moreAttributeString.length)];
[memoAttributeString appendAttributedString:moreAttributeString];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:showMore ? @"ic_select_pre" : @"ic_select_nor"];
textAttachment.bounds = CGRectMake(0, 0, 13, 13);
NSAttributedString *attacAttibuteString = [NSAttributedString attributedStringWithAttachment:textAttachment];
NSMutableAttributedString *attacMutableAttibuteString = [[NSMutableAttributedString alloc] initWithAttributedString:attacAttibuteString];
[attacMutableAttibuteString addAttribute:NSLinkAttributeName value:@"more://" range:NSMakeRange(0, attacMutableAttibuteString.length)];
[memoAttributeString appendAttributedString:attacMutableAttibuteString];
contentSize = [memoAttributeString boundingRectWithSize:CGSizeMake(self.textView.bounds.size.width, 1000)
options:NSStringDrawingTruncatesLastVisibleLine
| NSStringDrawingUsesLineFragmentOrigin
| NSStringDrawingUsesFontLeading
context:nil].size;
}
CGFloat textViewHeight = 69; // 显示文字3行69够了
if (contentSize.height > 40) {
textViewHeight = (!showMore ? MAX(contentSize.height, 69) : 69) + (self.textView.bounds.size.width-contentSize.width) ; // 但是如果是3行英文可能还不够,需要去一个最大值
}
self.textView.textContainer.maximumNumberOfLines = 3;
if (showMore && contentSize.height > textViewHeight) {
textViewHeight = contentSize.height + (self.textView.bounds.size.width-contentSize.width) + 8;
if (contentSize.height > 23*10) {
textViewHeight = 230 + (self.textView.bounds.size.width-contentSize.width);
}
self.textView.textContainer.maximumNumberOfLines = 10;
}
self.textView.attributedText = memoAttributeString;
self.textView.frame = CGRectMake(15, self.textView.frame.origin.y, self.textView.bounds.size.width, textViewHeight);
self.showMore = !_showMore;
}
每行显示的文本
/// @param text : 原文本内容
/// @param maxLines : 最多显示多少行
/// @param largeLines : 全部内容显示多少行
/// @param attStr : 需要显示的富文本
/// @param rect : 显示的区域
/// @return 用于显示的文本
- (NSString *)getSeparatedLinesFromText:(NSString *)text maxLines:(NSInteger)maxLines largeLines:(NSInteger)largeLines attStr:(NSMutableAttributedString *)attStr rect:(CGRect)rect {
// NSString *text = [textV text];
// UIFont *font = [textV font];
// CGRect rect = [textV bounds];
// CTFontRef myFont = CTFontCreateWithName((__bridge CFStringRef)([font fontName]), [font pointSize], NULL);
//
// /* 在 Core Text 中使用 NSAttributedString 而不是NSString,NSAttributedString 是一个非常强大的 NSString 派生类,
// 它允许你对文本应用格式化属性。 现在我们还没有用到格式化,这里仅仅使用纯文本。 */
// NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
// [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
/* CTFramesetter 是使用 Core Text 绘制时最重要的类。它管理您的字体引用和文本绘制帧。
目前您需要了解 CTFramesetterCreateWithAttributedString 通过应用属性化文本创建 CTFramesetter 。
本节中,在 framesetter 之后通过一个所选的文本范围(这里我们选择整个文本)与需要绘制到的矩形路径创建一个帧。*/
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
/* 这里你需要创建一个用于绘制文本的路径区域。Mac 上的 Core Text 支持矩形图形等不同形状,
但在 iOS 上只支持矩形。在这个示例中,你将通过 self.bounds 使用整个视图矩形区域创建 CGPath 引用。 */
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,MAXFLOAT));
//CTFrameDraw 将 frame 描述到设备上下文。
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
//需要多少行
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
//不需要添加“...” 直接返回
if (lines.count <= maxLines) return text;
if (lines.count <= maxLines && maxLines == largeLines) return text;
//获取到需要添加...的那一行内容
NSInteger indexLine = maxLines - 1;
CTLineRef lineRef = (__bridge CTLineRef )lines[indexLine];
CFRange lineRange = CTLineGetStringRange(lineRef);
NSRange range = NSMakeRange(lineRange.location, lineRange.length);
NSString *lineString = [text substringWithRange:range];
//移除最后几个字节的文本
NSInteger replaceLength = 0;
while (replaceLength < 8) {
NSString *lastChar = [lineString substringWithRange:NSMakeRange(lineString.length-1, 1)];
if ([self isChinese:lastChar]) {
replaceLength += 2;
} else {
replaceLength += 1;
}
lineString = [lineString stringByReplacingCharactersInRange:NSMakeRange(lineString.length-1, 1) withString:@""];
}
lineString = [NSString stringWithFormat:@"%@...",lineString]; // 添加...
NSString *memoStr = @"";
for (int i=0; i < maxLines-1; i++) {
lineRef = (__bridge CTLineRef )lines[i];
lineRange = CTLineGetStringRange(lineRef);
range = NSMakeRange(lineRange.location, lineRange.length);
memoStr = [NSString stringWithFormat:@"%@%@",memoStr,[text substringWithRange:range]];
}
memoStr = [NSString stringWithFormat:@"%@%@",memoStr,lineString];
CFRelease(frame); // Core Frame 下的对象需要自己释放
CFRelease(path);
CFRelease(frameSetter);
return memoStr;
}
判断是否是中文
// 判断是否是中文
- (BOOL)isChinese:(NSString *)c {
int strlength = 0;
char *p = (char *)[c cStringUsingEncoding:NSUnicodeStringEncoding];
for (int i = 0; i < [c lengthOfBytesUsingEncoding:NSUnicodeStringEncoding]; i++) {
if (*p) {
p++;
strlength++;
} else
p++;
}
return (strlength/2 == 1) ? YES : NO;
}
属性设置
#pragma mark - lazy load -
- (UITextView *)textView {
if (!_textView) {
_textView = [UITextView new];
_textView.delegate = self;
_textView.editable = NO; // 禁止可编辑
_textView.selectable = YES; // 允许点击事件
_textView.scrollEnabled = NO; // 禁止滑动,如果允许滑动,那下面设置 contentInset 和 textContainerInset 就多余了
_textView.bounces = NO;
_textView.contentInset = UIEdgeInsetsMake(4, 0, 0, -10); // 设置内容边距 ,设置为-10是为了配合 textContainerInset ,因为有时候因为右边边距不够换行 而造成计算的行数和内容与实际显示的内容不符
_textView.textContainerInset = UIEdgeInsetsMake(4, 0, 0, 0); // 设置文本边距
_textView.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
_textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping; // 设置为 NSLineBreakByWordWrapping 是为了后面在合适的位置添加“更多”文本
[_textView.textContainer setLineFragmentPadding:0.01]; // 设置左边距为0,不然显示跟计算的不正确
// [_textView.layoutManager setAllowsNonContiguousLayout:YES]; // 设置
_textView.linkTextAttributes = @{
NSForegroundColorAttributeName : [UIColor orangeColor],
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
}; // 设置link的属性
}
return _textView;
}
- (NSString *)topicString {
if (!_topicString) {
_topicString = @"#这个是标题#";
}
return _topicString;
}
- (NSString *)textString {
if (!_textString) {
_textString = @"今天是jason的生日,我准备送她一部iPhone手机,顺便吟诗一首:君不见黄河之水天上来,奔流到海不复回。high hope!君不见高堂明镜悲白发,千里冰丝暮成雪。shr!仰天大笑出门去,我辈岂是蓬蒿人。buibuibui!床前明月光,月初钱包光。仰天大笑出门去,我辈岂是蓬蒿人。buibuibui!床前明月光,月初钱包光。";
}
return _textString;
}
代理方法
#pragma mark - UITextViewDelegate -
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
if ([[URL scheme] isEqualToString:@"topic"]) {
NSLog(@"shouldInteractWithURL - topic");
[self showTestText:_showMore];
return NO;
} else if ([[URL scheme] isEqualToString:@"more"]) {
[self showTestText:_showMore];
return NO;
}
return NO;
}
这里面代码片段组合起来就是一个完整的代码,里面的一些数据设置,如果与自己不一样,可以尝试去调整一下。
网友评论