日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用CoreText实现图文混排

發(fā)布時間:2024/4/13 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用CoreText实现图文混排 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

OS沒有現(xiàn)成的支持圖文混排的控件,而要用多個基礎(chǔ)控件組合拼成圖文混排這樣復(fù)雜的排版,是件很苦逼的事情。對此的解決方案有使用CoreText進(jìn)行繪制,或者使用TextKit。本文主要講解對于CoreText的使用。

案例下載地址

https://github.com/ClavisJ/CoreTextDemo

環(huán)境信息:

Mac OS X 10.10.1

Xcode 6.1.1

iOS 8.1

正文:

一、Core Text簡介

CoreText是基于IOS3.2及OSX10.5的用于文字精細(xì)排版的文本框架。它直接與Core Graphics(又稱:Quartz)交互,將需要顯示的文本內(nèi)容,位置,字體,字形直接傳遞給Quartz,與其他UI組件相比,能更高效的進(jìn)行渲染。

Core Text 架構(gòu)圖

?

二、CoreText與UIWebView在排版方面的優(yōu)劣比較

UIWebView也常用于處理復(fù)雜的排版,對應(yīng)排版他們之間的優(yōu)劣如下(摘自 《iOS開發(fā)進(jìn)階》—— 唐巧):

  • CoreText占用的內(nèi)容更少,渲染速度更快。UIWebView占用的內(nèi)存多,渲染速度慢。

  • CoreText在渲染界面的前就可以精確地獲得顯示內(nèi)容的高度(只要有了CTFrame即可),而WebView只有渲染出內(nèi)容后,才能獲得內(nèi)容的高度(而且還需要用JavaScript代碼來獲取)。

  • CoreText的CTFrame可以在后臺線程渲染,UIWebView的內(nèi)容只能在主線程(UI線程)渲染。

  • 基于CoreText可以做更好的原生交互效果,交互效果可以更加細(xì)膩。而UIWebView的交互效果都是用JavaScript來實現(xiàn)的,在交互效果上會有一些卡頓的情況存在。例如,在UIWebView下,一個簡單的按鈕按下的操作,都無法做出原生按鈕的即時和細(xì)膩的按下效果。

CoreText排版的劣勢:

  • CoreText渲染出來的內(nèi)容不能像UIWebView那樣方便地支持內(nèi)容的復(fù)制。

  • 基于CoreText來排版需要自己處理很多復(fù)制的邏輯,例如需要自己處理圖片與文字混排相關(guān)的邏輯,也需要自己實現(xiàn)連接點擊操作的支持。

在業(yè)界有很多應(yīng)用都采用CoreText技術(shù)進(jìn)行排版,例如新浪微博客戶端,多看閱讀客戶端,猿題庫等等。

?

三、繪制純文本

我們創(chuàng)建一個繼承于UIView的類,重寫他的drawRect方法,來繪制純文本。

-?(void)drawRect:(CGRect)rect?{[super?drawRect:rect];????//?步驟1:得到當(dāng)前用于繪制畫布的上下文,用于后續(xù)將內(nèi)容繪制在畫布上//?因為Core?Text要配合Core?Graphic?配合使用的,如Core?Graphic一樣,繪圖的時候需要獲得當(dāng)前的上下文進(jìn)行繪制CGContextRef?context?=?UIGraphicsGetCurrentContext();????//?步驟2:翻轉(zhuǎn)當(dāng)前的坐標(biāo)系(因為對于底層繪制引擎來說,屏幕左下角為(0,0))CGContextSetTextMatrix(context,?CGAffineTransformIdentity);????CGContextTranslateCTM(context,?0,?self.bounds.size.height);????CGContextScaleCTM(context,?1.0,?-1.0);????//?步驟3:創(chuàng)建繪制區(qū)域CGMutablePathRef?path?=?CGPathCreateMutable();????CGPathAddEllipseInRect(path,?NULL,?self.bounds);????//?步驟4:創(chuàng)建需要繪制的文字與計算需要繪制的區(qū)域NSMutableAttributedString?*attrString?=?[[NSMutableAttributedString?alloc]?initWithString:@"iOS程序在啟動時會創(chuàng)建一個主線程,而在一個線程只能執(zhí)行一件事情,如果在主線程執(zhí)行某些耗時操作,例如加載網(wǎng)絡(luò)圖片,下載資源文件等會阻塞主線程(導(dǎo)致界面卡死,無法交互),所以就需要使用多線程技術(shù)來避免這類情況。iOS中有三種多線程技術(shù)?NSThread,NSOperation,GCD,這三種技術(shù)是隨著IOS發(fā)展引入的,抽象層次由低到高,使用也越來越簡單。"];????//?步驟5:根據(jù)AttributedString生成CTFramesetterRefCTFramesetterRef?frameSetter?=?CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);CTFrameRef?frame?=?CTFramesetterCreateFrame(frameSetter,?CFRangeMake(0,?[attrString?length]),?path,?NULL);????//?步驟6:進(jìn)行繪制CTFrameDraw(frame,?context);????//?步驟7.內(nèi)存管理CFRelease(frame);????CFRelease(path);????CFRelease(frameSetter); }

運行的效果如下圖

CoreText繪制純文本

?

四、關(guān)于坐標(biāo)系

上訴代碼的步驟2對繪圖的坐標(biāo)系進(jìn)行了處理,因為在iOS UIKit中,UIView是以左上角為原點,而Core Text一開始的定位是使用與桌面應(yīng)用的排版系統(tǒng),桌面應(yīng)用的坐標(biāo)系是以左下角為原點,即Core Text在繪制的時候也是參照左下角為原點進(jìn)行繪制的,所以需要對當(dāng)前的坐標(biāo)系進(jìn)行處理。

實際上,Core Graphic 中的context也是以左下角為原點的, 但是為什么我們用Core Graphic 繪制一些簡單的圖形的時候不需要對坐標(biāo)系進(jìn)行處理喃,是因為通過這個方法UIGraphicsGetCurrentContext()來獲得的當(dāng)前context是已經(jīng)被處理過的了,用下面方法可以查看指定的上下文的當(dāng)前圖形狀態(tài)變換矩陣。

NSLog(@"當(dāng)前context的變換矩陣?%@",?NSStringFromCGAffineTransform(CGContextGetCTM(context)));

打印結(jié)果為[2, 0, 0, -2, 0, 654],可以發(fā)現(xiàn)變換矩陣與CGAffineTransformIdentity的值[1, 0, 0, 1, 0, 0]是不相同的,并且與設(shè)備是否為Retina屏和設(shè)備尺寸相關(guān)。他的作用是將上下文空間坐標(biāo)系進(jìn)行翻轉(zhuǎn),并使原來的左下角原點變成右上角是原點,并將向上為正y軸變?yōu)橄蛳聻檎齳軸。 所以在使用drawRect的時候,當(dāng)前的context已經(jīng)被做了一次翻轉(zhuǎn),如果不對當(dāng)前的坐標(biāo)系進(jìn)行處理,會發(fā)現(xiàn),繪制出來的文字是鏡像上下顛倒的,如圖

不處理context

所以需要先重置當(dāng)前的坐標(biāo)系翻轉(zhuǎn)狀態(tài),在進(jìn)行一次翻轉(zhuǎn),處理之后的矩陣為[2, 0, -0, 2, 0, 0],函數(shù)CGContextTranslateCTM的作用變換坐標(biāo)系中的原點,函數(shù)CGContextScaleCTM的作用是改變用戶坐標(biāo)系統(tǒng)的規(guī)模比例。

?

五、自定義文本的顏色,字體與行間距

可以看到我們使用了NSMutableAttributedString這個類來描述需要繪制的文字,而一個NSMutableAttributedString對象可以包含很多屬性,每一個屬性都有起對應(yīng)的字符區(qū)域,我們可以用這些屬性來描述文本中特殊的顏色和字體。

