iOS CoreText(二)

這篇主要講利用coreText來實現(xiàn)圖文混排和點擊事件

圖文混排思路

文字的繪制只需要知道文字的大小就夠了,而圖片的繪制不一樣,需要知道圖片的坐標,高度和寬度。在CoreText中,我們可以把插入的圖片當做一個特殊的CTRun,通過delegate來設置圖片的寬度和高度,這樣就解決了圖片的高度和寬度問題,但是CoreText不會自動的對圖片進行繪制,因此需要我們自己找到圖片的顯示位置(原點坐標),然后自己進行繪制

具體實現(xiàn)

根據(jù)上一篇的 iOS CoreText(一)中的代碼,我們需要在需要顯示圖片的地方,插入一個空白字符,然后設置CTRun的代理

- (void)drawTextAndImage:(CGContextRef)context size:(CGSize)size {
    NSMutableAttributedString *astring = _textString;
    //設置坐標系
    //設置字形的變換矩陣為不做圖形變換
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    //平移方法,將畫布向上平移一個屏幕高度
    CGContextTranslateCTM(context, 0, size.height);
    //縮放方法,x軸縮放系數(shù)為1,則不變,y軸縮放系數(shù)為-1,則相當于以x軸為軸旋轉(zhuǎn)180度
    CGContextScaleCTM(context, 1, -1);
    //這次的重點
    //設置CTRun代理
    CTRunDelegateCallbacks callBacks;
    memset(&callBacks, 0, sizeof(CTRunDelegateCallbacks));
    
    callBacks.version = kCTRunDelegateVersion1;
    callBacks.getAscent = ascentCallbacks;
    callBacks.getDescent = descentCallbacks;
    callBacks.getWidth = widthCallbacks;
    
    CTRunDelegateRef delegate = CTRunDelegateCreate(&callBacks, (void *)astring);
    
    //創(chuàng)建空白字符
    unichar placeHolder = 0xFFFC;
    NSString *placeHolderString = [NSString stringWithCharacters:&placeHolder length:1];
    NSMutableAttributedString *placeHolderAttributedString = [[NSMutableAttributedString alloc]initWithString:placeHolderString];
 
    NSDictionary *attributedDic = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)delegate, kCTRunDelegateAttributeName,nil];
    [placeHolderAttributedString setAttributes:attributedDic range:NSMakeRange(0, 1)];
    CFRelease(delegate);
    
    //將圖片插入
    [astring insertAttributedString:placeHolderAttributedString atIndex:astring.length/2];
   
    //創(chuàng)建path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
  
    //繪文字
    CTFramesetterRef frameRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)astring);
    CTFrameRef fref = CTFramesetterCreateFrame(frameRef, CFRangeMake(0, astring.length), path, NULL);
    CTFrameDraw(fref, context);
    
    //繪圖
    UIImage *image = [UIImage imageNamed:@"tj_Image"];
    CGRect imageRect = [self calculateImageRect:fref];
    CGContextDrawImage(context, imageRect, image.CGImage);
    
    CFRelease(path);
    CFRelease(fref);
    CFRelease(frameRef);
}
#pragma mark ---CTRUN代理---
CGFloat ascentCallbacks (void *ref) {
    return 11;
}

CGFloat descentCallbacks (void *ref) {
    return 7;
}

CGFloat widthCallbacks (void *ref) {
    return 36;
}

和上次一樣的我們就不講了,從設置代理開始說起
先要設置代理的回調(diào)CTRunDelegateCallbacks callBacks;,包括了四個需要設置的屬性,版本號、上邊距、下邊距和寬度,后面對應的是C的函數(shù)名,負責確定圖片的寬度和高度。
設置完成后,穿件一個空白字符unichar placeHolder = 0xFFFC
將空白字符轉(zhuǎn)換成NSString,再轉(zhuǎn)換成NSMutableAttributedString
創(chuàng)建一個字典,key為kCTRunDelegateAttributeName,value為我們創(chuàng)建的delegate(__bridge為OC對象和CF對象之間的橋接),本質(zhì)上addAttributes:就是add一個字典,這點要理解
然后將空白字符插入要顯示的位置

 //繪圖
//我們要顯示的圖片
    UIImage *image = [UIImage imageNamed:@"tj_Image"];
//自己定義的方法,為了得到圖片的坐標和大小
    CGRect imageRect = [self calculateImageRect:fref];
//將圖片繪制到指定的地方
    CGContextDrawImage(context, imageRect, image.CGImage);

我們現(xiàn)在主要來講講calculateImageRect這個方法

先來說說這個方法的思路:
我們先獲取到所有的CTLine,然后遍歷每個CTLine中的CTRun的,取出CTRun對應的Attributes字典,判斷字典中是否有key為kCTRunDelegateAttributeName的value,如果有,就是我們插入的圖片的位置。

