直接附代碼:
#import "MyView.h"
#import <CoreText/CoreText.h>
// 行距
const CGFloat kGlobalLineLeading = 5.0;
// 在15字體下,比值小于這個計算出來的高度會致使emoji顯示不全
const CGFloat kPerLineRatio = 1.4;
@interface MyView()
@property (nonatomic ,assign) CGFloat textHeight;
@end
@implementation MyView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.text = @"我自橫刀向天笑,去留肝膽兩昆侖。--譚嗣同同學你好啊。This is my first CoreText demo,how are you ?I love three things,the sun,the moon,and you.the sun for the day,the moon for the night,and you forever.??????????????去年本日此門中,人面桃花相映紅。人面不知何處去,桃花照舊笑春風。??????????????少年不知愁滋味,愛上層樓,愛上層樓,為賦新詞強說愁。56321363464.而今識盡愁滋味,欲說還休,欲說還休,卻道天涼好個秋。123456,7890,56321267895434。缺月掛疏桐,漏斷人初靜。誰見幽人獨來往,縹緲孤鴻影。驚起卻回頭,有恨無人省。撿盡寒枝不肯棲,孤單沙洲冷。";
self.font = [UIFont systemFontOfSize:15];
}
return self;
}
/**
* 高度 = 每行的固定高度 * 行數
*/
+ (CGFloat)textHeightWithText:(NSString *)aText width:(CGFloat)aWidth font:(UIFont *)aFont{
NSMutableAttributedString *content = [[NSMutableAttributedString alloc] initWithString:aText];
// 給字符串設置字體行距等樣式
[self addGlobalAttributeWithContent:content font:aFont];
CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)content);
// 粗略的高度,該高度不準,僅供參考
CGSize suggestSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0, content.length), NULL, CGSizeMake(aWidth, MAXFLOAT), NULL);
NSLog(@"suggestHeight = %f",suggestSize.height);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, CGRectMake(0, 0, aWidth, suggestSize.height));
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, content.length), pathRef, NULL);
CFArrayRef lines = CTFrameGetLines(frameRef);
CFIndex lineCount = CFArrayGetCount(lines);
NSLog(@"行數 = %ld",lineCount);
// 總高度 = 行數*每行的高度,其中每行的高度為指定的值,不同字體大小不1樣
CGFloat accurateHeight = lineCount * (aFont.pointSize * kPerLineRatio);
CGFloat height = accurateHeight;
CFRelease(pathRef);
CFRelease(frameRef);
return height;
}
#pragma mark - 工具方法
#pragma mark 給字符串添加全局屬性,比如行距,字體大小,默許色彩
+ (void)addGlobalAttributeWithContent:(NSMutableAttributedString *)aContent font:(UIFont *)aFont
{
CGFloat lineLeading = kGlobalLineLeading; // 行間距
const CFIndex kNumberOfSettings = 2;
//設置段落格式
CTParagraphStyleSetting lineBreakStyle;
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
lineBreakStyle.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakStyle.valueSize = sizeof(CTLineBreakMode);
lineBreakStyle.value = &lineBreakMode;
//設置行距
CTParagraphStyleSetting lineSpaceStyle;
CTParagraphStyleSpecifier spec;
spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
lineSpaceStyle.spec = spec;
lineSpaceStyle.valueSize = sizeof(CGFloat);
lineSpaceStyle.value = &lineLeading;
// 結構體數組
CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
lineBreakStyle,
lineSpaceStyle,
};
CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
// 將設置的行距利用于整段文字
[aContent addAttribute:NSParagraphStyleAttributeName value:(__bridge id)(theParagraphRef) range:NSMakeRange(0, aContent.length)];
CFStringRef fontName = (__bridge CFStringRef)aFont.fontName;
CTFontRef fontRef = CTFontCreateWithName(fontName, aFont.pointSize, NULL);
// 將字體大小利用于整段文字
[aContent addAttribute:NSFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, aContent.length)];
// 給整段文字添加默許色彩
[aContent addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, aContent.length)];
// 內存管理
CFRelease(theParagraphRef);
CFRelease(fontRef);
}
#pragma mark - 1行1行繪制,行高肯定,行與行之間對齊
#pragma mark - 1行1行繪制,行高肯定,高度不夠時加上省略號
- (void)drawRectWithLineByLineAlignmentAndEllipses{
// 1.創建需要繪制的文字
NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:self.text];
// 2.設置行距等樣式
[[self class] addGlobalAttributeWithContent:attributed font:self.font];
self.textHeight = [[self class] textHeightWithText:self.text width:CGRectGetWidth(self.bounds) font:self.font];
// 3.創建繪制區域,path的高度對繪制有直接影響,如果高度不夠,則計算出來的CTLine的數量會少1行或少多行
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), self.textHeight*2));
// 4.根據NSAttributedString生成CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);
// 重置高度
CGFloat realHeight = self.textHeight;
// 繪制全部文本需要的高度大于實際高度則調劑,并加上省略號
if (realHeight > CGRectGetHeight(self.frame)){
realHeight = CGRectGetHeight(self.frame);
}
NSLog(@"realHeight = %f",realHeight);
// 獲得上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// 轉換坐標系
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
CGContextTranslateCTM(contextRef, 0, realHeight); // 這里隨著調劑
CGContextScaleCTM(contextRef, 1.0, -1.0);
// 這里可調劑可不調劑
CGPathAddRect(path, NULL, CGRectMake(0, 0, CGRectGetWidth(self.bounds), realHeight));
// 1行1行繪制
CFArrayRef lines = CTFrameGetLines(ctFrame);
CFIndex lineCount = CFArrayGetCount(lines);
CGPoint lineOrigins[lineCount];
// 把ctFrame里每行的初始坐標寫到數組里,注意CoreText的坐標是左下角為原點
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
CGFloat frameY = 0;
for (CFIndex i = 0; i < lineCount; i++){
// 遍歷每行CTLine
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;
CGFloat lineDescent;
CGFloat lineLeading; // 行距
// 該函數除會設置好ascent,descent,leading以外,還會返回這行的寬度
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
//CoreText的origin的Y值是在baseLine處,而不是下方的descent。
CGPoint lineOrigin = lineOrigins[i];
//行高
CGFloat lineHeight = self.font.pointSize * kPerLineRatio;
//self.font.descender為負值
frameY = realHeight - (i + 1)*lineHeight - self.font.descender;
NSLog(@"frameY = %f",frameY);
lineOrigin.y = frameY;
//調劑坐標
CGContextSetTextPosition(contextRef, lineOrigin.x, lineOrigin.y);
if (frameY + self.font.descender > lineHeight){
CTLineDraw(line, contextRef);
}else{
NSLog(@"最后1行");
// 最后1行,加上省略號
static NSString* const kEllipsesCharacter = @"\u2026";
CFRange lastLineRange = CTLineGetStringRange(line);
// 1個emoji表情占用兩個長度單位
NSLog(@"range.location = %ld,range.length = %ld,總長度 = %ld",lastLineRange.location,lastLineRange.length,attributed.length);
if (lastLineRange.location + lastLineRange.length < (CFIndex)attributed.length){
// 這1行放不下所有的字符(下1行還有字符),則把此行后面的回車、空格符去掉后,再把最后1個字符替換成省略號
CTLineTruncationType truncationType = kCTLineTruncationEnd;
NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1;
// 拿到最后1個字符的屬性字典
NSDictionary *tokenAttributes = [attributed attributesAtIndex:truncationAttributePosition
effectiveRange:NULL];
// 給省略號字符設置字體大小、色彩等屬性
NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:kEllipsesCharacter
attributes:tokenAttributes];
// 用省略號單獨創建1個CTLine,下面在截斷重新生成CTLine的時候會用到
CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)tokenString);
// 把這1行的屬性字符串復制1份,如果要把省略號放到中間或其他位置,只需指定復制的長度便可
NSUInteger copyLength = lastLineRange.length;
NSMutableAttributedString *truncationString = [[attributed attributedSubstringFromRange:NSMakeRange(lastLineRange.location, copyLength)] mutableCopy];
if (lastLineRange.length > 0)
{
// Remove any whitespace at the end of the line.
unichar lastCharacter = [[truncationString string] characterAtIndex:copyLength - 1];
// 如果復制字符串的最后1個字符是換行、空格符,則刪掉
if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:lastCharacter])
{
[truncationString deleteCharactersInRange:NSMakeRange(copyLength - 1, 1)];
}
}
// 拼接省略號到復制字符串的最后
[truncationString appendAttributedString:tokenString];
// 把新的字符串創建成CTLine
CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString);
// 創建1個截斷的CTLine,該方法不能少,具體作用還有待研究
CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, self.frame.size.width, truncationType, truncationToken);
if (!truncatedLine)
{
// If the line is not as wide as the truncationToken, truncatedLine is NULL
truncatedLine = CFRetain(truncationToken);
}
CFRelease(truncationLine);
CFRelease(truncationToken);
CTLineDraw(truncatedLine, contextRef);
CFRelease(truncatedLine);
} else{
// 這1行恰好是最后1行,且最后1行的字符可以完全繪制出來
CTLineDraw(line, contextRef);
}
// 跳出循環,避免繪制剩下的過剩的CTLine
break;
}
}
CFRelease(path);
CFRelease(framesetter);
CFRelease(ctFrame);
}
- (void)drawRect:(CGRect)rect
{
[self drawRectWithLineByLineAlignmentAndEllipses];
}
@end
調用:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Do any additional setup after loading the view, typically from a nib.
MyView *view = [[MyView alloc]initWithFrame:CGRectMake(0, 20, self.view.width, 100)];
view.backgroundColor = [UIColor redColor];
view.text = @"我自橫刀向天笑,去留肝膽兩昆侖。--譚嗣同同學你好啊。This is my first CoreText demo,how are you ?I love three things,the sun,the moon,and you.the sun for the day,the moon for the night,and you forever.??????????????去年本日此門中,人面桃花相映紅。人面不知何處去,桃花照舊笑春風。??????????????少年不知愁滋味,愛上層樓,愛上層樓,為賦新詞強說愁。56321363464.而今識盡愁滋味,欲說還休,欲說還休,卻道天涼好個秋。123456,7890,56321267895434。缺月掛疏桐,漏斷人初靜。誰見幽人獨來往,縹緲孤鴻影。驚起卻回頭,有恨無人省。撿盡寒枝不肯棲,孤單沙洲冷。";
view.font = [UIFont systemFontOfSize:15];
[self.view addSubview:view];
}
結果:
參考:
CoreText使用教程(4)