views:

464

answers:

2

Say I have an array of points that form a line and a text. How can I go about drawing the text along this line in

 - (void)drawRect:(CGRect)rect 

of a UIView?

I am able to draw the path without a problem. Is there a standard method that I overlooked or a framework that would allow me to draw the text along that path? Ideally I would like to do this using only QuartzCore/CoreGraphics.

I tried calculating the width of each character and rotating every character. This kind of works, but I was wondering if there is a more elegant method.

+2  A: 

I believe you can do this in Mac OS X, but the closest you'll come on the iPhone is CGContextShowGlyphsWithAdvances and this wont even rotate.

It shouldn't be too hard to use a loop and draw each character using something like the following. This is adapted from Apple's documentation and not tested, so beware:

CGContextSelectFont(myContext, "Helvetica", 12, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(myContext, 10);
CGContextSetTextDrawingMode(myContext, kCGTextFillStroke);
CGContextSetRGBFillColor(myContext, 0, 0, 0, 1);
CGContextSetRGBStrokeColor(myContext, 0, 0, 0, 1);

NSUInteger charIndex = 0;
for(NSString *myChar in arrayOfChars) {
    char *cString = [myChar UTF8String];
    CGPoint charOrigin = originForPositionAlongPath(charIndex, myPath);
    CGFloat slope = slopeForPositionAlongPath(charIndex, myPath);

    CGContextSetTextMatrix(myContext, CGAffineTransformMakeRotation(slope));
    CGContextShowTextAtPoint(myContext, charOrigin.x, charOrigin.y, cString, 1);
}

Edit: Here's an idea of the PositionAlongPath functions. Again, they aren't tested, but should be close. originAlong... returns (-1, -1) if you run out of path.

CGPoint originForPositionAlongPath(int index, NSArray *path) {
    CGFloat charWidth = 12.0;
    CGFloat widthToGo = charWidth * index;

    NSInteger i = 0;
    CGPoint position = [path objectAtIndex:i];

    while(widthToGo >= 0) {
            //out of path, return invalid point
        if(i >= [path count]) {
            return CGPointMake(-1, -1);
        }

        CGPoint next = [path objectAtIndex:i+1];

        CGFloat xDiff = next.x - position.x;
        CGFloat yDiff = next.y - position.y;
        CGFloat distToNext = sqrt(xDiff*xDiff + yDiff*yDiff);

        CGFloat fracToGo = widthToGo/distToNext
            //point is before next point in path, interpolate the answer
        if(fracToGo < 1) {
            return CGPointMake(position.x+(xDiff*fracToGo), position.y+(yDiff*fracToGo));
        }

            //advance to next point on the path
        widthToGo -= distToNext;
        position = next;
        ++i;
    }
}


CGFloat slopeForPositionAlongPath(int index, NSArray *path) {
    CGPoint begin = originForPositionAlongPath(index, path);
    CGPoint end = originForPositionAlongPath(index+1, path);

    CGFloat xDiff = end.x - begin.x;
    CGFloat yDiff = end.y - begin.y;

    return atan(yDiff/xDiff);
}
David Kanarek
I guess the difficult part is finding the two functions originForPositionAlongPath, slopeForPositionAlongPath.
Felix
I'm not sure if what you tried is along the lines of what I added or not. If you give me an idea of why what you tried wasn't good enough, I'll see if I can improve what I've come up with.
David Kanarek
I had previously only implemented a test case for a given simple path with two segments, but did manage to formulate the two general methods you wrote. I'll check them out later today. Thanks a million for your help. I'll be back with more feedback.
Felix
Any progress with this? I'm interested to see how well the final solution works.
David Kanarek
Did this work out? Because I could use some help with a similar problem.
Wim Haanstra
A: 

This is probably irrelevant but you can text along a path with SVG (http://www.w3.org/TR/SVG/text.html#TextOnAPath), and the iPhoneOS supports it.

KennyTM
Thank you, but that is not exactly what I was aiming at as I would like to be able to do this within QuartzCore.
Felix