欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

CoreText(四):图文混排

程序员文章站 2022-05-26 15:52:57
在一个uiview的子控件上实现图文混排显示,支持本地图片和网络图片的显示。 coretext从绘制纯文本到绘制图片,依然是使用nsattributedstring,只不过图片的实现方式是用一个空白...

在一个uiview的子控件上实现图文混排显示,支持本地图片和网络图片的显示。

coretext从绘制纯文本到绘制图片,依然是使用nsattributedstring,只不过图片的实现方式是用一个空白字符作为在nsattributedstring中的占位符,然后设置代理,告诉coretext给该占位字符留出一定的宽高。最后把图片绘制到预留的位置上。

1、图片的代理方法:

#pragma mark 图片代理
void rundelegatedealloccallback(void *refcon){
    nslog(@"rundelegate dealloc");
}
cgfloat rundelegategetascentcallback(void *refcon){
    nsstring *imagename = (__bridge nsstring *)refcon;
    if ([imagename iskindofclass:[nsstring class]]){
        // 对应本地图片
        return [uiimage imagenamed:imagename].size.height;
    }
    // 对应网络图片
    return [[(__bridge nsdictionary *)refcon objectforkey:@"height"] floatvalue];
}
cgfloat rundelegategetdescentcallback(void *refcon){
    return 0;
}
cgfloat rundelegategetwidthcallback(void *refcon){
    nsstring *imagename = (__bridge nsstring *)refcon;
    if ([imagename iskindofclass:[nsstring class]]){
        // 本地图片
        return [uiimage imagenamed:imagename].size.width;
    }
    // 对应网络图片
    return [[(__bridge nsdictionary *)refcon objectforkey:@"width"] floatvalue];
}

2、下载图片的方法

- (void)downloadimagewithurl:(nsurl *)url{
    __weak typeof(self) weakself = self;
    dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{
        sdwebimageoptions options = sdwebimageretryfailed | sdwebimagehandlecookies | sdwebimagecontinueinbackground;
        options = sdwebimageretryfailed | sdwebimagecontinueinbackground;
        [[sdwebimagemanager sharedmanager] downloadimagewithurl:url options:options progress:nil completed:^(uiimage *image, nserror *error, sdimagecachetype cachetype, bool finished, nsurl *imageurl) {
            weakself.image = image;
            nslog(@"%@",image);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (weakself.image)
                {
                    [weakself setneedsdisplay];
                }
            });
        }];
    });
}

3、图文混排

