代码改变世界

coreText自定义富文本Label

2012-04-28 11:11  java环境变量  阅读(539)  评论(0编辑  收藏  举报

UILabel是经常使用的一种控件,iOS上的UILabel已经能很好满足一些需求。比如设置对齐方式,换行模式等等。

但如果需求是需要一串字符中不同的字符颜色,字体都单独设置,UILabel就无法满足了。那就自己来做个富文本Label好了。

先创建继承UILabel的AttributedLabel.h,AttributedLabel.m文件,重载UILabeld -(void)drawTextInRect:(CGRect)rect方法,我们的文本绘制就放在这个函数中。

使用coreText进行文本绘制,需要在工程中添加CoreText.framework,然后在AttributedLabel.m里import<CoreText/CoreText.h>就可以使用了。coreText负责绘制,那绘制的内容和属性则要靠NSAttributedString来存储,如果属性具有不确定性,可以使用NSMutableAttributedString,方便后面添加属性。

先来看下如何创建一个具有两个颜色,两种字体的“hello world”的NSMutableAttributedString实例。

  1. NSString *text = @“hello word”;
  2.  
  3. NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text];
  4.  
  5. [attributedText addAttribute:(NSString*)(kCTForegroundColorAttributeName) value:(id)[[UIColor blueColor]CGColor] range:NSMakeRange(0,5)];
  6.  
  7. [attributedText addAttribute:(NSString*)(kCTForegroundColorAttributeName) value:(id)[[UIColor redColor]CGColor] range:NSMakeRange(6,5)];
  8.  
  9. CTFontRef  font_hello = CTFontCreateWithName((CFStringRef)@“Helvetica”,16,NULL);
  10.  
  11. CTFontRef  font_world = CTFontCreateWithName((CFStringRef)@“GillSans”,20,NULL);
  12.  
  13. [attributedText addAttribute: (NSString*)(kCTFontAttributeName) value:(id)font_hello range:NSMakeRange(0,5)];
  14.  
  15. [attributedText addAttribute: (NSString*)(kCTFontAttributeName) value:(id)font_world range:NSMakeRange(6,5)];

这样,一个包含简单绘制属性的NSMutableAttributedString实例就创建出来了。

接下来就要在drawTextInRect函数中开始绘制了。

普通视图坐标系原点在左上方,而QuartZ绘图的坐标系原点在左下方,所以我们先要调整坐标系。

  1. CGContextRef context = UIGraphicsGetCurrentContext();
  2.  
  3. CGContextSetTextMatrix(context,CGAffineTransformIdentity);//重置
  4.  
  5. CGContextTranslateCTM(context,0,self.bounds.size.height); //y轴高度
  6.  
  7. CGContextScaleCTM(context,1.0,-1.0);//y轴翻转

好了,可以开始绘制,因为”hello world”较短,一行就可以放下,那么可以这么绘制

  1. CTLineRef line = CTLineCreateWithAttributedString(attributedText);
  2.  
  3. CGContextSetTextPosition(context,0,0);
  4.  
  5. CTLineDraw(line,context);
  6.  
  7. CFRelease(line);

OK,就这么多搞定。

那如果文本很长,希望换行来显示怎么办?

那我们先要给attributedText添加些关于行相关属性。

下面都是基本的,可以根据自己绘制情况调整,扩充相应的变量。

  1. CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;//换行模式
  2.  
  3. CTTextAlignment alignment = kCTLeftTextAlignment;//对齐方式
  4.  
  5. float lineSpacing =2.0;//行间距
  6.  
  7. CTParagraphStyleSetting paraStyles[3] = {
  8.  
  9. {.spec = kCTParagraphStyleSpecifierLineBreakMode,.valueSize = sizeof(CTLineBreakMode), .value = (const void*)&lineBreakMode},
  10.  
  11. {.spec = kCTParagraphStyleSpecifierAlignment,.valueSize = sizeof(CTTextAlignment), .value = (const void*)&alignment},
  12.  
  13. {.spec = kCTParagraphStyleSpecifierLineSpacing,.valueSize = sizeof(CGFloat), .value = (const void*)&lineSpacing},
  14.  
  15. };
  16.  
  17. CTParagraphStyleRef style = CTParagraphStyleCreate(paraStyles,3);
  18.  
  19. [attributedText addAttribute:(NSString*)(kCTParagraphStyleAttributeName) value:(id)style range:NSMakeRange(0,[text length])];
  20.  
  21. CFRelease(style);

