在網(wǎng)上找了很多有關(guān)計算文本文字和文本高度的方法,但是顯示在textview上時,計算的文本每一行的文字和實際顯示的不一樣,在此記錄一下這幾天踩的坑。主要還是在textview的設(shè)置上有一些影響因素
在這兒也分享一篇對我有幫助的文章 UITextView輸入時高度自適應(yīng)&文本邊距的設(shè)置
先看任務(wù)
/**
* 目的:給文本添加更多,點擊更多,顯示更多內(nèi)容
*
* 操作:一段文本,首次顯示,最多出現(xiàn)3行。若超過3行,則出現(xiàn)更多按鈕,點擊更多,顯示全部內(nèi)容。
* 如果內(nèi)容超過10行,則顯示10行,其余內(nèi)容"..."處理。顯示更多內(nèi)容后,“更多”按鈕變?yōu)椤笆掌稹保? *
* 思考:
* 1、如果小于3行,直接顯示內(nèi)容
* 2、如果大于3行,顯示3行,則需要顯示“更多按鈕”
* 3、如果大于3行,小于10行,顯示最多10行,直接添加“收起”按鈕即可
* 4、如果大于10行,顯示最多10行,需要添加“...”和“收起”按鈕
*/
效果

更多.png

收起.png
相關(guān)控件
#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];
}
關(guān)鍵方法
/**
* 目的:給文本添加更多,點擊更多,顯示更多內(nèi)容
*
* 操作:一段文本,首次顯示,最多出現(xiàn)3行。若超過3行,則出現(xiàn)更多按鈕,點擊更多,顯示全部內(nèi)容。
* 如果內(nèi)容超過10行,則顯示10行,其余內(nèi)容"..."處理。顯示更多內(nèi)容后,“更多”按鈕變?yōu)椤笆掌稹保? *
* 思考:
* 1、如果小于3行,直接顯示內(nèi)容
* 2、如果大于3行,顯示3行,則需要顯示“更多按鈕”
* 3、如果大于3行,小于10行,顯示最多10行,直接添加“收起”按鈕即可
* 4、如果大于10行,顯示最多10行,需要添加“...”和“收起”按鈕
*/
- (void)showTestText:(BOOL)showMore {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:4]; // 設(shè)置行間距
// 需要顯示的富文本,用這個文本去計算每行需要顯示的內(nèi)容和需要的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];
// 內(nèi)容富文本
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];
// 根據(jù)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];
// 然后再 根據(jù)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 : 原文本內(nèi)容
/// @param maxLines : 最多顯示多少行
/// @param largeLines : 全部內(nèi)容顯示多少行
/// @param attStr : 需要顯示的富文本
/// @param rect : 顯示的區(qū)域
/// @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 派生類,
// 它允許你對文本應(yīng)用格式化屬性。 現(xiàn)在我們還沒有用到格式化,這里僅僅使用純文本。 */
// NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:text];
// [attStr addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)myFont range:NSMakeRange(0, attStr.length)];
/* CTFramesetter 是使用 Core Text 繪制時最重要的類。它管理您的字體引用和文本繪制幀。
目前您需要了解 CTFramesetterCreateWithAttributedString 通過應(yīng)用屬性化文本創(chuàng)建 CTFramesetter 。
本節(jié)中,在 framesetter 之后通過一個所選的文本范圍(這里我們選擇整個文本)與需要繪制到的矩形路徑創(chuàng)建一個幀。*/
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attStr);
/* 這里你需要創(chuàng)建一個用于繪制文本的路徑區(qū)域。Mac 上的 Core Text 支持矩形圖形等不同形狀,
但在 iOS 上只支持矩形。在這個示例中,你將通過 self.bounds 使用整個視圖矩形區(qū)域創(chuàng)建 CGPath 引用。 */
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,rect.size.width,MAXFLOAT));
//CTFrameDraw 將 frame 描述到設(shè)備上下文。
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;
//獲取到需要添加...的那一行內(nèi)容
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];
//移除最后幾個字節(jié)的文本
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;
}
屬性設(shè)置
#pragma mark - lazy load -
- (UITextView *)textView {
if (!_textView) {
_textView = [UITextView new];
_textView.delegate = self;
_textView.editable = NO; // 禁止可編輯
_textView.selectable = YES; // 允許點擊事件
_textView.scrollEnabled = NO; // 禁止滑動,如果允許滑動,那下面設(shè)置 contentInset 和 textContainerInset 就多余了
_textView.bounces = NO;
_textView.contentInset = UIEdgeInsetsMake(4, 0, 0, -10); // 設(shè)置內(nèi)容邊距 ,設(shè)置為-10是為了配合 textContainerInset ,因為有時候因為右邊邊距不夠換行 而造成計算的行數(shù)和內(nèi)容與實際顯示的內(nèi)容不符
_textView.textContainerInset = UIEdgeInsetsMake(4, 0, 0, 0); // 設(shè)置文本邊距
_textView.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
_textView.textContainer.lineBreakMode = NSLineBreakByWordWrapping; // 設(shè)置為 NSLineBreakByWordWrapping 是為了后面在合適的位置添加“更多”文本
[_textView.textContainer setLineFragmentPadding:0.01]; // 設(shè)置左邊距為0,不然顯示跟計算的不正確
// [_textView.layoutManager setAllowsNonContiguousLayout:YES]; // 設(shè)置
_textView.linkTextAttributes = @{
NSForegroundColorAttributeName : [UIColor orangeColor],
NSFontAttributeName:[UIFont systemFontOfSize:16 weight:UIFontWeightRegular],
}; // 設(shè)置link的屬性
}
return _textView;
}
- (NSString *)topicString {
if (!_topicString) {
_topicString = @"#這個是標題#";
}
return _topicString;
}
- (NSString *)textString {
if (!_textString) {
_textString = @"今天是jason的生日,我準備送她一部iPhone手機,順便吟詩一首:君不見黃河之水天上來,奔流到海不復回。high hope!君不見高堂明鏡悲白發(fā),千里冰絲暮成雪。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;
}
這里面代碼片段組合起來就是一個完整的代碼,里面的一些數(shù)據(jù)設(shè)置,如果與自己不一樣,可以嘗試去調(diào)整一下。