- (void)drawrect:(cgrect)rect {

    [super drawrect:rect];
    nsstring* title = @"在现实生活中,我们要不断内外兼修,几十载的人生旅途,看过这边风景,必然错过那边彩虹,有所得,必然有所失。有时,我们只有彻底做到拿得起,放得下,才能拥有一份成熟,才会活得更加充实、坦然、轻松和*。";

    //步骤1:获取上下文
    cgcontextref contextref = uigraphicsgetcurrentcontext();
    // [a,b,c,d,tx,ty]
    nslog(@"转换前的坐标:%@",nsstringfromcgaffinetransform(cgcontextgetctm(contextref)));

    //步骤2:翻转坐标系;
    cgcontextsettextmatrix(contextref, cgaffinetransformidentity);
    cgcontexttranslatectm(contextref, 0, self.bounds.size.height);
    cgcontextscalectm(contextref, 1.0, -1.0);
    nslog(@"转换后的坐标:%@",nsstringfromcgaffinetransform(cgcontextgetctm(contextref)));


    //步骤3:创建nsattributedstring
    nsmutableattributedstring *attributed = [[nsmutableattributedstring alloc] initwithstring:title];
    //设置字体大小
    [attributed addattribute:nsfontattributename value:[uifont systemfontofsize:20] range:nsmakerange(0, 5)];
    //设置字体颜色
    [attributed addattribute:nsforegroundcolorattributename value:[uicolor redcolor] range:nsmakerange(3, 10)];
    [attributed addattribute:(id)kctforegroundcolorattributename value:(id)[uicolor greencolor].cgcolor range:nsmakerange(0, 2)];
    // 设置行距等样式
    cgfloat linespace = 10; // 行距一般取决于这个值
    cgfloat linespacemax = 20;
    cgfloat linespacemin = 2;
    const cfindex knumberofsettings = 3;
    // 结构体数组
    ctparagraphstylesetting thesettings[knumberofsettings] = {
        {kctparagraphstylespecifierlinespacingadjustment,sizeof(cgfloat),&linespace},
        {kctparagraphstylespecifiermaximumlinespacing,sizeof(cgfloat),&linespacemax},
        {kctparagraphstylespecifierminimumlinespacing,sizeof(cgfloat),&linespacemin}
    };
    ctparagraphstyleref theparagraphref = ctparagraphstylecreate(thesettings, knumberofsettings);
    // 单个元素的形式
    //    ctparagraphstylesetting thesettings = {kctparagraphstylespecifierlinespacingadjustment,sizeof(cgfloat),&linespace};
    //    ctparagraphstyleref theparagraphref = ctparagraphstylecreate(&thesettings, knumberofsettings);
    // 两种方式皆可
    //    [attributed addattribute:(id)kctparagraphstyleattributename value:(__bridge id)theparagraphref range:nsmakerange(0, attributed.length)];
    // 将设置的行距应用于整段文字
    [attributed addattribute:nsparagraphstyleattributename value:(__bridge id)(theparagraphref) range:nsmakerange(0, attributed.length)];
    cfrelease(theparagraphref);
    // 插入图片部分
    //为图片设置ctrundelegate,delegate决定留给图片的空间大小
    nsstring *weicaiimagename = @"cloud.jpg";
    ctrundelegatecallbacks imagecallbacks;
    imagecallbacks.version = kctrundelegateversion1;
    imagecallbacks.dealloc = rundelegatedealloccallback;
    imagecallbacks.getascent = rundelegategetascentcallback;
    imagecallbacks.getdescent = rundelegategetdescentcallback;
    imagecallbacks.getwidth = rundelegategetwidthcallback;
    // ①该方式适用于图片在本地的情况
    // 设置ctrun的代理
    ctrundelegateref rundelegate = ctrundelegatecreate(&imagecallbacks, (__bridge void *)(weicaiimagename));
    nsmutableattributedstring *imageattributedstring = [[nsmutableattributedstring alloc] initwithstring:@" "];//空格用于给图片留位置
    [imageattributedstring addattribute:(nsstring *)kctrundelegateattributename value:(__bridge id)rundelegate range:nsmakerange(0, 1)];
    cfrelease(rundelegate);
    [imageattributedstring addattribute:@"imagename" value:weicaiimagename range:nsmakerange(0, 1)];
    // 在index处插入图片,可插入多张
    [attributed insertattributedstring:imageattributedstring atindex:5];
    //    [attributed insertattributedstring:imageattributedstring atindex:10];

    // ②若图片资源在网络上,则需要使用0xfffc作为占位符
    // 图片信息字典
    nsstring *picurl =@"https://www.baidu.com/img/bd_logo1.png";
    uiimage* pimage = [uiimage imagenamed:@"123.png"];
    nsdictionary *imginfodic = @{@"width":@(270),@"height":@(129)}; // 宽高跟具体图片有关
    // 设置ctrun的代理
    ctrundelegateref delegate = ctrundelegatecreate(&imagecallbacks, (__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);

    // 将创建的空白attributedstring插入进当前的attrstring中,位置可以随便指定,不能越界
    [attributed insertattributedstring:space atindex:10];


    //步骤4:根据nsattributedstring创建ctframesetterref
    ctframesetterref framesetterref = ctframesettercreatewithattributedstring((cfattributedstringref)attributed);

    //步骤5:创建绘制区域cgpathref
    cgmutablepathref pathref = cgpathcreatemutable();
    cgpathaddrect(pathref, null, self.bounds);

    //步骤6:根据ctframesetterref和cgpathref创建ctframe;
    ctframeref frameref = ctframesettercreateframe(framesetterref, cfrangemake(0, [attributed length]), pathref, null);

    //步骤7:ctframedraw绘制。
    ctframedraw(frameref, contextref);

    // 处理绘制图片的逻辑
    cfarrayref lines = ctframegetlines(frameref);
    cgpoint lineorigins[cfarraygetcount(lines)];
    // 把ctframe里每一行的初始坐标写到数组里
    ctframegetlineorigins(frameref, cfrangemake(0, 0), lineorigins);

    // 遍历ctrun找出图片所在的ctrun并进行绘制
    for (int i = 0; i < cfarraygetcount(lines); i++)
    {
        // 遍历每一行ctline
        ctlineref line = cfarraygetvalueatindex(lines, i);
        cgfloat lineascent;
        cgfloat linedescent;
        cgfloat lineleading; // 行距
        ctlinegettypographicbounds(line, &lineascent, &linedescent, &lineleading);
        cfarrayref runs = ctlinegetglyphruns(line);

        for (int j = 0; j < cfarraygetcount(runs); j++)
        {
            // 遍历每一个ctrun
            cgfloat runascent;
            cgfloat rundescent;
            cgpoint lineorigin = lineorigins[i]; // 获取该行的初始坐标
            ctrunref run = cfarraygetvalueatindex(runs, j); // 获取当前的ctrun
            nsdictionary* attributes = (nsdictionary*)ctrungetattributes(run);
            cgrect runrect;
            runrect.size.width = ctrungettypographicbounds(run, cfrangemake(0,0), &runascent, &rundescent, null);
            // 这一段可参考nimbus的niattributedlabel
            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 iskindofclass:[nsstring class]]){
                // 绘制本地图片
                uiimage *image = [uiimage imagenamed:imagename];
                cgrect imagedrawrect;
                imagedrawrect.size = image.size;
                nslog(@"%.2f",lineorigin.x); // 该值是0,runrect已经计算过起始值
                imagedrawrect.origin.x = runrect.origin.x;// + lineorigin.x;
                imagedrawrect.origin.y = lineorigin.y;
                cgcontextdrawimage(contextref, imagedrawrect, image.cgimage);
            } else {
                imagename = nil;
                ctrundelegateref delegate = (__bridge ctrundelegateref)[attributes objectforkey:(__bridge id)kctrundelegateattributename];
                if (!delegate){
                    continue; // 如果是非图片的ctrun则跳过
                }
                // 网络图片
                uiimage *image;
                if (!self.image){
                    // 图片未下载完成,使用占位图片
                    image = pimage;
                    // 去下载图片
                    [self downloadimagewithurl:[nsurl urlwithstring:picurl]];
                }else{
                    image = self.image;
                }
                // 绘制网络图片
                cgrect imagedrawrect;
                imagedrawrect.size = image.size;
                nslog(@"%.2f",lineorigin.x); // 该值是0,runrect已经计算过起始值
                imagedrawrect.origin.x = runrect.origin.x;// + lineorigin.x;
                imagedrawrect.origin.y = lineorigin.y;
                cgcontextdrawimage(contextref, imagedrawrect, image.cgimage);
            }
        }
    }
    //内存管理
    cfrelease(frameref);
    cfrelease(pathref);
    cfrelease(framesetterref);
}

本文实现了同时绘制本地图片和网络图片。大体思路是,网络图片还未下载时,先使用该图片的占位图片进行绘制(为了方便,占位图直接使用了另一张本地图片),然后使用sdwebimage框架提供的下载功能去下载网络图片,等下载完成时,调用uiview的setneeddisplay方法进行重绘即可。

需要注意的一点就是,对于本地图片,是可以直接拿到其宽高数据的,对于网络的图片,在下载完成之前不知道其宽高,我们往往会采取在其url后边拼接上宽高信息的方式来处理。
CoreText(四):图文混排