Displaying a block of text with an oversized initi

2019-05-14 17:57发布

问题:

Is there any way to provide this kind of effect in a UITextView? If not, then how can I achieve this effect?

回答1:

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!