Reputation: 13887
I'd like to draw an NSAttributedString
(or NSString
) vertically on iOS. By "vertically" I mean:
Apple's documentation says a standard attribute on an NSAttributedString
is NSVerticalGlyphFormAttributeName
, but unfortunately, says:
"In iOS, horizontal text is always used and specifying a different value is undefined."
The same document also mentions NSTextLayoutSectionsAttribute
, which seems to support NSTextLayoutOrientation
, which allows NSTextLayoutOrientationHorizontal
or NSTextLayoutOrientationVertical
.
None of those 4 terms get any hits on SO at the moment. Just that lonely whistling sound.
However, I don't have any idea how to set this up, whether it works with iOS, or if can be used for vertical string rendering in the style of [myAttributedString drawAtPoint: whereItOughtaGo]
.
Thanks.
Upvotes: 6
Views: 3633
Reputation: 141
If you draw it with attribute you can create a paragraph attribute and set the left and right margins for 0 or very small. This results in a vertical display string when drawAtPoint is used.
Upvotes: 1
Reputation: 57050
To add to the other great answers, I played around with NSTextLayoutSectionsAttribute
and even subclassed NSTextContainer
to override layoutOrientation
to return NSTextLayoutOrientationVertical
. It doesn't work. I see NSATSTypesetter
querying for layout orientation, but rendering is still done in horizontal orientation. I guess it's a Core Text limitation; documentation and header files state numerous times that only horizontal layout orientation is supported in Core Text.
As per documentation, the NSTextLayoutOrientationProvider
protocol is only provided if a subclass of NSLayoutManager
wishes to implement a different layout orientation.
Upvotes: 1
Reputation: 2917
You can draw your vertical text by using CoreText (to get glyphs) and CoreGraphics (to draw CGGlyphs). A simple sample, that's only take care of one font attribute (handle the effective range for each attribute, if want to handle different font, font size, etc …)
#import "NSAttributedString+VerticalDrawing.h"
@implementation TestView
- (void)drawRect:(CGRect)rect
{
NSAttributedString *aString = [[NSAttributedString alloc] initWithString:@"This is a vertical text"
attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:14.]}];
[aString drawVerticalAtPoint:CGPointMake(10, 10)];
}
@end
@implementation NSAttributedString (VerticalDrawing)
- (void)drawVerticalAtPoint:(CGPoint)location
{
UIFont *font = [[self attributesAtIndex:0 effectiveRange:NULL] objectForKey:NSFontAttributeName];
NSUInteger myLength = [self length];
CTFontRef ctfont = CTFontCreateWithName((CFStringRef)[font fontName], [font pointSize], NULL);
CGGlyph *glyphs = malloc(sizeof(CGGlyph) * myLength);
UniChar *characters = malloc(sizeof(UniChar) * myLength);
CGSize *advances = malloc(sizeof(CGSize) * myLength);
[[self string] getCharacters:characters range:NSMakeRange(0,myLength)];
CTFontGetGlyphsForCharacters(ctfont, characters, glyphs, myLength);
CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, glyphs, advances, myLength);
free(characters);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFontRef cgfont = CTFontCopyGraphicsFont(ctfont, NULL);
CGContextSetFont(ctx, cgfont);
CGContextSetFontSize(ctx, CTFontGetSize(ctfont));
CGAffineTransform textMatrix = CGAffineTransformMakeTranslation(location.x, location.y);
textMatrix = CGAffineTransformScale(textMatrix, 1, -1);
CGContextSetTextMatrix(ctx, textMatrix);
CGFloat lineHeight = CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont) + CTFontGetLeading(ctfont);
location.y = -CTFontGetAscent(ctfont);
CGFloat maxAdvance = 0.;
NSUInteger i;
for (i = 0; i < myLength; i++)
maxAdvance = MAX(maxAdvance, advances[i].width);
for (i = 0; i < myLength; i++)
{
location.x = (maxAdvance - advances[i].width) * 0.5;
CGContextShowGlyphsAtPositions(ctx, &glyphs[i], &location, 1);
location.y -= lineHeight;
}
free(glyphs);
free(advances);
CGFontRelease(cgfont);
CFRelease(ctfont);
}
@end
Upvotes: 6
Reputation: 1390
If it just a single lined text as in your example you can use various workarounds. I would personally create a UILabel
subclass as follows:
@implementation VerticalLabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.numberOfLines = 0;
}
return self;
}
- (void)setText:(NSString *)text {
NSMutableString *newString = [NSMutableString string];
for (int i = text.length-1; i >= 0; i--) {
[newString appendFormat:@"%c\n", [text characterAtIndex:i]];
}
super.text = newString;
}
@end
Now you just have to replace:
UILabel *lbl = [[UILabel alloc] init];
with:
UILabel *lbl = [[VerticalLabel alloc] init];
to get vertical text.
Upvotes: 7