Sure, you can do that. With iOS 7, you don't need to go all the way down to Core Text, though. NSLayoutManager
can handle it in many cases. See the CurvyText demo that I wrote for iOS:PTL. You can drag all the control points around and see the text layout along the curve.
To see just how fast this layout can get in pure Core Text and Core Animation, see the PinchText demo from Rich Text, Core Text. This one shows how to adjust Core Text layout to respond to multi-touch, so the text seems to bend towards your fingers. It includes examples of how to animate with Core Animation to get smooth adjustments (and even a small "splash" effect when you remove your finger).
I don't quite know what you mean by "the whole drawing into a context nonsense that slows everything down." I draw these into a context very, very quickly (and Core Animation also does a lot of drawing into contexts).
Bending text around a circle is easier than either of these demos. The trick is to calculate the points along your circle, and use those points to translate and rotate your context before asking the layout manager to draw the glyph. Here's an example drawText
from CurvyTextView (which draws along a Bézier curve).
- (void)drawText {
if ([self.attributedString length] == 0) { return; }
NSLayoutManager *layoutManager = self.layoutManager;
CGContextRef context = UIGraphicsGetCurrentContext();
NSRange glyphRange;
CGRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:0
effectiveRange:&glyphRange];
double offset = 0;
CGPoint lastGlyphPoint = self.P0;
CGFloat lastX = 0;
for (NSUInteger glyphIndex = glyphRange.location;
glyphIndex < NSMaxRange(glyphRange);
++glyphIndex) {
CGContextSaveGState(context);
CGPoint location = [layoutManager locationForGlyphAtIndex:glyphIndex];
CGFloat distance = location.x - lastX; // Assume single line
offset = [self offsetAtDistance:distance
fromPoint:lastGlyphPoint
andOffset:offset];
CGPoint glyphPoint = [self pointForOffset:offset];
double angle = [self angleForOffset:offset];
lastGlyphPoint = glyphPoint;
lastX = location.x;
CGContextTranslateCTM(context, glyphPoint.x, glyphPoint.y);
CGContextRotateCTM(context, angle);
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(glyphIndex, 1)
atPoint:CGPointMake(-(lineRect.origin.x + location.x),
-(lineRect.origin.y + location.y))];
CGContextRestoreGState(context);
}
}
The "magic" of this is in calculating the transforms you need, which is done in offsetAtDistance:fromPoint:andOffset:
, pointForOffset:
and angleForOffset:
. Those routines are much simpler to write for a circle than a generic Bézier curve, so this is probably a very good starting point. Note that this code is not particularly optimized. It was designed for readability more than speed, but it is still very fast on an iPad 3. If you need it to be faster, there are several techniques, including a lot of pre-calculating that can be done.
The PinchText demo is in pure Core Text and Core Animation, and is quite a bit more complicated since it does all of its math in Accelerate (and really needs to). I doubt you need that since your layout problem isn't that complicated. Some straightforward C can probably calculate everything you need in plenty of time. But the PinchText demo does show how to let Core Animation manage transitions more beautifully. Look at addTouches:inView:scale:
:
- (void)addTouches:(NSSet *)touches inView:(UIView *)view scale:(CGFloat)scale
{
for (UITouch *touch in touches) {
TouchPoint *touchPoint = [TouchPoint touchPointForTouch:touch inView:view scale:scale];
NSString *keyPath = [self touchPointScaleKeyPathForTouchPoint:touchPoint];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:keyPath];
anim.duration = kStartTouchAnimationDuration;
anim.fromValue = @0;
anim.toValue = @(touchPoint.scale);
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self addAnimation:anim forKey:keyPath];
[self.touchPointForIdentifier setObject:touchPoint forKey:touchPoint.identifier];
}
}
What's going on here is that it's animating the model data ("scale" here is "how much does this touch impact the layout;" it has nothing to do with transforms). needsDisplayForKey:
indicates that when that model data structure is modified, the layer needs to redraw itself. And it completely recomputes and redraws itself into its context every frame. Done correctly, this can be incredibly fast.
This code should hopefully get you started. Not to overly push the book, but the CurvyText demo is discussed extensively in iOS Pushing the Limits chapter 21.