iOS 編程中如何使用CoreText類庫
從框架圖中我們可以看出:
CTFrame 作爲一個整體的畫布(Canvas),其中由行(CTLine)組成,而每行可以分爲一個或多個小方塊(CTRun)。
注意:你不需要自己創建CTRun,Core Text將根據NSAttributedString的屬性來自動創建CTRun。每個CTRun對象對應不同的屬性,正因此,你可以自由的控制字體、顏色、字間距等等信息。
通常處理步聚:
1.使用core text就是先有一個要顯示的string,然後定義這個string每個部分的樣式->attributedString -> 生成 CTFramesetter -> 得到CTFrame -> 繪製(CTFrameDraw)
其中可以更詳細的設置換行方式,對齊方式,繪製區域的大小等。
2.繪製只是顯示,點擊事件就需要一個判斷了。
CTFrame 包含了多個CTLine,並且可以得到各個line的其實位置與大小。判斷點擊處在不在某個line上。CTLine 又可以判斷這個點(相對於ctline的座標)處的文字範圍。然後遍歷這個string的所有NSTextCheckingResult,根據result的rang判斷點擊處在不在這個rang上,從而得到點擊的鏈接與位置。
首先,創建工程,添加系統庫CoreText
//
// CoreTextView.m
// CoreTextDemo
//
// Created by swplzj on 13-10-30.
// Copyright (c) 2013年 swplzj. All rights reserved.
//
#import "CoreTextView.h"
#import <CoreText/CoreText.h>
#import <math.h>
@implementation CoreTextView
static inline double radians (double degrees)
{
return degrees * M_PI / 180;
}
void ctLineAndRun(CTFrameRef frame)
{
int lineIndex = 1;
//獲取frame中的所有行
CFArrayRef allLines = CTFrameGetLines(frame);
//獲取frame中第一行的元素
CTLineRef firstLineRef = CFArrayGetValueAtIndex(allLines, lineIndex);
//獲得frame中所有的行數
int linesCount = CFArrayGetCount(allLines);
NSLog(@"ctLine的總數量:%d", linesCount);
//獲取frame中的所有CTRun
CFArrayRef ctRun = CTLineGetGlyphRuns(firstLineRef);
// CTRunRef firstRun = CFArrayGetValueAtIndex(ctRun, 0);
// int runsNum = CTLineGetGlyphCount(ctRun);
NSLog(@"ctRun的數量:%ld", CFArrayGetCount(ctRun));
//按長度截斷字符串:省略號
NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:@"\u2026"];
CTLineRef lineToken = CTLineCreateWithAttributedString((CFAttributedStringRef) attributedStr);
//截斷的方式
CTLineTruncationType lineTruncationType = kCTLineTruncationEnd;
CTLineRef newLine = CTLineCreateTruncatedLine(firstLineRef, 260, lineTruncationType, lineToken);
CGContextRef context = UIGraphicsGetCurrentContext();
//重置畫布
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//保存Context狀態
CGContextSaveGState(context);
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectInset((CGRect) {{40, 40}, {320 - 80, 40}}, 0.0f, 0.0f);
CGPathAddRect(path, NULL, bounds);
CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
CGContextFillPath(context);
CGContextSetTextPosition(context, 40, 40);
CTLineDraw(newLine, context);
CGPathCloseSubpath(path);
CGPathRelease(path);
//獲取一行文字中,指定charIndex字符(也就是StringRange的Location)相對ctline本身x原點的偏移量(譬如第一個字符很顯然是0),返回值與secondaryOffset同爲一個值,另外也可以用來求一行字符所佔的像素長度
//第二個字符index位置是在ctframe整個中的位置,譬如第二行的第一個字符的index是加上第一行所有字符後的index
CGFloat secondaryOffSet;
CGFloat primaryOffSet = CTLineGetOffsetForStringIndex(firstLineRef, CTLineGetStringRange(firstLineRef).location, &secondaryOffSet);
NSLog(@"第%ld個字符與本身x原點的偏移量:%f",CTLineGetStringRange(firstLineRef).location + 1,primaryOffSet);
//獲取相對於Flush的偏移量
// CGFloat penOffset = CTLineGetPenOffsetForFlush(firstLine, NSTextAlignmentLeft, 240);
// NSLog(@"相對於Flush的偏移量:%f",penOffset);
//CTLineGetStringIndexForPosition 這個方法主要是獲得某個ctline中某個位置(position)的character在整個ctframe中的index.這個方法在當有段前縮進或者首行縮進的時候,並不準確,不會跟着縮進而進行偏移,所以段前縮進多少,就需要把position的x值減去多少(譬如段前縮進是20,則要減去20個單位),所以,這個方法最好的用處就是判斷一行ctline最多容納多少的字符,只需把這個point的x位置調很大(超過ctframe path的寬度)就可以了。
CGPoint origins[linesCount];
CTFrameGetLineOrigins(frame,CFRangeMake(0, 0), origins);
CGPoint indexPoint = origins[lineIndex];
indexPoint.x -= 20;//段前縮進20,需要減去20//indexPoint.x += 300;可以求
CFIndex index = CTLineGetStringIndexForPosition(firstLineRef, indexPoint);
NSLog(@"ctframe中%@位置(position)的字符索引:%ld",NSStringFromCGPoint(indexPoint),index);
//CTLine在整個文本中所處的range,location是該行前面n行的字符數和,換行符也包括
// CFRange range = CTLineGetStringRange(firstLine);
// NSLog(@"location:%ld=====length:%ld",range.location,range.length);
//獲取一行中上行高(ascent),下行高(descent),行距(leading),整行高爲(ascent+|descent|+leading) 返回值爲整行字符串長度佔有的像素寬度。
// CGFloat asc,des,lead;
// double lineWidth = CTLineGetTypographicBounds(firstLine, &asc, &des, &lead);
// NSLog(@"ascent = %f,descent = %f,leading = %f,lineWidth = %f",asc,des,lead,lineWidth);
//獲取一行文字最佳可視範圍(會把所有文字都包含進去)
// CGRect lineRect = CTLineGetImageBounds(firstLine, context);
// NSLog(@"整行的範圍:%@",NSStringFromCGRect(lineRect));
CGContextRestoreGState(context);
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:@"北京出現嚴重霧霾後,網友紛紛給PM2.5取中文名字。嚴肅點就叫“公霧源”,高端點就叫“京塵”,霸氣點就叫“塵疾思汗”,樂觀點就叫“塵世美”,娛樂點就叫“塵慣吸”。但直到那五個字映入眼簾,才明白了中文的強大和詞彙的無窮魅力:喂人民服霧!"];
[self setAttributedStringParagraphStyle:attributedStr];
// [self setAttributesStringAttributes:attributedStr];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) attributedStr);
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectInset((CGRect) {{20, 100}, {CGRectGetWidth(self.bounds) - 40, 400}}, 10.0f, 10.0f);
CGPathAddRect(path, NULL, bounds);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.bounds.size.height);//轉換CTM
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSaveGState(context);//向圖形狀態棧中保存context狀態
//ctLine的相關操作
ctLineAndRun(frame);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);//重置畫布
CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextFillPath(context);
//CTLineGetImageBounds的應用,獲取一行文字的範圍, 就是指把這一行文字點有的像素矩陣作爲一個image圖片,來得到整個矩形區域
CFArrayRef Lines = CTFrameGetLines(frame);
int lineCount = CFArrayGetCount(Lines);
CGPoint origins[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
NSInteger lineIndex = 0;
for (id oneLine in (NSArray *)Lines) {
//相對於每一行基線原點的偏移量和寬高(例如:{{1.2, -2.57227}, {208.025, 19.2523}},就是相對於本身的基線原點向右偏移1.2個單位,向下偏移2.57227個單位,後面是寬高)
CGRect lineBounds = CTLineGetImageBounds((CTLineRef)oneLine, context);
//每一行的起始點(相對於context)加上相對於本身基線原點的偏移量
lineBounds.origin.x += origins[lineIndex].x + 30;
lineBounds.origin.y += origins[lineIndex].y + 110;
lineIndex++;
//填充
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextSetLineWidth(context, 1.0);
//設置長方形4個頂點
CGPoint points[] = {CGPointMake(lineBounds.origin.x, lineBounds.origin.y), CGPointMake(lineBounds.origin.x + lineBounds.size.width, lineBounds.origin.y), CGPointMake(lineBounds.origin.x + lineBounds.size.width, lineBounds.origin.y + lineBounds.size.height), CGPointMake(lineBounds.origin.x, lineBounds.origin.y + lineBounds.size.height)};
CGContextAddLines(context, points, 4);
CGContextClosePath(context);
CGContextStrokePath(context);
}
//畫
CTFrameDraw(frame, context);
CGPathRelease(path);
CFRelease(frame);
CFRelease(frameSetter);
//恢復狀態棧中原有的狀態
CGContextRestoreGState(context);
}
- (void)setAttributesStringAttributes:(NSMutableAttributedString *)attributedString
{
//設置字體
CTFontRef font = CTFontCreateWithName(CFSTR("Georgia"), 20, NULL);
[attributedString addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, 4)];
//設置字間距
long kernNumber = 5;
CFNumberRef kernNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &kernNumber);
[attributedString addAttribute:(id)kCTKernAttributeName value:(id)kernNum range:(NSRange){0, 4}];
//設置字體顏色
long strokeNumber = 2;
CFNumberRef strokeNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &strokeNumber);
[attributedString addAttribute:(id)kCTStrokeWidthAttributeName value:(id)strokeNum range:(NSRange){0, 4}];
[attributedString addAttribute:(id)kCTStrokeColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(0, 4)];
//設置下劃線
[attributedString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInt:kCTUnderlineStyleSingle] range:NSMakeRange(0, 4)];
[attributedString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[UIColor redColor] range:(NSRange){0, 4}];
//多屬性設置
UIColor *color = [UIColor redColor];
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:(id)font, kCTFontAttributeName, kernNum, kCTKernAttributeName, color.CGColor, kCTForegroundColorAttributeName, nil];
[attributedString setAttributes:attributes range:NSMakeRange(0, 4)];
}
- (void)setAttributedStringParagraphStyle:(NSMutableAttributedString *)attributedString
{
//對齊方式
CTTextAlignment textAlignment = kCTLeftTextAlignment;
CTParagraphStyleSetting alignmentStyleSetting;
alignmentStyleSetting.spec = kCTParagraphStyleSpecifierAlignment;
alignmentStyleSetting.valueSize = sizeof(textAlignment);
alignmentStyleSetting.value = &textAlignment;
//首行縮進
CGFloat firstLineIndentSize = 55.0f;
CTParagraphStyleSetting firstLineIndent;
firstLineIndent.spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;
firstLineIndent.value = &firstLineIndentSize;
firstLineIndent.valueSize = sizeof(float);
//段前縮進(文字左側距離context最左側的距離)
CGFloat headIndentSize = 20.0f;
CTParagraphStyleSetting headIndent;
headIndent.spec = kCTParagraphStyleSpecifierHeadIndent;
headIndent.valueSize = sizeof(float);
headIndent.value = &headIndentSize;
//斷尾縮進(文字右側距離context最左側的距離)
CGFloat tailIndentSize = 251.0f; //文字最左側到文字最右側的距離
CTParagraphStyleSetting tailIndent;
tailIndent.spec = kCTParagraphStyleSpecifierTailIndent;
tailIndent.value = &tailIndentSize;
tailIndent.valueSize = sizeof(float);
//換行模式
CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
CTParagraphStyleSetting lineBreakMode;
lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakMode.value = &lineBreak;
lineBreakMode.valueSize = sizeof(CTLineBreakMode);
//最大行高
CGFloat maxHeightSize = 50.0f; //最大行高不能超過50個像素,超過按照最大像素來
CTParagraphStyleSetting maxHeightSetting;
maxHeightSetting.spec = kCTParagraphStyleSpecifierMaximumLineHeight;
maxHeightSetting.value = &maxHeightSize;
maxHeightSetting.valueSize = sizeof(float);
//多行高
CGFloat multipleHeight = 1.2f; //1.2倍原來的高度
CTParagraphStyleSetting multipleHeightSetting;
multipleHeightSetting.spec = kCTParagraphStyleSpecifierLineHeightMultiple;
multipleHeightSetting.value = &multipleHeight;
multipleHeightSetting.valueSize = sizeof(float);
//最大行距
CGFloat maxLineSpace = 5.0f;//最大行距不能超過5像素,超過了按最大行距畫圖,最小行距同理,行距調整隻在中間值中進行
CTParagraphStyleSetting maxLineSpaceSetting;
maxLineSpaceSetting.spec = kCTParagraphStyleSpecifierMaximumLineSpacing;
maxLineSpaceSetting.valueSize = sizeof(float);
maxLineSpaceSetting.value = &maxLineSpace;
//行距
CGFloat lineSpace = 25.0f; //行距25像素
CTParagraphStyleSetting lineSpaceSetting;
lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;
lineSpaceSetting.value = &lineSpace;
lineSpaceSetting.valueSize = sizeof(float);
//段前間隔
CGFloat paragraghSpace = 15.0f;
CTParagraphStyleSetting paragraghInterval;
paragraghInterval.spec = kCTParagraphStyleSpecifierParagraphSpacing;
paragraghInterval.valueSize = sizeof(float);
paragraghInterval.value = ¶graghSpace;
CTParagraphStyleSetting settingsArray[] = {alignmentStyleSetting, firstLineIndent, headIndent, tailIndent, lineBreakMode, maxLineSpaceSetting, multipleHeightSetting, lineSpaceSetting, maxLineSpaceSetting, paragraghInterval};
//採用setting裏面前10個的段落設置,不夠10個,就按照setting擁有的最大段落設置個數來算
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settingsArray, 10);
NSDictionary *paragraphStyleDic = [[NSDictionary alloc] initWithObjectsAndKeys:(id)paragraphStyle, kCTParagraphStyleAttributeName, nil];
[attributedString setAttributes:paragraphStyleDic range:(NSRange){0, [attributedString length]}];
}
- (void)ctLineAndRun
{
}
@end
關於CoreText的官方API:
https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_Framework_Ref/_index.html