8

I'm having trouble drawing an NSAttributedString with no margins around it in its view. Here's my code:

NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:72.0f]};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"Hello"
                                                             attributes:attributes];
[string drawWithRect:rect
             options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesDeviceMetrics
             context:nil];

This leads to the behavior below:

String drawn with margins

Note the margins at the left and top.

  1. How can I avoid these margins and make the text exactly meet the edges of the containing view? What I want is for the actual drawn text to meet the edges of the view, that is, the topmost pixel drawn in any glyph should be at the top of the view, and the leftmost pixel drawn in any glyph is at the left side of the view.
  2. Assuming 1 is possible, is there a way I can get the actual width and height of the drawn text so that I can calculate font size and kerning to make the text meet the bottom and right edges as well?

I realize that there may be ways to align text without making it meet the edges of the view, but making it meet the edges of the view will allow me to work with the view intuitively using autolayout and so on.

I do not care about the behavior when the string contains leading or trailing whitespace.

If this is not possible with NSAttributedString, are there other ways to get this behavior that you'd recommend?

To clarify, here's what I want to see for number 1.

Desired behavior

Luke
  • 7,110
  • 6
  • 45
  • 74
  • regarding question 2: http://stackoverflow.com/questions/3429671/how-to-get-the-width-of-an-nsstring – luk2302 May 31 '15 at 19:11
  • Why is the second picture right? There is space after the "o" and space below all the letters. It seems to me that you have not defined clearly, even to yourself, what you're after. – matt Jun 05 '15 at 14:44
  • Also, is the colored background crucial? Because if the background were clear, no one would know where the view boundaries are, and the issue would fall away. – matt Jun 05 '15 at 14:46
  • @matt - Note that for item 1, I do not care about the right and bottom. I have defined clearly to myself what I want, let me know how I can clarify the question. I will not actually be using a colored background, but I need the drawn text to be aligned with other elements on the screen, so it does matter where the view boundaries are. – Luke Jun 05 '15 at 15:16
  • Then instead of eliminating space, it seems to me that your question should be simply: "Where is the top left corner of the drawn text?" Knowing that would allow you to position the text. But again, this depends on a realistic and meaningful definition of "top left corner", and it does not seem to me that you have such a definition in mind. – matt Jun 05 '15 at 15:23
  • Or perhaps the question should be how to align labels with one another. That's actually quite easy, because they have built-in baselines that can be made to align automatically. In that case, of course, you should be thinking about the _bottom_ left of the text. Really, you would do better to ask a question based on the practical problem you are trying to solve, rather than a question that assumes a bunch of things you don't need to assume (and which, it seems to me, you have not clarified in your own mind beforehand). – matt Jun 05 '15 at 15:24
  • From what you have said, you starting issue is that fonts all come with ascending and desceding values which force some white space. Having tried, its not that easy to subclass or extend UIFont to override these. For a given font you can use the size of capital letters and the ascending to work out a Y offset to remove the top white space and the descending to work out the bottom offset. You should then be able to use `drawAtPoint` to draw offset to give you the left and top flush. Change the view size to deal with bottom and right. – Rory McKinnel Jun 08 '15 at 11:21
  • Can you elaborate more on what exactly you're trying to achieve? What should happen to characters descending below the baseline? Accents on capitals going above the cap height? Take a look at [this infographic](http://www.myfirstfont.com/images/glyphterms.gif). Which dimensions do you want to compute and align? – Christian Schnorr Jun 10 '15 at 16:11

3 Answers3

4

You can get the height,width vector values using CoreText:

double fWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
size_t width = (size_t)ceilf(fWidth);
size_t height = (size_t)ceilf(ascent + descent + leading);

so you can get the rect. But the top margin is not wrong

Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99
maquanhong
  • 110
  • 2
2

Use CoreText, CTLineGetTypographicBounds() is probably what you are looking for. I haven't used this myself. The following code demonstrate the idea (drawing "Hello" to a custom UIView with no top/left margin).

override func drawRect(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    UIGraphicsPushContext(context)

    let viewHeight = bounds.size.height

    let attributedString = NSAttributedString(string: "Hello")
    let line = CTLineCreateWithAttributedString(attributedString)
    var ascent: CGFloat = 0.0
    let width = CTLineGetTypographicBounds(line, &ascent, nil, nil)

    CGContextSaveGState(context)

    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextTranslateCTM(context, 0, -viewHeight)
    CGContextSetTextPosition(context, 0, viewHeight - ascent)
    CTLineDraw(line, context)

    CGContextRestoreGState(context)

    UIGraphicsPopContext()
}
Joe Smith
  • 1,900
  • 1
  • 16
  • 15
0

Try this out.

float fonsize = 30.0;
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:fonsize]};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"HelloWorld"
                                                             attributes:attributes];
CGSize stringBoundingBox = [string.string sizeWithFont:[UIFont systemFontOfSize:fonsize]];
CGRect rect = CGRectMake(50, 50, stringBoundingBox.width,stringBoundingBox.height);

UILabel* lbl = [[UILabel alloc] initWithFrame:rect];
lbl.backgroundColor = [UIColor yellowColor];
lbl.attributedText = string;
[self.view addSubview:lbl];
user1988
  • 112
  • 1
  • 5