- (CGRect)calculateImageRect:(CTFrameRef)frame {
    //先找CTLine的原點,再找CTRun的原點
    NSArray *allLine = (NSArray *)CTFrameGetLines(frame);
    NSInteger lineCount = [allLine count];
    //獲取CTLine原點坐標
    CGPoint points[lineCount];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
    CGRect imageRect = CGRectMake(0, 0, 0, 0);
    for (int i = 0; i < lineCount; i++) {
        CTLineRef line = (__bridge CTLineRef)allLine[i];
        //獲取所有的CTRun
        CFArrayRef allRun = CTLineGetGlyphRuns(line);
        CFIndex runCount = CFArrayGetCount(allRun);
        
        //獲取line原點
        CGPoint lineOrigin = points[i];
        
        
        for (int j = 0; j < runCount; j++) {
            CTRunRef run = CFArrayGetValueAtIndex(allRun, j);
            NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];
            if (delegate == nil) {
                //暫時不用關注這部分代碼,主要用于點擊事件
                NSString *textClickString = [attributes valueForKey:@"textClick"];
                if (textClickString != nil) {
                    [textFrameArray addObject:[NSValue valueWithCGRect:[self getLocWith:frame line:line run:run origin:lineOrigin]]];
                }
                
                continue;
            }
           //獲取圖片的Rect
            imageRect = [self getLocWith:frame line:line run:run origin:lineOrigin];
        }
    }
    return imageRect;
}

對照上面的思路來看,代碼不復雜

- (CGRect)getLocWith:(CTFrameRef)frame line:(CTLineRef)line run:(CTRunRef)run origin:(CGPoint)point {
    CGRect boundRect;
    CGFloat ascent = 0.0f;
    CGFloat descent = 0.0f;
    CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
    boundRect.size.width = width;
    boundRect.size.height = ascent + descent;
    
    //獲取x偏移量
    CGFloat xoffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
    boundRect.origin.x = point.x + xoffset;
    boundRect.origin.y = point.y - descent;
    
    //獲取BoundingBox
    CGPathRef path = CTFrameGetPath(frame);
    CGRect colRect = CGPathGetBoundingBox(path);

    return CGRectOffset(boundRect, colRect.origin.x, colRect.origin.y);
}
    CGFloat ascent = 0.0f;
    CGFloat descent = 0.0f;
    CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
    boundRect.size.width = width;
    boundRect.size.height = ascent + descent;

這里獲取的widthascentdescent,其實就是我們在CTRunDelegateCallbacks callBacks中設置的那幾個函數(shù)

CGFloat xoffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);

這句代碼就是獲取該CTRun對于CTLine的x軸偏移,知道偏移量和CTLine的原點,我們就可以計算出圖片的原點。

boundRect.origin.y = point.y - descent;

主要是為了讓圖片能居中顯示(可以自己調(diào)節(jié)試試)
這樣圖片的Rect就獲取到了

現(xiàn)在來說說點擊事件

當我們?yōu)槟扯挝淖只蛘吣硰垐D片設置點擊事件,主要利用了本質(zhì)上addAttributes:就是add一個字典這一特性,這樣我們就可以直接在字典中定義一個特殊的key,用來判斷該CTRun是否是具有點擊事件的CTRun

LJTextView *textView = [[LJTextView alloc] initWithFrame:CGRectMake(0, 64, width, height - 64)];
textView.backgroundColor = [UIColor whiteColor];
textView.textString = [[NSMutableAttributedString alloc]initWithString:@"123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123"];
    
NSDictionary *dic = @{@"textClick":@"click",NSBackgroundColorAttributeName:[UIColor redColor]};
[textView.textString addAttributes:dic range:NSMakeRange(24, 4)];

上面的代碼我們就定義了已一個textClick的特殊key

NSString *textClickString = [attributes valueForKey:@"textClick"];
if (textClickString != nil) {
      [textFrameArray addObject:[NSValue valueWithCGRect:[self getLocWith:frame line:line run:run origin:lineOrigin]]];
}

獲取圖片Rect的時候,我們同時獲取了擁有點擊事件的CTRun的Rect并把他記錄在textFrameArray數(shù)組中,方面后面進行判斷

- (CGRect)convertRectToWindow:(CGRect)rect {
    return CGRectMake(rect.origin.x, self.bounds.size.height - rect.origin.y - rect.size.height, rect.size.width, rect.size.height);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = touches.anyObject;
    CGPoint point = [touch locationInView:self];
    [textFrameArray enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger idx, BOOL * _Nonnull stop) {
        CGRect rect = [value CGRectValue];
        CGRect convertRect = [self convertRectToWindow:rect];
        if (CGRectContainsPoint(convertRect, point)) {
            NSString *message = [NSString stringWithFormat:@"點擊了%lu",(unsigned long)idx];
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil];
            [alert show];
        }
    }];
}

convertRectToWindow主要用于轉(zhuǎn)換坐標系,上面代碼主要用于判斷點擊的位置在不在坐標范圍內(nèi),在則響應,不在則不處理

其實CoreText并不復制,只要了解,后面就好在工作中運用了
下一篇我們介紹YYLable的實現(xiàn)機制

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內(nèi)容