在很多新聞類或有文字展示的應用中現在都會出現圖文混排的界面例如網易新聞等,乍一看去相似一個網頁,其實這樣效果並非由UIWebView 加載網頁實現。現在分享一種比較簡單的實現方式
iOS sdk中爲我們提供了一套完善的文字排版開發組件:CoreText。CoreText庫中提供了很多的工具來對文本進行操作,例如CTFont、CTLine、CTFrame等。利用這些工具可以對文字字體每一行每一段落進行操作。
此例中默認圖片都在右上方,且爲了美觀和開發簡便設定所佔寬度都相同。
1. 首先,需要引入CoreText庫
在需要使用的類文件中添加#import <CoreText/CoreText.h>頭文件。
2. 設置文本的參數
創建一個NSMutableAttributedString對象,包含所需展示的文本字符串。這樣就可以對其進行設置了。通過CTFontCreateWithName函數創建一個CTFont對象,利用NSMutableAttributedString對象的addAttribute方法進行設置。類似的方法可以設置字間距。
對其方式與行間距的設置方式:
- // 文本對齊方式
- CTTextAlignment alignment = kCTLeftTextAlignment;
- CTParagraphStyleSetting alignmentStyle;
- alignmentStyle.spec = kCTParagraphStyleSpecifierAlignment;
- alignmentStyle.valueSize = sizeof(alignment);
- alignmentStyle.value = &alignment;
- // 創建設置數組
- CTParagraphStyleSetting settings[] ={alignmentStyle};
- CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 1);
同樣使用addAttribute設置字符串對象。這樣的方法還可以設置行間距,段間距等參數。
3. 計算圖片所佔高度。圖片可以使用UIImageView 來進行顯示。很容易便可獲取每張圖片所佔總高度。
4. 由於圖片寬度是固定的這樣就可以計算每行文字縮短的字數。只要文本的總體高度低於圖像總高度則文字長度都是縮短的。用CTTypesetterSuggestLineBreak函數動態的計算每一行裏的字數,因爲每一行裏面的中文字、標點符號、數字、字母都不一樣所以可以顯示的字數肯定也是不同的,所以需要作這樣的計算。這樣循環直至文本結束,就可以知道有多少行字了。再根據字體高度和行間距得出總的文本高度,如果文本高度大於圖片總高度那麼顯示區域的Frame高度就是文本的高度,反之亦然。
5. 繪製文本:
設置每一行繪製文本的區間:
- CFRange lineRange = CFRangeMake(currentIndex, lineLength);
- 建立文本行對象
- CTLineRef line = CTTypesetterCreateLine(typeSetter, lineRange);
- CGFloat x = [self textOffsetForLine:line inRect:self.bounds];
- // 設置一行的位置
- CGContextSetTextPosition(context, x, y);
- // 繪製一行文字
- CTLineDraw(line, context);
6. 其他功能:
在完成文本繪製功能後可以加入調整文字大小的功能,和圖片的放大的功能。
文字大小可以通過直接設置字體大小後重新繪製文本來實現。
圖片放大可以在視圖上添加一個新的UIImageView 來展示放大後的圖片,並且加入動畫效
實現代碼:
- void RunDelegateDeallocCallback( void* refCon ){
- }
- CGFloat RunDelegateGetAscentCallback( void *refCon ){
- NSString *imageName = (NSString *)refCon;
- return 80;//[UIImage imageNamed:imageName].size.height;
- }
- CGFloat RunDelegateGetDescentCallback(void *refCon){
- return 0;
- }
- CGFloat RunDelegateGetWidthCallback(void *refCon){
- NSString *imageName = (NSString *)refCon;
- return 100;//[UIImage imageNamed:imageName].size.width;
- }
先設置一個CTRun的委託,主要是用於指定對象的上行高,寬,或上下文釋放時使用。
- -(void)drawCharAndPicture
- {
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);//設置字形變換矩陣爲CGAffineTransformIdentity,也就是說每一個字形都不做圖形變換
- CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);
- CGContextConcatCTM(context, flipVertical);//將當前context的座標系進行flip
- NSLog(@"bh=%f",self.bounds.size.height);
- NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] initWithString:@"請在這裏插入一張圖片位置"] autorelease];
- //爲圖片設置CTRunDelegate,delegate決定留給圖片的空間大小
- NSString *imgName = @"img.png";
- CTRunDelegateCallbacks imageCallbacks;
- imageCallbacks.version = kCTRunDelegateVersion1;
- imageCallbacks.dealloc = RunDelegateDeallocCallback;
- imageCallbacks.getAscent = RunDelegateGetAscentCallback;
- imageCallbacks.getDescent = RunDelegateGetDescentCallback;
- imageCallbacks.getWidth = RunDelegateGetWidthCallback;
- CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, imgName);
- NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@" "];//空格用於給圖片留位置
- [imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(id)runDelegate range:NSMakeRange(0, 1)];
- CFRelease(runDelegate);
- [imageAttributedString addAttribute:@"imageName" value:imgName range:NSMakeRange(0, 1)];
- [attributedString insertAttributedString:imageAttributedString atIndex:4];
- //換行模式
- CTParagraphStyleSetting lineBreakMode;
- CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
- lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
- lineBreakMode.value = &lineBreak;
- lineBreakMode.valueSize = sizeof(CTLineBreakMode);
- CTParagraphStyleSetting settings[] = {
- lineBreakMode
- };
- CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 1);
- // build attributes
- NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(id)style forKey:(id)kCTParagraphStyleAttributeName ];
- // set attributes to attributed string
- [attributedString addAttributes:attributes range:NSMakeRange(0, [attributedString length])];
- CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);
- CGMutablePathRef path = CGPathCreateMutable();
- CGRect bounds = CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height);
- CGPathAddRect(path, NULL, bounds);
- CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0, 0), path, NULL);
- CTFrameDraw(ctFrame, context);
- CFArrayRef lines = CTFrameGetLines(ctFrame);
- CGPoint lineOrigins[CFArrayGetCount(lines)];
- CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
- NSLog(@"line count = %ld",CFArrayGetCount(lines));
- for (int i = 0; i < CFArrayGetCount(lines); i++) {
- CTLineRef line = CFArrayGetValueAtIndex(lines, i);
- CGFloat lineAscent;
- CGFloat lineDescent;
- CGFloat lineLeading;
- CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
- NSLog(@"ascent = %f,descent = %f,leading = %f",lineAscent,lineDescent,lineLeading);
- CFArrayRef runs = CTLineGetGlyphRuns(line);
- NSLog(@"run count = %ld",CFArrayGetCount(runs));
- for (int j = 0; j < CFArrayGetCount(runs); j++) {
- CGFloat runAscent;
- CGFloat runDescent;
- CGPoint lineOrigin = lineOrigins[i];
- CTRunRef run = CFArrayGetValueAtIndex(runs, j);
- NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
- CGRect runRect;
- runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);
- NSLog(@"width = %f",runRect.size.width);
- runRect=CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);
- NSString *imageName = [attributes objectForKey:@"imageName"];
- //圖片渲染邏輯
- if (imageName) {
- UIImage *image = [UIImage imageNamed:imageName];
- if (image) {
- CGRect imageDrawRect;
- imageDrawRect.size = image.size;
- imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x;
- imageDrawRect.origin.y = lineOrigin.y;
- CGContextDrawImage(context, imageDrawRect, image.CGImage);
- }
- }
- }
- }
- CFRelease(ctFrame);
- CFRelease(path);
- CFRelease(ctFramesetter);
- }
效果:
從上面看大家可能沒有發現什麼問題,當把圖片放在字的最左邊會是什麼樣子的?
因此爲了避免這種情況發生,我在代碼中添加了換行模式。添加換行後的效果: