你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> CoreText(四):圖文混排

CoreText(四):圖文混排

編輯:IOS開發綜合

在一個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後邊拼接上寬高信息的方式來處理。
這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved