正如其他人所提到的,仅使用NSAttributedString 是不可能做到这一点的。 Nikolai 有正确的方法,使用CTFrameSetters。但是,可以告诉框架设置器在特定区域(即由 CGPath 定义)呈现文本。
您必须创建 2 个框架设置器,一个用于首字下沉,另一个用于其余文本。
然后,您抓住首字下沉的框架并构建一个CGPathRef,该CGPathRef 围绕首字下沉的框架空间运行。
然后,您将两个框架设置器都渲染到您的视图中。
我创建了一个示例项目,其中包含一个名为 DropCapView 的对象,它是 UIView 的子类。此视图呈现第一个字符并将剩余的文本环绕在它周围。
看起来像这样:
有很多步骤,所以我添加了一个指向托管该示例的 github 项目的链接。项目中有 cmets 可以帮助您。
DropCap project on 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 (e.g. 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);
}
附:这来自于:https://stackoverflow.com/a/9272955/1218605