Displaying a block of text with an oversized initial letter in a UITextView

眉间皱痕 提交于 2019-12-06 04:22:19

Having the same requirement, I created a UIView subclass that draws text with a drop cap. The text is drawn using core text, and as @CodaFi suggested the drop cap is drawn in a separate core text frame.

The full implementation of the class: https://gist.github.com/4596476

The meat of it looks something like this:

- (void)drawRect:(CGRect)rect {
    // Create attributed strings
    NSAttributedString *bodyText = [[NSAttributedString alloc] initWithString:[self.text substringWithRange:NSMakeRange(1, self.text.length -1)] attributes:_bodyAttributes];
    NSAttributedString *capText = [[NSAttributedString alloc] initWithString:[[self.text substringWithRange:NSMakeRange(0, 1)] uppercaseString] attributes:_capAttributes];

    CGRect capFrame = CGRectMake(0, 0, [capText size].width, [capText size].height);

    // Set up graphics context
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // Create type frames
    CGMutablePathRef bodyPath = CGPathCreateMutable();
    CGAffineTransform transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1, -1), 0, -self.bounds.size.height);
    CGPathMoveToPoint(bodyPath, &transform, CGRectGetMaxX(capFrame), 0);
    CGPathAddLineToPoint(bodyPath, &transform, self.bounds.size.width, 0);
    CGPathAddLineToPoint(bodyPath, &transform, self.bounds.size.width, self.bounds.size.height);
    CGPathAddLineToPoint(bodyPath, &transform, 0, self.bounds.size.height);
    CGPathAddLineToPoint(bodyPath, &transform, 0, CGRectGetMaxY(capFrame));
    CGPathAddLineToPoint(bodyPath, &transform, CGRectGetMaxX(capFrame), CGRectGetMaxY(capFrame));
    CGPathCloseSubpath(bodyPath);

    CGMutablePathRef capPath = CGPathCreateMutable();
    CGPathAddRect(capPath, &transform, CGRectMake(0, 0, capFrame.size.width+10, capFrame.size.height+10));

    // Draw text
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) bodyText);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), bodyPath, NULL);
    CFRelease(framesetter);
    CTFrameDraw(frame, context);
    CFRelease(frame);
    framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)capText);
    frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), capPath, NULL);
    CFRelease(framesetter);
    CTFrameDraw(frame, context);
    CFRelease(frame);
}

The gist of it is you create two paths, one rectangle to contain the drop cap, and a rectangle with a notch removed for the rest of the text. The full implementation on gist allows you to control the fonts, spacing around the drop cap, and content inset of the whole view using properties.

It does not implement most of the features of a UITextView (only the features I needed) so it may not be a full solution.

Hope that helps!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!