IOS CoreText --- 圖文混排之代碼封裝

來源:互聯網
上載者:User

IOS CoreText --- 圖文混排之代碼封裝

上一節中,我詳細的講解了用物件導向的思想將Core Text的純C語言的代碼進行了封裝。這一節,我將對“圖文混排”的效果也進行封裝工作。不過,這一節的代碼是基於上一節的,所以,如果你沒有瀏覽過上一節的內容,請點擊這裡。先看看最終的:

 

現在,我們就來對上一節的代碼,繼續擴充。

1. 添加了圖片資訊,所以我們需要修改資料來源(plist)的結構

1)為每一項添加了type資訊,“txt”表示純文字;“img”表示圖片;圖片資訊包括name,width,height。 name就是圖片的地址,我這裡是儲存在沙箱中,實際開發的時候,可以載入遠程圖片。

2)一定要提供圖片的width和height資訊,因為Core Text排版是要計算每一個元素的佔位大小的。如果不提供圖片的width和height資訊,用戶端在載入遠程圖片後,還要計算出width和height,效率低下,如果在網路比較差的情況下,圖片一直載入不到,那麼Core Text排版就明顯混亂了;如果服務端資料提供了width和height資訊,就算圖片沒有載入過來,也可以有同等大小的空白地區被佔位著,不影響整體的布局。

 

2. 定義CoreTextImageData模型,用於儲存圖片的名稱及位置資訊

 

@interface CoreTextImageData : NSObject@property (nonatomic,copy) NSString *name;// 此座標是 CoreText 的座標系,而不是UIKit的座標系@property (nonatomic,assign) CGRect imagePosition;@end

 

 

3. CoreTextData類中應該包含CoreTextImageData模型資訊,這裡用的是數組imageArray,因為有可能包含多張圖片。所以改造一下CoreTextData類,CoreTextData.h代碼如下:

 

@interface CoreTextData : NSObject@property (nonatomic,assign) CTFrameRef ctFrame;@property (nonatomic,assign) CGFloat height;@property (nonatomic,strong) NSArray *imageArray;@end

 

4. 改造CTFrameParser類中的parseTemplateFile方法,使其包含CoreTextImageData資訊

 

+ (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config {    NSMutableArray *imageArray = [NSMutableArray array];    NSAttributedString *content = [self loadTemplateFile:path config:config imageArray:imageArray];    CoreTextData *data = [self parseAttributedContent:content config:config];    data.imageArray = imageArray;    return data;}

5. 在loadTemplateFile方法添加支援image的代碼, 這樣,就將plist中img的相關資訊儲存到CoreTextImageData模型中了。
但是問題來了,Core Text本身並不支援對圖片的展示功能!但是,我們可以在要顯示文本的地方,用一個特殊的空白字元代替,同時設定該字型的CTRunDelegate資訊為要顯示的圖片的寬度和高度,這樣最後產生的CTFrame執行個體,就會在繪製時將圖片的位置預留下來。因為CTDisplayView的繪製代碼是在drawRect裡面的,所以我們可以方便的把需要繪製的圖片,用Quartz 2D的CGContextDrawImage方法直接繪製出來就行了。我這裡所描述的流程,就是在調用的parseImageDataFromNSDictionary中實現的。

 

 

+ (NSAttributedString *)loadTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config  imageArray:(NSMutableArray *)imageArray{    NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];    // JSON方式擷取資料    //        NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];    NSArray *array = [NSArray arrayWithContentsOfFile:path];    if (array) {        if ([array isKindOfClass:[NSArray class]]) {            for (NSDictionary *dict in array) {                NSString *type = dict[@"type"];                if ([type isEqualToString:@"txt"]) {                    NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config];                    [result appendAttributedString:as];                } else if ([type isEqualToString:@"img"]) {                    CoreTextImageData *imageData = [[CoreTextImageData alloc] init];                    imageData.name = dict[@"name"];                    [imageArray addObject:imageData];                    NSAttributedString *as = [self parseImageDataFromNSDictionary:dict config:config];                    [result appendAttributedString:as];                }            }        }    }    return result;}

 

6. 佔位字元及設定佔位字元的CTRunDelegate,代碼中是用'0xFFFC'這個字元進行佔位的。

 

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];}+ (NSAttributedString *)parseImageDataFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig *)config {    CTRunDelegateCallbacks callbacks;    // memset將已開闢記憶體空間 callbacks 的首 n 個位元組的值設為值 0, 相當於對CTRunDelegateCallbacks記憶體空間初始化    memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));    callbacks.version = kCTRunDelegateVersion1;    callbacks.getAscent = ascentCallback;    callbacks.getDescent = descentCallback;    callbacks.getWidth = widthCallback;        CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(dict));    // 使用0xFFFC 作為空白的預留位置    unichar objectReplacementChar = 0xFFFC;    NSString *content = [NSString stringWithCharacters:&objectReplacementChar length:1];    NSDictionary *attributes = [self attributesWithConfig:config];    NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:content attributes:attributes];    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);    CFRelease(delegate);        return space;}

7. 在5,6 兩點的代碼執行完畢後,代碼會返回到第4點,執行下面這句代碼:

 

 

data.imageArray = imageArray;

它實際上就是重寫了CoreTextData中的imageArray屬性方法,下面代碼的目的就是計算空白字元的實際佔位大小。對下面的代碼,我進行大致的說明:

 

1) 通過調用CTFrameGetLines方法獲得所有的CTLine。

2)通過調用CTFrameGetLineOrigins方法擷取每一行的起始座標。

3)通過調用CTLineGetGlyphRuns方法,擷取每一行所有的CTRun。

4)通過CTRun的attributes資訊找到key為CTRunDelegateAttributeName的資訊,如果存在,表明他就是佔位字元,否則的話直接過濾掉。

5)最終計算獲得每一個佔位字元的實際尺寸大小。

 

- (void)setImageArray:(NSArray *)imageArray {    _imageArray = imageArray;    [self fillImagePosition];}- (void)fillImagePosition {    if (self.imageArray.count == 0) return;    NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrame);    int lineCount = lines.count;    // 每行的起始座標    CGPoint lineOrigins[lineCount];    CTFrameGetLineOrigins(self.ctFrame, CFRangeMake(0, 0), lineOrigins);        int imageIndex = 0;    CoreTextImageData *imageData = self.imageArray[0];    for (int i = 0; i < lineCount; i++) {        if (!imageData) break;                CTLineRef line = (__bridge CTLineRef)(lines[i]);        NSArray *runObjectArray = (NSArray *)CTLineGetGlyphRuns(line);        for (id runObject in runObjectArray) {            CTRunRef run = (__bridge CTRunRef)(runObject);            NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)([runAttributes valueForKey:(id)kCTRunDelegateAttributeName]);            // 如果delegate是空,表明不是圖片            if (!delegate) continue;                        NSDictionary *metaDict = CTRunDelegateGetRefCon(delegate);            if (![metaDict isKindOfClass:[NSDictionary class]]) continue;                        /* 確定圖片run的frame */            CGRect runBounds;            CGFloat ascent,descent;            runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);            runBounds.size.height = ascent + descent;            // 計算出圖片相對於每行起始位置x方向上面的位移量            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;                imageData.imagePosition = runBounds;            imageIndex++;            if (imageIndex == self.imageArray.count) {                imageData = nil;                break;            } else {                imageData = self.imageArray[imageIndex];            }        }    }}

8. 改造CTDisplayView中的代碼,完成繪製工作。

 

1)先調用CTFrameDraw方法完成整體的繪製,此時圖片地區就是圖片實際大小的一片空白顯示。

2)遍曆CoreTextData中的imageArray數組,使用CGContextDrawImage方法在對應的空白地區繪製圖片。

 

- (void)drawRect:(CGRect)rect {    [super drawRect:rect];    CGContextRef context = UIGraphicsGetCurrentContext();    CGContextSetTextMatrix(context, CGAffineTransformIdentity);    CGContextTranslateCTM(context, 0, self.bounds.size.height);    CGContextScaleCTM(context, 1.0, -1.0);    // 先整體繪製    if (self.data) {        CTFrameDraw(self.data.ctFrame, context);    }    // 繪製出圖片    for (CoreTextImageData *imageData in self.data.imageArray) {        UIImage *image = [UIImage imageNamed:imageData.name];        if (image) {            CGContextDrawImage(context, imageData.imagePosition, image.CGImage);        }    }}

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.