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);
        }
    }
}
时间: 2024-10-10 16:40:58

IOS CoreText --- 图文混排之代码封装的相关文章

iOS coretext图文混排

需要引入CoreText框架 然后引入头文件 至于用这个框架排出来的版就是自定义cell决定的了,在这里我们可以引用一个第三方的自定义的cell 给从接口里申请下来的数据创建一个Model接收 初始化在字典里 在需要图文混排的页面 定义一个数组的属性,在viewDodLoad里创建一个个字典并把字典都加到数组里去, 在给字典格式的时候可以把字体的大小和颜色设置好,如果一次要给好几个显示的内容那么就像下面这么写 注意把他们存在字典里的key都是text 现在就是需要把数组里的数据显示在cell上面

简单的Coretext 图文混排

在很多新闻类或有文字展示的应用中现在都会出现图文混排的界面例如网易新闻等,乍一看去相似一个网页,其实这样效果并非由UIWebView 加载网页实现.现在分享一种比较简单的实现方式 iOS sdk中为我们提供了一套完善的文字排版开发组件:CoreText.CoreText库中提供了很多的工具来对文本进行操作,例如CTFont.CTLine.CTFrame等.利用这些工具可以对文字字体每一行每一段落进行操作. 此例中默认图片都在右上方,且为了美观和开发简便设定所占宽度都相同. 1.        

iOS 简单图文混排01

1.在Label中显示图片 // 图文混排显示 - (void)setLabel { // NSTextAttachment - 附件 NSTextAttachment *attachMent = [[NSTextAttachment alloc] init]; // 为附件设置图片 attachMent.image = [UIImage imageNamed: @"d_aini"]; // 键附件添加到图文混排 NSAttributedString *str = [NSAttribu

iOS支持图文混排的按钮(UIButton)

创建UIButton子类 直接上代码了 .h文件 创建UIButton子类 直接上代码了 .h文件 #import <UIKit/UIKit.h> @interface GraphicBtn : UIButton @property (nonatomic,assign)CGRect titleRect; @property (nonatomic,assign)CGRect imageRect; @end .m文件   #import "GraphicBtn.h" @impl

CoreText实现图文混排之点击事件

今天呢,我们继续把CoreText图文混排的点击事件补充上,这样我们的图文混排也算是圆满了. 哦,上一篇的链接在这里 http://www.jianshu.com/p/6db3289fb05d CoreText实现图文混排.所有需要用到的准备知识都在上一篇,没有赶上车的朋友可以去补个票~ 上正文. CoreText做图文混排之点击事件 主要思路 我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有 -(void)touchesBegan:(NSSet)touches

iOS 图文混排 链接 可点击

对于这个话题 我想到 1 第一个解决方法就是使用 webView 比较经典 把所有复杂工作都交给控件本身去处理了,  但是好像好多需要自定义的地方 没法从 webView获得响应回调 :(估计也可以实现 也比较复杂,而且 这个需要对 html编码进行分析理解剥离等) 2 富文本方式 核心框架 coretext 图文混排 一点问题都没有 关键是怎么对 目标图片 或者链接 进行触发响应 要点: (1)首先要封装的要相对独立 拓展也方便  首当其冲就是  和服务端约定的 数据模型 CoreTextMo

Coretext实现富文本图文混排及Gif图片播放

CoreText是iOS3.2推出的一套文字排版和渲染框架,可以实现图文混排,富文本显示等效果. CoreText中的几个重要的概念:  CTFont CTFontCollection CTFontDescriptor CTFrame CTFramesetter CTGlyphInfo CTLine CTParagraphStyle CTRun CTTextTab CTTypesetter 先来了解一下该框架的整体视窗组合图: CTFrame 作为一个整体的画布(Canvas),其中由行(CTL

TextKit实现图文混排

//    NSAttributedString   这个类可以设置文本属性:加粗.斜体.删除线.下划线... //    NSMutableAttributedString  可变属性文本:可以动态添加文本的属性 NSString *text = @"iOS实现图文混排的方式:1.WebView     html+javascript 2.CoreText   (C语言实现的框架) "; UITextView *textview = [[UITextView alloc] initW

【iOS】使用CoreText实现图文混排

iOS没有现成的支持图文混排的控件,而要用多个基础控件组合拼成图文混排这样复杂的排版,是件很苦逼的事情.对此的解决方案有使用CoreText进行绘制,或者使用TextKit.本文主要讲解对于CoreText的使用. 案例下载地址 https://github.com/ClavisJ/CoreTextDemo 环境信息: Mac OS X 10.10.1 Xcode 6.1.1 iOS 8.1 正文: 一.Core Text简介 CoreText是基于IOS3.2及OSX10.5的用于文字精细排版