-?(void)drawRect:(CGRect)rect?{????//?省略前面的步驟1-4//?步驟8:設(shè)置部分文字顏色[attrString?addAttribute:(id)kCTForegroundColorAttributeName?value:[UIColor?greenColor]?range:NSMakeRange(10,?10)];????//?設(shè)置部分文字CGFloat?fontSize?=?20;CTFontRef?fontRef?=?CTFontCreateWithName((CFStringRef)@"ArialMT",?fontSize,?NULL);[attrString?addAttribute:(id)kCTFontAttributeName?value:(__bridge?id)fontRef?range:NSMakeRange(15,?10)];????CFRelease(fontRef);???//?設(shè)置行間距CGFloat?lineSpacing?=?10;????const?CFIndex?kNumberOfSettings?=?3;CTParagraphStyleSetting?theSettings[kNumberOfSettings]?=?{{kCTParagraphStyleSpecifierLineSpacingAdjustment,?sizeof(CGFloat),?&lineSpacing},{kCTParagraphStyleSpecifierMaximumLineSpacing,?sizeof(CGFloat),?&lineSpacing},{kCTParagraphStyleSpecifierMinimumLineSpacing,?sizeof(CGFloat),?&lineSpacing}};CTParagraphStyleRef?theParagraphRef?=?CTParagraphStyleCreate(theSettings,?kNumberOfSettings);[attrString?addAttribute:(id)kCTParagraphStyleAttributeName?value:(__bridge?id)theParagraphRef?range:NSMakeRange(0,?attrString.length)];????CFRelease(theParagraphRef);????//?省略之后的步驟5-7}

最終的效果如下

自定義文本屬性

提示:在配置NSMutableAttributedString?的Attribute的時候,用到了很多這樣的(__bridge?id)標(biāo)識,來解釋下:這個因為addAttribute:是OC的方法,需要Object C 對象,而CTParagraphStyleRef這些是由C語言實現(xiàn)的Core Foundation Framework 框架中的對象,這兩種類型可以相互轉(zhuǎn)換和操作。Core Foundation Framework 框架中的對象也有引用計數(shù)的概念,但是不是Cocoa Framework中的release/retain不同,而是使用自身的CFRetain/CFRelease接口,在使用的時候要多加注意引用和釋放的問題, 更加詳細(xì)的解釋可以參照這篇文章。

六、圖文混排

終于要開始進(jìn)行圖文混排了,上面說了那么多,我們來進(jìn)行一個小結(jié),下圖是CoreText繪制的流程圖與CTFrame和CTLine,CTRun之間的關(guān)系:

CoreText繪制的流程圖,CTFrame和CTLine CTRun之間的關(guān)系

?

我們來解釋一下這些類:

CFAttributedStringRef :屬性字符串,用于存儲需要繪制的文字字符和字符屬性

CTFramesetterRef:通過CFAttributedStringRef進(jìn)行初始化,作為CTFrame對象的生產(chǎn)工廠,負(fù)責(zé)根據(jù)path創(chuàng)建對應(yīng)的CTFrame

CTFrame:用于繪制文字的類,可以通過CTFrameDraw函數(shù),直接將文字繪制到context上

CTLine:在CTFrame內(nèi)部是由多個CTLine來組成的,每個CTLine代表一行

CTRun:每個CTLine又是由多個CTRun組成的,每個CTRun代表一組顯示風(fēng)格一致的文本

實際上CoreText是不直接支持繪制圖片的,但是我們可以先在需要顯示圖片的地方用一個特殊的空白占位符代替,同時設(shè)置該字體的CTRunDelegate信息為要顯示的圖片的寬度和高度,這樣繪制文字的時候就會先把圖片的位置留出來,再在drawRect方法里面用CGContextDrawImage繪制圖片。

-?(void)drawRect:(CGRect)rect?{[super?drawRect:rect];????//?省略步驟1-4??,步驟8//?步驟9:圖文混排部分//?CTRunDelegateCallbacks:一個用于保存指針的結(jié)構(gòu)體,由CTRun?delegate進(jìn)行回調(diào)CTRunDelegateCallbacks?callbacks;memset(&callbacks,?0,?sizeof(CTRunDelegateCallbacks));callbacks.version?=?kCTRunDelegateVersion1;callbacks.getAscent?=?ascentCallback;callbacks.getDescent?=?descentCallback;callbacks.getWidth?=?widthCallback;????//?圖片信息字典NSDictionary?*imgInfoDic?=?@{@"width":@100,@"height":@30};????//?設(shè)置CTRun的代理CTRunDelegateRef?delegate?=?CTRunDelegateCreate(&callbacks,?(__bridge?void?*)imgInfoDic);????//?使用0xFFFC作為空白的占位符unichar?objectReplacementChar?=?0xFFFC;????NSString?*content?=?[NSString?stringWithCharacters:&objectReplacementChar?length:1];????NSMutableAttributedString?*space?=?[[NSMutableAttributedString?alloc]?initWithString:content];????CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space,?CFRangeMake(0,?1),?kCTRunDelegateAttributeName,?delegate);????CFRelease(delegate);?//?將創(chuàng)建的空白AttributedString插入進(jìn)當(dāng)前的attrString中,位置可以隨便指定,不能越界[attrString?insertAttributedString:space?atIndex:50];????//?省略步驟5-6//?步驟10:繪制圖片UIImage?*image?=?[UIImage?imageNamed:@"coretext-img-1.png"];????CGContextDrawImage(context,?[self?calculateImagePositionInCTFrame:frame],?image.CGImage);???//?省略步驟7}?#pragma?mark?-?CTRun?delegate?回調(diào)方法static?CGFloat?ascentCallback(void?*ref)?{????return?[(NSNumber?*)[(__bridge?NSDictionary?*)ref?objectForKey:@"height"]?floatValue];}?static?CGFloat?descentCallback(void?*ref)?{????return?0;}?static?CGFloat?widthCallback(void?*ref)?{????return?[(NSNumber?*)[(__bridge?NSDictionary?*)ref?objectForKey:@"width"]?floatValue];}?/***??根據(jù)CTFrameRef獲得繪制圖片的區(qū)域**??@param?ctFrame?CTFrameRef對象**??@return繪制圖片的區(qū)域*/-?(CGRect)calculateImagePositionInCTFrame:(CTFrameRef)ctFrame?{????//?獲得CTLine數(shù)組NSArray?*lines?=?(NSArray?*)CTFrameGetLines(ctFrame);????NSInteger?lineCount?=?[lines?count];????CGPoint?lineOrigins[lineCount];CTFrameGetLineOrigins(ctFrame,?CFRangeMake(0,?0),?lineOrigins);????//?遍歷每個CTLinefor?(NSInteger?i?=?0?;?i?<?lineCount;?i++)?{CTLineRef?line?=?(__bridge?CTLineRef)lines[i];????????NSArray?*runObjArray?=?(NSArray?*)CTLineGetGlyphRuns(line);????????//?遍歷每個CTLine中的CTRunfor?(id?runObj?in?runObjArray)?{CTRunRef?run?=?(__bridge?CTRunRef)runObj;????????????NSDictionary?*runAttributes?=?(NSDictionary?*)CTRunGetAttributes(run);CTRunDelegateRef?delegate?=?(__bridge?CTRunDelegateRef)[runAttributes?valueForKey:(id)kCTRunDelegateAttributeName];????????????if?(delegate?==?nil)?{????????????????continue;}????????????NSDictionary?*metaDic?=?CTRunDelegateGetRefCon(delegate);????????????if?(![metaDic?isKindOfClass:[NSDictionary?class]])?{????????????????continue;}????????????CGRect?runBounds;????????????CGFloat?ascent;????????????CGFloat?descent;runBounds.size.width?=?CTRunGetTypographicBounds(run,?CFRangeMake(0,?0),?&ascent,?&descent,?NULL);runBounds.size.height?=?ascent?+?descent;????????????CGFloat?xOffset?=?CTLineGetOffsetForStringIndex(line,?CTRunGetStringRange(run).location,?NULL);runBounds.origin.x?=?lineOrigins[i].x?+?xOffset;runBounds.origin.y?=?lineOrigins[i].y;runBounds.origin.y?-=?descent;????????????CGPathRef?pathRef?=?CTFrameGetPath(ctFrame);????????????CGRect?colRect?=?CGPathGetBoundingBox(pathRef);????????????CGRect?delegateBounds?=?CGRectOffset(runBounds,?colRect.origin.x,?colRect.origin.y);????????????return?delegateBounds;}}????return?CGRectZero;}

?至此我們就完成了使用CoreText進(jìn)行圖文混排,上面獲得圖片位置的方法只能獲得第一張圖片位置,大家可以自行完善一下,用數(shù)組來進(jìn)行存儲圖片繪制區(qū)域。唐巧在《iOS開發(fā)進(jìn)階》一書中更多的介紹了對CoreText的封裝,感興趣的可以看看。


轉(zhuǎn)載于:https://my.oschina.net/u/2361492/blog/526814

總結(jié)

以上是生活随笔為你收集整理的使用CoreText实现图文混排的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。