1

I have to make sometging like this working on iOS 6 and iOS 7: enter image description here

But I don't know how to do it. Can you help me?

user2375706
  • 705
  • 2
  • 8
  • 14

2 Answers2

3

To draw your text around the image, with CoreText, you need to create a CTFramesetterRef, then create a CTFrameRef with a path that specifies the shape of the frame by calling CTFramesetterCreateFrame.

A sample code that's draw an image in the top-left corner of an UIView and some text around the image :

@implementation MyView
- (void)drawRect:(CGRect)rect
{
    UIImage *image = [UIImage imageNamed:@"YourArrowImage"];

    // create the transform to flip the path and text drawing
    CGAffineTransform transform = CGAffineTransformMakeTranslation(0, rect.size.height);
    transform = CGAffineTransformScale(transform, 1., -1.);

    // create a path that's exclude the image rect { .origin = CGPointZero, .size = [image size] } from the rect
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, &transform, [image size].width, 0);
    CGPathAddLineToPoint(path, &transform, rect.size.width, 0);
    CGPathAddLineToPoint(path, &transform, rect.size.width, rect.size.height);
    CGPathAddLineToPoint(path, &transform, 0, rect.size.height);
    CGPathAddLineToPoint(path, &transform, 0, [image size].height);
    CGPathAddLineToPoint(path, &transform, [image size].width, [image size].height);
    CGPathCloseSubpath(path);

    // setup your text with the desired attributes
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus."
                                                                           attributes:@{ NSFontAttributeName :[UIFont systemFontOfSize:24.],
                                                       NSForegroundColorAttributeName: [UIColor grayColor] }];

    // create CoreText framesetter and the text frame to draw
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);

    [attributedString release];

    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), path, NULL);

    CFRelease(path);
    CFRelease(framesetter);

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // save graphics state
    CGContextSaveGState(ctx);

    // apply the transfom to draw the text
    CGContextConcatCTM(ctx, transform);
    // draw the text frame
    CTFrameDraw(frame, ctx);

    // restore graphics state
    CGContextRestoreGState(ctx);

    CFRelease(frame);

    // draw the image
    [image drawAtPoint:CGPointMake(0,0)];
}
@end
Emmanuel
  • 2,897
  • 1
  • 14
  • 15
  • Hi, Emmanuel. I realized that the path that I applied here and the path being drawn by `CGContextStrokePath` is flipped vertically. Let say for example I got a triangle pointing upward. The path drawn is correct but the one used of CT is flipped vertically (pointing to the bottom). Would you know why? I have tried to transform the context, but none has helped so far. Thanks. – Unheilig Apr 16 '14 at 14:14
  • @Unheilig Which path are you talking about, you replaced your image by a path ? Anyway, CoreGraphics origin is at "top-left" (on iOS), and CoreText origin is at "bottom-left". It's why you need to apply a transform while we draw text (`CGContextConcatCTM(ctx, transform)` in my sample). You don't need this transform to draw your path (which is defined with CoreGraphics coordinate system), so draw it before `CGContextSaveGState` or after `CGContextRestoreGState`, and your problem should be fixed. – Emmanuel Apr 16 '14 at 15:02
  • @Unheilig [See this about graphics states.](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_overview/dq_overview.html#//apple_ref/doc/uid/TP30001066-CH202-TPXREF132) – Emmanuel Apr 16 '14 at 15:03
  • @Emmanual I understand the coordinate system. What I meant is I draw a path myself (a triangle, pointing up) and then I used `CGContextStrokePath` to stroke this path. Using the same path I inserted it in your code here, the result is that the triangle is now flipped, pointing downward. I think the problem is this line: `CGContextConcatCTM(ctx, transform);` I understand we need this for the text, but it seems it flips the path vertically as well. Thanks. – Unheilig Apr 16 '14 at 15:07
  • @Unheilig It's why you need to draw your path outside the calls of `CGContextSaveGState` and `CGContextRestoreGState`. – Emmanuel Apr 16 '14 at 15:12
  • Yes, my paths are draw outside of that. I will keep trying and see. I also think it is weird. – Unheilig Apr 16 '14 at 15:21
  • @Unheilig You draw the same path that's you use to create the `CTFrameRef` ? If so, the problem is here, in my sample code this path is already flipped. – Emmanuel Apr 16 '14 at 15:30
  • Yes, I draw a path outside of the state, use that path to set the `CTFrameRef`, flipped the system like you did above within save/restore state; while the text are rendered good the path flipped. – Unheilig Apr 16 '14 at 15:41
  • @Unheilig The path is already flipped for CoreText, by passing `transform` when we create it. Then draw this path inside save/restore state (after the `CGContextConcatCTM`) it will flipped again. – Emmanuel Apr 16 '14 at 16:02
  • Right, I got it. Things look good now. Thanks a lot. It was fun, though a lot of work. Now, am getting a coffee, sit back and while looking at the result. :-) Merci. – Unheilig Apr 16 '14 at 16:47
  • @Unheilig De rien :-) – Emmanuel Apr 16 '14 at 16:57
1

I can not suggest you how to implement it for iOS6. But in iOS7 it is easily implemented with

UIBezierPath* exclusionPath = ... create exclusion path here
_textView.textContainer.exclusionPaths  = @[exclusionPath];

Very good tutorial - http://www.raywenderlich.com/50151/text-kit-tutorial

Avt
  • 16,927
  • 4
  • 52
  • 72