Benjohn
Benjohn

Reputation: 13887

Drawing vertical text on iOS

I'd like to draw an NSAttributedString (or NSString) vertically on iOS. By "vertically" I mean:

Vertical text example

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

Answers (4)

scottdev
scottdev

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

Léo Natan
Léo Natan

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

Emmanuel
Emmanuel

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

sample

Upvotes: 6

Fazil
Fazil

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

Related Questions