http://www.lxweimin.com/p/6db3289fb05d
TextKit?
AttributedString?
CoreText實現圖文混排其實就是在富文本中插入一個空白的圖片占位符的富文本字符串,通過代理設置相關的圖片尺寸信息,根據從富文本得到的frame計算圖片繪制的frame再繪制圖片這么一個過程。
(3)圖文混排
?????CTFrameRef??textFrame?????//?coreText?的?frame
?????CTLineRef??????line?????????????//??coreText?的?line
? ? ? CTRunRef??????run?????????????//??line??中的部分文字
?????相關方法:
CFArrayRef?CTFrameGetLines????(CTFrameRef?frame?)??????//獲取包含CTLineRef的數組
void?CTFrameGetLineOrigins(
CTFrameRef?frame,
CFRange?range,
CGPoint?origins[]?)??//獲取所有CTLineRef的原點
CFRange?CTLineGetStringRange??(CTLineRef?line?)????//獲取line中文字在整段文字中的Range
CFArrayRef?CTLineGetGlyphRuns??(CTLineRef?line?)????//獲取line中包含所有run的數組
?CFRange?CTRunGetStringRange??(CTRunRef?run?)?????//獲取run在整段文字中的Range
CFIndex?CTLineGetStringIndexForPosition(
CTLineRef?line,
CGPoint?position?)???//獲取點擊處position文字在整段文字中的index
CGFloat?CTLineGetOffsetForStringIndex(
CTLineRef?line,
CFIndex?charIndex,
CGFloat*?secondaryOffset?)?//獲取整段文字中charIndex位置的字符相對line的原點的x值
??主要步驟:
???????1)計算并存儲文字中保含的所有表情文字及其Range
???????2)替換表情文字為指定寬度的NSAttributedString
CTRunDelegateCallbacks?callbacks;
callbacks.version?=?kCTRunDelegateVersion1;
callbacks.getAscent?=?ascentCallback;
callbacks.getDescent?=?descentCallback;
callbacks.getWidth?=?widthCallback;
callbacks.dealloc?=?deallocCallback;
CTRunDelegateRef?runDelegate?=?CTRunDelegateCreate(&callbacks,?NULL);
NSDictionary?*attrDictionaryDelegate?=?[NSDictionary?dictionaryWithObjectsAndKeys:
(id)runDelegate,?(NSString*)kCTRunDelegateAttributeName,
[UIColor?clearColor].CGColor,(NSString*)kCTForegroundColorAttributeName,
nil];
NSAttributedString?*faceAttributedString?=?[[NSAttributedString?alloc]?initWithString:@"*"?attributes:attrDictionaryDelegate];
[weiBoText?replaceCharactersInRange:faceRange?withAttributedString:faceAttributedString];
[faceAttributedString?release];
???????3)??根據保存的表情文字的Range計算表情圖片的Frame
textFrame?通過CTFrameGetLines?獲取所有line的數組?lineArray
遍歷lineArray中的line通過CTLineGetGlyphRuns獲取line中包含run的數組?runArray
遍歷runArray中的run?通過CTRunGetStringRange獲取run的Range
判斷表情文字的location是否在run的Range
如果在?通過CTLineGetOffsetForStringIndex獲取x的值?y的值為line原點的值
???????4)Draw表情圖片到計算獲取到的Frame
(3)點擊文字觸發事件
??主要步驟:
???????1)?根據touch事件獲取點point
???????2)???textFrame?通過CTFrameGetLineOrigins獲取所有line的原點
???????3)?比較point和line原點的y值獲取點擊處于哪個line
? ? ? ?4)??line、point?通過CTLineGetStringIndexForPosition獲取到點擊字符在整段文字中的????index
? ? ? ?5)??NSAttributedString?通過index?用方法-(NSDictionary?*)attributesAtIndex:(NSUInteger)location?effectiveRange:(NSRangePointer)range??可以獲取到點擊到的NSAttributedString中存儲的NSDictionary
???????6)?通過NSDictionary中存儲的信息判斷點擊的哪種文字類型分別處理
二、CoreText與UIWebView在排版方面的優劣比較
UIWebView也常用于處理復雜的排版,對應排版他們之間的優劣如下(摘自 《iOS開發進階》—— 唐巧):
CoreText占用的內容更少,渲染速度更快。UIWebView占用的內存多,渲染速度慢。
CoreText在渲染界面的前就可以精確地獲得顯示內容的高度(只要有了CTFrame即可),而WebView只有渲染出內容后,才能獲得內容的高度(而且還需要用JavaScript代碼來獲取)。
CoreText的CTFrame可以在后臺線程渲染,UIWebView的內容只能在主線程(UI線程)渲染。
基于CoreText可以做更好的原生交互效果,交互效果可以更加細膩。而UIWebView的交互效果都是用JavaScript來實現的,在交互效果上會有一些卡頓的情況存在。例如,在UIWebView下,一個簡單的按鈕按下的操作,都無法做出原生按鈕的即時和細膩的按下效果。
CoreText排版的劣勢:
CoreText渲染出來的內容不能像UIWebView那樣方便地支持內容的復制。
基于CoreText來排版需要自己處理很多復制的邏輯,例如需要自己處理圖片與文字混排相關的邏輯,也需要自己實現連接點擊操作的支持。
- (void)drawRect:(CGRect)rect{
? ? [superdrawRect:rect];
? ? //獲取當前繪制上下文
? ? CGContextRef context = UIGraphicsGetCurrentContext();
? ? //設置字形的變換矩陣為不做圖形變換
? ? CGContextSetTextMatrix(context, CGAffineTransformIdentity);
? ? //平移方法,將畫布向上平移一個屏幕高
? ? CGContextTranslateCTM(context, 0, self.bounds.size.height);
? ? //縮放方法,x軸縮放系數為1,則不變,y軸縮放系數為-1,則相當于以x軸為軸旋轉180度
? ? CGContextScaleCTM(context,1.0, -1.0);
? ? NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n這里在測試圖文混排,\n我是一個富文本"];
? ? /*
?? ? 事實上,圖文混排就是在要插入圖片的位置插入一個富文本類型的占位符。通過CTRUNDelegate設置圖片
?? ? */
? ? CTRunDelegateCallbacks callBacks;
? ? //memset將已開辟內存空間 callbacks 的首 n 個字節的值設為值 0, 相當于對CTRunDelegateCallbacks內存空間初始化
? ? memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));
? ? callBacks.version = kCTRunDelegateVersion1;
? ? //基線為過原點的x軸,ascent即為CTRun頂線距基線的距離,descent即為底線距基線的距離。
? ? callBacks.getAscent = ascentCallBacks;
? ? callBacks.getDescent = descentCallBacks;
? ? callBacks.getWidth = widthCallBacks;
? ? NSDictionary* dicPic =@{@"height":@129,@"width":@400};
? ? CTRunDelegateRefdelegate =CTRunDelegateCreate(& callBacks, (__bridgevoid*)dicPic);
?? // 設置代理的時候綁定了一個返回圖片尺寸的字典。
? ? unicharplaceHolder =0xFFFC;//創建空白字符
? ? NSString* placeHolderStr = [NSStringstringWithCharacters:&placeHolderlength:1];
? ? NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];
? ? CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
? ? CFRelease(delegate);
? ? [attributeStrinsertAttributedString:placeHolderAttrStratIndex:12];
? ? //繪制文字
? ? //一個frame的工廠,負責生成frame
? ? CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
? ? //創建繪制區域
? ? CGMutablePathRef path = CGPathCreateMutable();
? ? //添加繪制尺寸
? ? CGPathAddRect(path, NULL, self.bounds);
? ? NSIntegerlength = attributeStr.length;
? ? CTFrameRefframe =CTFramesetterCreateFrame(frameSetter,CFRangeMake(0, length), path,NULL);
? ? //根據frame繪制文字
? ? CTFrameDraw(frame, context);
? ? //繪制圖片
? ? UIImage* image = [UIImageimageNamed:@"timg"];
? ? CGRect imgFrm = [self calculateImageRectWithFrame:frame];
? ? CGContextDrawImage(context,imgFrm, image.CGImage);
? ? CFRelease(frame);
? ? CFRelease(path);
? ? CFRelease(frameSetter);
}
staticCGFloatascentCallBacks(void* ref)
{
? ? return[(NSNumber*)[(__bridgeNSDictionary*)refvalueForKey:@"height"]floatValue];
}
staticCGFloatdescentCallBacks(void* ref)
{
? ? return 0;
}
staticCGFloatwidthCallBacks(void* ref)
{
? ? return[(NSNumber*)[(__bridgeNSDictionary*)refvalueForKey:@"width"]floatValue];
}
-(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
{
? ? /*就是遍歷我們的frame中的所有CTRun,檢查他是不是我們綁定圖片的那個,如果是,根據該CTRun所在CTLine的origin以及CTRun在CTLine中的橫向偏移量計算出CTRun的原點,加上其尺寸即為該CTRun的尺寸。*/
? ? //獲取繪制frame中的所有CTLine
? ? NSArray* arrLines = (NSArray*)CTFrameGetLines(frame);
? ? NSIntegercount = [arrLinescount];
? ? CGPointpoints[count];
? ? CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
? ? for(inti =0; i < count; i ++) {
? ? ? ? CTLineRefline = (__bridgeCTLineRef)arrLines[i];
? ? ? ? NSArray* arrGlyphRun = (NSArray*)CTLineGetGlyphRuns(line);
? ? ? ? for(intj =0; j < arrGlyphRun.count; j ++) {
? ? ? ? ? ? CTRunRefrun = (__bridgeCTRunRef)arrGlyphRun[j];
? ? ? ? ? ? NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);? ? ? ? ? ? CTRunDelegateRefdelegate = (__bridgeCTRunDelegateRef)[attributesvalueForKey:(id)kCTRunDelegateAttributeName];
? ? ? ? ? ? if(delegate ==nil) {
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? NSDictionary* dic =CTRunDelegateGetRefCon(delegate);
? ? ? ? ? ? if(![dicisKindOfClass:[NSDictionaryclass]]) {
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? CGPointpoint = points[i];
? ? ? ? ? ? CGFloatascent;
? ? ? ? ? ? CGFloatdescent;
? ? ? ? ? ? CGRectboundsRun;
? ? ? ? ? ? boundsRun.size.width=CTRunGetTypographicBounds(run,CFRangeMake(0,0), &ascent, &descent,NULL);
? ? ? ? ? ? boundsRun.size.height= ascent + descent;
? ? ? ? ? ? //獲取x偏移量
? ? ? ? ? ? CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
? ? ? ? ? ? //point是行起點位置,加上每個字的偏移量得到每個字的x
? ? ? ? ? ? boundsRun.origin.x= point.x+ xOffset;
? ? ? ? ? ? boundsRun.origin.y= point.y- descent;//計算原點
? ? ? ? ? ? CGPathRefpath =CTFrameGetPath(frame);//獲取繪制區域
? ? ? ? ? ? CGRectcolRect =CGPathGetBoundingBox(path);
? ? ? ? ? ? //獲取剪裁區域邊框
? ? ? ? ? ? CGRectimageBounds =CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
? ? ? ? ? ? returnimageBounds;
? ? ? ? }
? ? }
? ? return CGRectZero;
}