CoreText實現圖文混排 - 博客頻道
CoreText實現圖文混排
也好久沒來寫博客了,主要是最近也工作了,手頭的事有點多,一時間也就斷了,閑下來了我就來補博客了,剛好最近也做了很多東西,放在這里也算給自己做個筆記吧。
最近公司做了一個項目,需要用到圖文混排技術。于是呢就瘋狂地在網上搜刮資料。
不過很不幸的是,百度的CoreText資料還是比較少滴,翻來覆去就那幾個版本。
然而我又上不去谷歌,so,困難重重啊。
不過雖然資料少,不夠前輩們給的貢獻終于還是在我的努力下都被我消化了,然后我也來做個筆記。
Core Text 是基于iOS3.2+ 和 OSX 10.5+ 的一種能夠對文本格式和文本布局進行精細控制的文本引擎。
它良好的結合了 UIKit 和 Core Graphics/Quartz:
UIKit 的 UILabel 允許你通過在 IB 中簡單的拖曳添加文本,但你不能改變文本的顏色和其中的單詞。? CoreGraphics/Quartz幾乎允許你做任何系統允許的事情,但你需要為每個字形計算位置,并畫在屏幕上。CoreText正結合了這兩者!你可以完全控制位置、布局、類似文本大小和顏色這樣的屬性,而 CoreText將幫你完善其它的東西——類似文本換行、字體呈現等等。
以上就是對CoreText的介紹。
老司機認為,圖文混排中使用到的CoreText只是CoreText龐大體系中一個對富文本的增強的一部分。
我個人想法啊,我讀書少,理解的可能不到位,不過你咬我啊。
恩,我又逗逼了一波,說好的大師氣質呢,下面開始嚴肅了啊。
嚴肅的就是iOS7新推出的類庫Textkit,其實是在之前推出的CoreText上的封裝,根據蘋果的說法,他們開發了兩年多才完成,而且他們在開發時候也將表情混排作為一個使用案例進行研究,所以要實現表情混排將會非常容易。
蘋果引入TextKit的目的并非要取代已有的CoreText框架,雖然CoreText的主要作用也是用于文字的排版和渲染,但它是一種先進而又處于底層技術,如果我們需要將文本內容直接渲染到圖形上下文(Graphics context)時,從性能和易用性來考慮,最佳方案就是使用CoreText。
原理的東西學一學總沒有壞處。因此,還是有必要去學一學CoreText的。
那我們開始學習吧。
老司機說過,我要講的只是用來增強富文本的那一部分,那么富文本怎么使用呢。
富文本是什么呢?
富文本格式(RTF)規范是為了便于在應用程序之間輕松轉儲格式化文本和圖形的一種編碼方法?,F在,用戶可以利用特定轉換軟件,在不同系統如MS-DOS、Windows、OS/2、Macintosh和Power Macintosh的應用程序之間轉移字處理文檔。RTF規范提供一種在不同的輸出設備、操作環境和操作系統之間交換文本和圖形的一種格式。RTF使用ANSI, PC-8, Macintosh, 或IBM PC字符集控制文檔的表示法和格式化,包括屏幕顯示和打印。憑借RTF規范,不同的操作系統和不同的軟件程序創建的文檔能夠在這些操作系統和應用程序之間傳遞。將一個格式化的文件轉換為RTF文件的軟件稱為RTF書寫器。RTF書寫器用于分離現有文本中的程序控制信息,并且生成一個包含文本和與之相關的RTF組的新文件。將RTF文件轉換成格式化文件的軟件則稱為RTF閱讀器。
簡單的說,附帶有每一個文字屬性的字符串,就是富文本。
在iOS中,我們有一個專門的類來處理富文本AttributeString。
誒,標題越來越小了,都4個#號了,說明我扯遠了啊。不過要想使用CoreText不會富文本還是不行啊。
來吧。
AttributedString也分為NSAttributedString和NSMutableAttributedString兩個類,類似于String,我就不贅述了。
富文本本質上沒有什么難度,只要給指定的字符串附上指定的屬性就好了。下面給出富文本的一些基本方法。
-initWithString:以NSString初始化一個富文本對象
-setAttributes:range:為富文本中的一段范圍添加一些屬性,第一個參數是個NSDictionary字典,第二個參數是NSRange。
-addAttribute:value:range:添加一個屬性
-addAttributes:range:添加多個屬性
-removeAttribute:range:移除屬性
額,老司機知道這么說不直觀,來來來,上代碼。
NSDictionary* dic = @{NSFontAttributeName:[UIFontfontWithName:@"Zapfino"size:20],NSForegroundColorAttributeName:[UIColorredColor],NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)};NSMutableAttributedString* attributeStr = [[NSMutableAttributedStringalloc] initWithString:@"0我是一個富文本,9聽說我有很多屬性,19I will try。32這里清除屬性."];? ? [attributeStr setAttributes:dic range:NSMakeRange(0, attributeStr.length)];? ? [attributeStr addAttribute:NSFontAttributeNamevalue:[UIFontsystemFontOfSize:30] range:NSMakeRange(9,10)];? ? [attributeStr addAttribute:NSForegroundColorAttributeNamevalue:[UIColorcyanColor] range:NSMakeRange(13,13)];NSDictionary* dicAdd = @{NSBackgroundColorAttributeName:[UIColoryellowColor],NSLigatureAttributeName:@1};? ? [attributeStr addAttributes:dicAdd range:NSMakeRange(19,13)];? ? [attributeStr removeAttribute:NSFontAttributeNamerange:NSMakeRange(32,9)];UILabel* label = [[UILabelalloc] initWithFrame:CGRectMake(100,100,200,400)];? ? label.numberOfLines=0;? ? label.attributedText= attributeStr;
這里你要注意一下,給label的一定是給他的attributedText屬性,你給text是不行的。
是不是用起來很簡單,富文本,跟字典沒什么區別么。
是不是終于進入正題了。其實之所以說那么多,還是為了你看完就能保證會用啊,否則你不會富文本你自己還要查找富文本相關資料。
CoreText實現圖文混排其實就是在富文本中插入一個空白的圖片占位符的富文本字符串,通過代理設置相關的圖片尺寸信息,根據從富文本得到的frame計算圖片繪制的frame再繪制圖片這么一個過程。
先來整體代碼
-(void)drawRect:(CGRect)rect{? ? [superdrawRect:rect];CGContextRefcontext =UIGraphicsGetCurrentContext();CGContextSetTextMatrix(context,CGAffineTransformIdentity);CGContextTranslateCTM(context,0,self.bounds.size.height);CGContextScaleCTM(context,1.0, -1.0);NSMutableAttributedString* attributeStr = [[NSMutableAttributedStringalloc] initWithString:@"\n這里在測試圖文混排,\n我是一個富文本"];? ? CTRunDelegateCallbacks callBacks;? ? callBacks.version= kCTRunDelegateVersion1;? ? callBacks.getAscent= ascentCallBacks;? ? callBacks.getDescent= descentCallBacks;? ? callBacks.getWidth= widthCallBacks;NSDictionary* dicPic = @{@"height":@129,@"width":@400};? ? CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridgevoid*)dicPic);unicharplaceHolder =0xFFFC;NSString* placeHolderStr = [NSStringstringWithCharacters:&placeHolder length:1];NSMutableAttributedString* placeHolderAttrStr = [[NSMutableAttributedStringalloc] initWithString:placeHolderStr];CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr,CFRangeMake(0,1), kCTRunDelegateAttributeName, delegate);CFRelease(delegate);? ? [attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];? ? CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);CGMutablePathRefpath =CGPathCreateMutable();CGPathAddRect(path,NULL,self.bounds);NSIntegerlength = attributeStr.length;? ? CTFrameRef frame = CTFramesetterCreateFrame(frameSetter,CFRangeMake(0, length), path,NULL);? ? CTFrameDraw(frame, context);UIImage* image = [UIImageimageNamed:@"bd_logo1"];CGRectimgFrm = [selfcalculateImageRectWithFrame:frame];CGContextDrawImage(context,imgFrm, image.CGImage);CFRelease(frame);CFRelease(path);}staticCGFloatascentCallBacks(void* ref){return[(NSNumber*)[(__bridgeNSDictionary*)ref valueForKey:@"height"] floatValue];}staticCGFloatdescentCallBacks(void* ref){return0;}staticCGFloatwidthCallBacks(void* ref){return[(NSNumber*)[(__bridgeNSDictionary*)ref valueForKey:@"width"] floatValue];}-(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame{NSArray* arrLines = (NSArray*)CTFrameGetLines(frame);NSIntegercount = [arrLines count];CGPointpoints[count];? ? CTFrameGetLineOrigins(frame,CFRangeMake(0,0), points);for(inti =0; i < count; i ++) {? ? ? ? CTLineRef line = (__bridge CTLineRef)arrLines[i];NSArray* arrGlyphRun = (NSArray*)CTLineGetGlyphRuns(line);for(intj =0; j < arrGlyphRun.count; j ++) {? ? ? ? ? ? CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);? ? ? ? ? ? CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];if(delegate ==nil) {continue;? ? ? ? ? ? }NSDictionary* dic = CTRunDelegateGetRefCon(delegate);if(![dic isKindOfClass:[NSDictionaryclass]]) {continue;? ? ? ? ? ? }CGPointpoint = points[i];CGFloatascent;CGFloatdescent;CGRectboundsRun;? ? ? ? ? ? boundsRun.size.width= CTRunGetTypographicBounds(run,CFRangeMake(0,0), &ascent, &descent,NULL);? ? ? ? ? ? boundsRun.size.height= ascent + descent;CGFloatxOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location,NULL);? ? ? ? ? ? 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;? ? ? ? }? ? }returnCGRectZero;}
不瞞你說,我看著代碼都煩,也怕,所以放心,老司機會一句一句給你解釋的。
CGContextRefcontext =UIGraphicsGetCurrentContext();CGContextSetTextMatrix(context,CGAffineTransformIdentity);CGContextTranslateCTM(context,0,self.bounds.size.height);CGContextScaleCTM(context,1.0, -1.0);
先要來一個背景介紹哈
/*
coreText 起初是為OSX設計的,而OSX得坐標原點是左下角,y軸正方向朝上。iOS中坐標原點是左上角,y軸正方向向下。
若不進行坐標轉換,則文字從下開始,還是倒著的
如下圖(盜的圖,別打我)
*/
系統坐標系
CGContextRefcontext =UIGraphicsGetCurrentContext();
為什么要回去上下文呢?因為我們所有的繪制操作都是在上下文上進行繪制的。
CGContextSetTextMatrix(context,CGAffineTransformIdentity);CGContextTranslateCTM(context,0,self.bounds.size.height);CGContextScaleCTM(context,1.0, -1.0);
正如之上的背景說的,coreText使用的是系統坐標,然而我們平時所接觸的iOS的都是屏幕坐標,所以要將屏幕坐標系轉換系統坐標系,這樣才能與我們想想的坐標互相對應。
事實上呢,這三句是翻轉畫布的固定寫法,這三句你以后會經常看到的。
繼續。
/*
事實上,圖文混排就是在要插入圖片的位置插入一個富文本類型的占位符。通過CTRUNDelegate設置圖片
*/
NSMutableAttributedString* attributeStr = [[NSMutableAttributedStringalloc] initWithString:@"\n這里在測試圖文混排,\n我是一個富文本"];
CTRunDelegateCallbacks callBacks;callBacks.version= kCTRunDelegateVersion1;callBacks.getAscent= ascentCallBacks;callBacks.getDescent= descentCallBacks;callBacks.getWidth= widthCallBacks;
為什么要設置一個回調結構體呢?
因為coreText中大量的調用c的方法。事實上你會發現大部分跟系統底層有關的都需要調c的方法。所以設置代理要按照人家的方法來啊。
看看這幾句代碼也很好懂,就是注釋中寫的意思。
后三句分別就是說當我需要走這些代理的時候都會走那些代理方法。
好吧,扯到這又要補充知識了。這個距離什么東西呢?
字形
對對,這呢就是一個CTRun的尺寸圖,什么你問CTRun是啥?還沒到那呢,后面會詳細介紹。
在這你只要知道,一會我們繪制圖片的時候實際上實在一個CTRun中繪制這個圖片,那么CTRun繪制的坐標系中,他會以origin點作為原點進行繪制。
基線為過原點的x軸,ascent即為CTRun頂線距基線的距離,descent即為底線距基線的距離。
我們繪制圖片應該從原點開始繪制,圖片的高度及寬度及CTRun的高度及寬度,我們通過代理設置CTRun的尺寸間接設置圖片的尺寸。
NSDictionary * dicPic = @{@"height":@129,@"width":@400};? ? CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridgevoid*)dicPic);
上面只是設置了回調結構體,然而我們還沒有告訴這個代理我們要的圖片尺寸。
所以這句話就在設置代理的時候綁定了一個返回圖片尺寸的字典。
事實上此處你可以綁定任意對象。此處你綁定的對象既是回調方法中的參數ref。
好吧就然說到這我就直接把那三個回調方法說了吧,放在一起比較好理解一些。
staticCGFloatascentCallBacks(void*ref){return[(NSNumber *)[(__bridge NSDictionary *)refvalueForKey:@"height"] floatValue];}staticCGFloatdescentCallBacks(void*ref){return0;}staticCGFloatwidthCallBacks(void*ref){return[(NSNumber *)[(__bridge NSDictionary *)refvalueForKey:@"width"] floatValue];}
上文說過,ref既是創建代理是綁定的對象。所以我們在這里,從字典中分別取出圖片的寬和高。
值得注意的是,由于是c的方法,所以也沒有什么對象的概念。是一個指針類型的數據。不過oc的對象其實也就是c的結構體。我們可以通過類型轉換獲得oc中的字典。
__bridge既是C的結構體轉換成OC對象時需要的一個修飾詞。
老司機敲字慢啊,敲到這都兩個小時了,容我喝口水。
你們喝過紅色的尖叫么?老司機喝了那種煙頭泡的水之后精神滿滿的繼續敲字。(那水超難喝,你可以挑戰一下)
誒,說好的嚴肅呢?
首先創建一個富文本類型的圖片占位符,綁定我們的代理
unicharplaceHolder =0xFFFC;NSString* placeHolderStr = [NSStringstringWithCharacters:&placeHolder length:1];NSMutableAttributedString* placeHolderAttrStr = [[NSMutableAttributedStringalloc] initWithString:placeHolderStr];CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr,CFRangeMake(0,1), kCTRunDelegateAttributeName, delegate);CFRelease(delegate);
這里富文本的知識上文中已經介紹過了。不過老司機猜你有三個疑問。
這個添加屬性的方法怎么是這個樣子的?
因為這里是添加CTRunDelegate這種數據類型,要用CoreText專門的方法,不過其實就是形式不同,作用一樣的。
為什么這里富文本類型轉換的時候不用_bridge呢?老司機你不是說需要修飾詞么?你是不是騙我?(markDown語法沖突我少打一個下劃線)
真沒有,事實上不是所有數據轉換的時候都需要__bridge。你要問我怎么區分?那好我告訴你,C中就是傳遞指針的數據就不用。比如說字符串,數組。原因老司機現在解釋不通,等我能組織好語言的。
為什么還要釋放?我是ARC環境啊
不好意思,我也是。不過為什么要釋放呢?因為你進行了類型轉換之后就不屬于對象了,也不再歸自動引用計數機制管理了,所以你得手動管理咯。
然后將占位符插入到我們的富文本中
[attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];
此處我就不贅述了,富文本的知識你只要類比字典就好了。
至此,我們已經生成好了我們要的帶有圖片信息的富文本了,接下來我們只要在畫布上繪制出來這個富文本就好了。
繪制呢,又分成兩部分,繪制文本和繪制圖片。你問我為什么還分成兩個?
因為富文本中你添加的圖片只是一個帶有圖片尺寸的空白占位符啊,你繪制的時候他只會繪制出相應尺寸的空白占位符,所以什么也顯示不了啊。
那怎么顯示圖片???拿到占位符的坐標,在占位符的地方繪制相應大小的圖片就好了。恩,說到這,圖文混排的原理已經說完了。
先來繪制文本吧。
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);CGMutablePathRefpath =CGPathCreateMutable();CGPathAddRect(path,NULL,self.bounds);NSIntegerlength = attributeStr.length;CTFrameRef frame = CTFramesetterCreateFrame(frameSetter,CFRangeMake(0,length), path,NULL);CTFrameDraw(frame, context);
frameSetter是根據富文本生成的一個frame生成的工廠,你可以通過framesetter以及你想要繪制的富文本的范圍獲取該CTRun的frame。
但是你需要注意的是,獲取的frame是僅繪制你所需要的那部分富文本的frame。即當前情況下,你繪制范圍定為(10,1),那么你得到的尺寸是只繪制(10,1)的尺寸,他應該從屏幕左上角開始(因為你改變了坐標系),而不是當你繪制全部富文本時他該在的位置。
然后建立一會繪制的尺寸,實際上就是在指定你的繪制范圍。
接著生成整個富文本繪制所需要的frame。因為范圍是全部文本,所以獲取的frame即為全部文本的frame(此處老司機希望你一定要搞清楚全部與指定范圍獲取的frame他們都是從左上角開始的,否則你會進入一個奇怪的誤區,稍后會提到的)。
最后,根據你獲得的frame,繪制全部富文本。
上面你已經繪制出文字,不過沒有圖片哦,接下來繪制圖片。
繪制圖片用下面這個方法,通用的哦
CGContextDrawImage(context,imgFrm, image.CGImage);
我們可以看到這個方法有三個參數,分別是context,frame,以及image。
要什么就給他什么好咯,context和image都好說,context就是當前的上下文,最開始獲得那個。image就是你要添加的那個圖片,不過是CGImage類型。通過UIImage轉出CGImage就好了,我們重點講一下frame的獲取。
記得我之前說的誤區么?這里我們要獲得Image的frame,你有沒有想過我們的frameSetter?
我也想過,不過就像我說的,你單獨用frameSetter求出的image的frame是不正確的,那是只繪制image而得的坐標,所以哪種方法不能用哦,要用下面的方法。
你們一定發現,我獲取frame的方法單獨寫了一個方法,為什么呢?
1.將代碼分離,方便修改。
2.最主要的是這部分代碼到哪里都能用,達到復用效果。
NSArray* arrLines = (NSArray*)CTFrameGetLines(frame);NSIntegercount = [arrLines count];CGPointpoints[count];CTFrameGetLineOrigins(frame,CFRangeMake(0,0), points);
第一句呢,獲取繪制frame中的所有CTLine。CTLine,又不知道了吧,老司機又要無恥的盜圖了。
CTFrame組成
上面呢,我們能看到一個CTFrame繪制的原理。
CTLine 可以看做Core Text繪制中的一行的對象 通過它可以獲得當前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs
CTRun 或者叫做 Glyph Run,是一組共享想相同attributes(屬性)的字形的集合體
一個CTFrame有幾個CTLine組成,有幾行文字就有幾行CTLine。一個CTLine有包含多個CTRun,一個CTRun是所有屬性都相同的那部分富文本的繪制單元。所以CTRun是CTFrame的基本繪制單元。
接著說我們的代碼。
為什么我獲取的數組需要進行類型轉換呢?因為CTFrameGetLines()返回值是CFArrayRef類型的數據。就是一個c的數組類型吧,暫且先這么理解,所以需要轉換。
那為什么不用__bridge呢?記得么,我說過,本身就傳地址的數據是不用橋接的。就是這樣。
然后獲取數組的元素個數。有什么用呢,因為我們要用到每個CTLine的原點坐標進行計算。每個CTLine都有自己的origin。所以要生成一個相同元素個數的數組去盛放origin對象。
然后用CTFrameGetLineOrigins獲取所有原點。
到此,我們計算frame的準備工作完成了。才完成準備工作。
思路呢,就是遍歷我們的frame中的所有CTRun,檢查他是不是我們綁定圖片的那個,如果是,根據該CTRun所在CTLine的origin以及CTRun在CTLine中的橫向偏移量計算出CTRun的原點,加上其尺寸即為該CTRun的尺寸。
跟繞口令是的,不過就是這么個思路。
for(inti =0; i < count; i ++) {? ? ? ? CTLineRef line = (__bridge CTLineRef)arrLines[i];NSArray* arrGlyphRun = (NSArray*)CTLineGetGlyphRuns(line);for(intj =0; j < arrGlyphRun.count; j ++) {? ? ? ? ? ? CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);? ? ? ? ? ? CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];if(delegate ==nil) {continue;? ? ? ? ? ? }NSDictionary* dic = CTRunDelegateGetRefCon(delegate);if(![dic isKindOfClass:[NSDictionaryclass]]) {continue;? ? ? ? ? ? }CGPointpoint = points[i];CGFloatascent;CGFloatdescent;CGRectboundsRun;? ? ? ? ? ? boundsRun.size.width= CTRunGetTypographicBounds(run,CFRangeMake(0,0), &ascent, &descent,NULL);? ? ? ? ? ? boundsRun.size.height= ascent + descent;CGFloatxOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location,NULL);? ? ? ? ? ? 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;
有了上面的思路這里就很好理解了。
外層for循環呢,是為了取到所有的CTLine。
類型轉換什么的我就不多說了,然后通過CTLineGetGlyphRuns獲取一個CTLine中的所有CTRun。
里層for循環是檢查每個CTRun。
通過CTRunGetAttributes拿到該CTRun的所有屬性。
通過kvc取得屬性中的代理屬性。
接下來判斷代理屬性是否為空。因為圖片的占位符我們是綁定了代理的,而文字沒有。以此區分文字和圖片。
如果代理不為空,通過CTRunDelegateGetRefCon取得生成代理時綁定的對象。判斷類型是否是我們綁定的類型,防止取得我們之前為其他的富文本綁定過代理。
如果兩條都符合,ok,這就是我們要的那個CTRun。
開始計算該CTRun的frame吧。
獲取原點和獲取寬高被。
通過CTRunGetTypographicBounds取得寬,ascent和descent。有了上面的介紹我們應該知道圖片的高度就是ascent+descent了吧。
接下來獲取原點。
CTLineGetOffsetForStringIndex獲取對應CTRun的X偏移量。
取得對應CTLine的原點的Y,減去圖片的下邊距才是圖片的原點,這點應該很好理解。
至此,我們已經獲得了圖片的frame了。因為只綁定了一個圖片,所以直接return就好了,如果多張圖片可以繼續遍歷返回數組。
獲取到圖片的frame,我們就可以繪制圖片了,用上面介紹的方法。
哦,別忘了手動釋放你創建的對象哦。
CFRelease(frame)CFRelease(path)CFRelease(frameSetter)
大功告成。
好了,至此你已經完成圖片的繪制了。只要在ViewController里面引入你繪制CoreText文本的View正常的初始化添加子視圖就可以了。
好吧,這個教程我也是綜合了很多資料寫出來的。優勢是在于我一句一句講的,幾乎每一句都告訴你原理了吧。
恩,我也是在前人的基礎上自己總結查閱出來的,難免夾雜著個人理解和部分偏頗,如果各位看官發現我寫的有什么不對的地方歡迎與我聯系,老司機的郵箱codewicky@163.com。
原諒老司機逗逼的本質,嚴肅不起來。
下面是一些參考資料:
最后,你問我為什么一直叫自己老司機?哦,因為嘿嘿嘿~~~
哦,最后的最后,若果真有人轉載的話,麻煩你注明出處。
http://www.lxweimin.com/p/6db3289fb05d
文/老司機Wicky(簡書作者)
原文鏈接:http://www.lxweimin.com/p/6db3289fb05d
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。