CoreText(四):图文混排
程序员文章站
2023-11-21 20:06:22
在一个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后边拼接上宽高信息的方式来处理。
上一篇: PhotoShop(PS)模仿绘制超逼真的佳能6D相机实例教程
下一篇: 自动备份Oracle数据库