美文网首页tom
CoreTextDemo2 图文篇

CoreTextDemo2 图文篇

作者: coder_feng | 来源:发表于2020-08-11 14:21 被阅读0次

    本文介绍,在一个UIView的子控件上实现图文混排显示,支持本地图片和网络图片的显示,本文暂时不支持点击监听功能

    CoreTextDemo1 介绍了绘制文本,那么怎么绘制图片?其实CoreText从绘制文本到绘制图片,依然还是使用NSAttributedString,只不过图片的实现方式,首先需要用一个空白字符作为在NSArrtibutedString中的占位符,然后设置代理,并且通过代理回调告诉该占位字符保留一定的宽高,最后把图片绘制到insert占位符的位置上,下面会介绍绘制本地图片和网络图片

    网络图片还没有下载时候,先使用本地图片的占位图片进行绘制,下载完成后,再调用UIView的setNeedDisplay方法进行重绘即可,对于本地图片,是可以直接拿到其宽高数据的,对于网络的图片,在下载完成之前不知道其宽高,我们�往往会采取在其URL后边拼接上宽高信息的方式来处理

    • 展示代码

    图片的回调代理方法

    #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 RunDelegateDescentCallback(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];
    }
    

    上面的代码区别了本地图片和网络图片的宽高返回方式,本地图片直接读取,网络图片采用服务端返回的宽高字段

    下载图片方法

    - (void)downLoadImageWithURL:(NSURL *)url
    {
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //下载操作
            [[SDWebImageManager sharedManager] loadImageWithURL:url options:SDWebImageRetryFailed | SDWebImageHandleCookies | SDWebImageContinueInBackground progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
                weakSelf.image = image;
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (weakSelf.image) {
                        [self setNeedsDisplay];
                    }
                });
            }];
        });
    }
    

    这里通过pod引入了SDWebImage

    绘制方法

    - (void)drawRect:(CGRect)rect{
        [super drawRect:rect];
        //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.创建绘制区域,可以对path进行个性化裁剪以改变显示区域
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, self.bounds);
        //4.创建需要绘制的文字
        NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc]initWithString:@"这是我的第一个coreText demo,图片暂时不支持点击事件"];
        [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:[UIColor greenColor] range:NSMakeRange(0, 2)];
    
        // 设置行距等样式
        
        //设置行距等样式
        CGFloat lineSpace = 10;
        CGFloat lineSpaceMax = 20;
        CGFloat lineSpaceMin = 2;
        
        const CFIndex kNumberOfSettings = 3;
        
        //结构体数组
        CTParagraphStyleSetting settings[kNumberOfSettings] = {
            {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace},
            {kCTParagraphStyleSpecifierMaximumLineSpacing,(sizeof(CGFloat)),&lineSpaceMax},
            {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpaceMin}
        };
        
        CTParagraphStyleRef paragraphRef = CTParagraphStyleCreate(settings, 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)paragraphRef range:NSMakeRange(0, attributed.length)];
        
        CFRelease(paragraphRef);
        
        //插入图片部分,为图片设置CTRunDelegate,delegate决定留给图片的空间大小
        NSString *imageName = @"head_sample_up";
        CTRunDelegateCallbacks imageCallbacks;
        imageCallbacks.version = kCTRunDelegateVersion1;
        imageCallbacks.dealloc = RunDelegateDeallocCallback;
        imageCallbacks.getAscent = RunDelegateGetAscentCallback;
        imageCallbacks.getDescent = RunDelegateGetDescentCallback;
        imageCallbacks.getWidth = RunDelegateGetWidthCallback;
        
        //图片在本地的情况下
        //设置CTRun的代理
        CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, (__bridge void*)(imageName));
        //空格用于给图片留下位置
        NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc]initWithString:@" "];
        [imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:NSMakeRange(0, 1)];
        CFRelease(runDelegate);
        
        [imageAttributedString addAttribute:@"imageName" value:imageName range:NSMakeRange(0, 1)];
        //在index处插入图片,可插入多张
        [attributed insertAttributedString:imageAttributedString atIndex:5];
    //    [attributed insertAttributedString:imageAttributedString atIndex:10];
        
        //若图片资源在网络上,则需要使用0xFFFC作为占位符
        NSString *picURL = @"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2725216169,3839207469&fm=26&gp=0.jpg";
        //设置一个默认值,但是这个默认值和具体的图片有关,如果下载回来的小于这个值,可能绘图不准确
        NSDictionary *imgInfoDic = @{@"width":@500,@"height":@342};
        //设置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];
        
        //5.根据NSAttributedString生成CTFramesetterRef
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
        CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);
        //6.绘制图片以外的部分
        CTFrameDraw(ctFrame, contextRef);
        
        
        //处理绘制图片的逻辑
        CFArrayRef lines = CTFrameGetLines(ctFrame);
        CGPoint lineOrigins[CFArrayGetCount(lines)];
        
        //把ctFrame里每一行的初始坐标写到数组里面去
        CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
        
       //遍历CTLine找出图片所在的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);
            long runCount = CFArrayGetCount(runs);
            for (int j = 0; j < runCount; j++) {
                //遍历每一个CTRun
                CGFloat runAscent;
                CGFloat runDescent;
                CGPoint lineOrigin = lineOrigins[i];//获取该行的初始坐标
                //获取当前的CTRun
                CTRunRef run = CFArrayGetValueAtIndex(runs, j);
                NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
                CGRect runRect;
                runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &runAscent, &runDescent, NULL);
                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;
                    }
                    //网络图片
                    UIImage *image;
                    if (!self.image) {
                        //图片未下载完成,使用占位图片
                        image = [UIImage imageNamed:imageName];
                        //下载图片
                        [self downLoadImageWithURL:[NSURL URLWithString:picURL]];
                    }else{
                        image = self.image;
                    }
                    //绘制网络图片
                    CGRect imageDrawRect;
                    CGFloat width = image.size.width;
                    CGFloat height = image.size.height;
                    if (image.size.width > rect.size.width) {
                        width = rect.size.width;
                    }
                    if (image.size.height > rect.size.height) {
                        height = rect.size.height;
                    }
                    CGSize finalSize = CGSizeMake(width, height);
                    
                    imageDrawRect.size = finalSize;
                    
                    NSLog(@"%.2f",lineOrigin.x);
                    imageDrawRect.origin.x = runRect.origin.x;
                    imageDrawRect.origin.y = lineOrigin.y;
                    CGContextDrawImage(contextRef, imageDrawRect, image.CGImage);
                }
                
            }
            
        }
        
        CFRelease(path);
        CFRelease(framesetter);
        CFRelease(ctFrame);
        
    }
    

    出来的效果图,如下所示:


    Snip20200811_3.png

    相关文章

      网友评论

        本文标题:CoreTextDemo2 图文篇

        本文链接:https://www.haomeiwen.com/subject/khbsdktx.html