tom

CoreTextDemo2 图文篇

2020-08-11  本文已影响0人  coder_feng

本文介绍,在一个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
上一篇 下一篇

猜你喜欢

热点阅读