下面就可以绘制了

  1. CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText);
  2.  
  3. CGMutablePathRef path = CGPathCreateMutable();
  4.  
  5. CGPathAddRect(path,NULL,self.bounds);
  6.  
  7. CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0),path,NULL);
  8.  
  9. CGContextSetTextPosition(context,0,0);
  10.  
  11. CTFrameDraw(frame,context);
  12.  
  13. CFRelease(framesetter);
  14.  
  15. CFRelease(frame);

这样绘制出来的可以自适应换行了,或者可以做的复杂点,通过上面获取的CTFrameRef frame来得到CTLineRef绘制

  1. CFArrayRef lines = CTFrameGetLines(frame);
  2.  
  3. int lineNumber = CFArrayGetCount(lines);
  4.  
  5. CGPoint lineOrigins[lineNumber];
  6.  
  7. CTFrameGetLineOrigins(frame,CFRangeMake(0,lineNumber), lineOrigins);
  8.  
  9. for(int lineIndex = 0;lineIndex < lineNumber;lineIndex++){
  10.  
  11. CGPoint lineOrigin = lineOrigins[lineIndex];
  12.  
  13. CTLineRef line = CFArrayGetValueAtIndex(lines,lineIndex);
  14.  
  15. CGContextSetTextPosition(context,lineOrigin.x,lineOrigin.y);
  16.  
  17. CTLineDraw(line,context);
  18.  
  19. }

但这样绘制方法有个问题,就是即使行间距设为0.0(不能为负值)仍是比较分散的。如果希望更进一步控制好行间距,那自己就一行一行计算位置画

  1. float lineHeight = 20; //行高
  2.  
  3. BOOL drawFlag = YES;//是否绘制
  4.  
  5. int lineCount = 0//行数
  6.  
  7. CFIndex currentIndex = 0;//绘制计数
  8.  
  9. CTTypesetterRef typeSetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)attributedText);
  10.  
  11. float fontAscender = MAX(font_hello.ascender,font_world.ascender);
  12.  
  13. float y = self.bounds.origin.y+self.bounds.size.height-fontAscender;
  14.  
  15. while(drawFlag)
  16.  
  17. {
  18.  
  19. CFIndex lineLength = CTTypesetterSuggestLineBreak(typeSetter,currentIndex,self.bounds.size.width);
  20.  
  21. CFRange lineRange = CFRangeMake(currentIndex,lineLength);
  22.  
  23. CTLineRef line = CTTypesetterCreateLine(typeSetter,lineRange);
  24.  
  25. float x = CTLineGetPenOffsetForFlush(line,0,self.bounds.size.width);
  26.  
  27. CGContextSetTextPosition(context,x,y);
  28.  
  29. CTLineDraw(line,context);
  30.  
  31. if(currentIndex + lineLength >= [text length]){
  32.  
  33. drawFlag = NO;
  34.  
  35. }
  36.  
  37. CFRelease(line);
  38.  
  39. count++;
  40.  
  41. -=lineHeight;
  42.  
  43. currentIndex += lineLength;
  44.  
  45. }
  46.  
  47. CFRelease(typeSetter);

到这里的时候,又有个问题,就是用同样的字体和字号,绘制的字总是会比UILabel显示的粗些。

查看kCTStrokeWidthAttributeName属性发现默认已经为0.0,但这个值是可以为负值,那就变通的解决这个问题。

在attributedText里添加两个属性值

  1. CGFloat widthValue = -1.0;
  2.  
  3. CFNumberRef strokeWidth = CFNumberCreate(NULL,kCFNumberFloatType,&widthValue);
  4.  
  5. [attributedText addAttribute:(NSString*)(kCTStrokeWidthAttributeName) value:(id)strokeWidth range:NSMakeRange(0,[text length])];
  6.  
  7. [attributedText addAttribute:(NSString*)(kCTStrokeColorAttributeName) value:(id)[[UIColor whiteColor]CGColor] range:NSMakeRange(0,[text length])];

这样绘制出来的字就细致些。