stringWithFormat / Rendering Text (iOS)

  • I have need of rendering text manually to a UIView, along with graphics.
    I realise that I could probably use a UITextView as a subview and
    position it, but for now I'd like to learn the right way to render it
    manually.

    My aim is to render a particular string in a particular colour, in a
    particular font (or, any Sans Serif of the system's choosing would be
    enough here) and a particular font size.

    First, a quick question about +stringWithFormat: If I want to have a
    literal string as one of the parameters to the format, which is correct
    / best:

    NSString *textToDraw = [NSString stringWithFormat: @"%s",
    "my string"];

    or

    NSString *textToDraw = [NSString stringWithFormat: @"%@",
    @"my string"];

    (or something else?).

    Now, I'm led to believe that you can either use CGContextXXX calls, or
    Cocoa text drawing. I seemed to fall flat on my face with the Cocoa way,
    so I was trying to use this:
                                   CGContextShowTextAtPoint(contextRef, 0, 40,
    [textToDraw UTF8String], [textToDraw length]);

    (since -cString: is deprecated). But I end up with garbage characters
    and I don't know if I'm using +stringWithFormat: incorrectly, or
    CGContextShowTextAtPoint().

    What is the 'correct' or best recommended way to render text, lines and
    filled primitives? For the Cocoa way of rendering text, how do I control
    font face / size? The sample I found used NSDictionary for the
    attributes but it won't compile the NSFontAttributeName the (web) sample
    used for the key.

    Thanks in advance.

    --
    Jason Teagle
  • Jason,

    Have a look at this doc-page:
    https://developer.apple.com/library/mac/ipad/#documentation/graphicsimaging
    /conceptual/drawingwithquartz2d/dq_text/dq_text.html

    It discusses your options when drawing text with Quartz2D, and also mentions other techniques. One option of note is the NSString additions for UIKit for drawing text, which does most of what you want "out of the box".

    Mikkel

    Sent from my iPad

    On 24 May 2012, at 09:49, Jason Teagle <jt.a.la.mac...> wrote:

    > I have need of rendering text manually to a UIView, along with graphics. I realise that I could probably use a UITextView as a subview and position it, but for now I'd like to learn the right way to render it manually.
    >
    > My aim is to render a particular string in a particular colour, in a particular font (or, any Sans Serif of the system's choosing would be enough here) and a particular font size.
    >
    > First, a quick question about +stringWithFormat: If I want to have a literal string as one of the parameters to the format, which is correct / best:
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%s",
    > "my string"];
    >
    > or
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%@",
    > @"my string"];
    >
    > (or something else?).
    >
    >
    > Now, I'm led to believe that you can either use CGContextXXX calls, or Cocoa text drawing. I seemed to fall flat on my face with the Cocoa way, so I was trying to use this:
    > CGContextShowTextAtPoint(contextRef, 0, 40,
    > [textToDraw UTF8String], [textToDraw length]);
    >
    > (since -cString: is deprecated). But I end up with garbage characters and I don't know if I'm using +stringWithFormat: incorrectly, or CGContextShowTextAtPoint().
    >
    > What is the 'correct' or best recommended way to render text, lines and filled primitives? For the Cocoa way of rendering text, how do I control font face / size? The sample I found used NSDictionary for the attributes but it won't compile the NSFontAttributeName the (web) sample used for the key.
    >
    > Thanks in advance.
    >
    > --
    > Jason Teagle
  • Greetings Jason,

    On 5/24/12 12:49 AM, Jason Teagle wrote:
    > I have need of rendering text manually to a UIView, along with graphics.
    > I realise that I could probably use a UITextView as a subview and
    > position it, but for now I'd like to learn the right way to render it
    > manually.

    Please see the last paragraphs below for a discussion of why rendering
    manually is almost certainly the wrong way.

    You probably want UILabel.  UITextView is a much more heavyweight class
    that supports editing as well as display.

    For displaying small strings, UILabel is the way to go.

    > My aim is to render a particular string in a particular colour, in a
    > particular font (or, any Sans Serif of the system's choosing would be
    > enough here) and a particular font size.
    >
    > First, a quick question about +stringWithFormat: If I want to have a
    > literal string as one of the parameters to the format, which is correct
    > / best:
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%s",
    > "my string"];
    >
    > or
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%@",
    > @"my string"];

    These both are just wasting CPU cycles and adding extraneous code.

    Simply use:

    NSString *textToDraw = @"my string";

    > Now, I'm led to believe that you can either use CGContextXXX calls, or
    > Cocoa text drawing. I seemed to fall flat on my face with the Cocoa way,
    > so I was trying to use this:

    Whoa, let's back up here.  There is no way that using Quartz/Core
    Graphics is *easier* than using the Cocoa text drawing functionality
    (particularly the NSString UIKit additions).  If you "fall flat" using
    the latter, you shouldn't even consider making life yet harder for yourself.

    What did you try in Cocoa and what were the results?

    > CGContextShowTextAtPoint(contextRef, 0, 40,
    > [textToDraw UTF8String], [textToDraw length]);
    >
    > (since -cString: is deprecated). But I end up with garbage characters
    > and I don't know if I'm using +stringWithFormat: incorrectly, or
    > CGContextShowTextAtPoint().
    >
    > What is the 'correct' or best recommended way to render text, lines and
    > filled primitives? For the Cocoa way of rendering text, how do I control
    > font face / size? The sample I found used NSDictionary for the
    > attributes but it won't compile the NSFontAttributeName the (web) sample
    > used for the key.

    NSFontAttributeName is declared in the NSAttributedString *AppKit*
    additions, which isn't available on iOS.  Core Text on iOS has
    kCTFontAttributeName, but Core Text is a whole other beast that doesn't
    really apply to your situation (and is definitely NOT something to
    tackle when getting started).

    (As a general rule, text presentation is one area where there is little
    overlap between iOS and OS X, so it's important to pay close attention
    to which platform is targeted by any sample code.)

    Back to your problem at hand, if your text snippet is all drawn using
    the same font attributes, just use UILabel and position it alongside/on
    top of one or more UIImageViews for your graphics.

    If you really need to do custom drawing for some other purpose, use the
    NSString UIKit additions.  There is *very* little reason in most cases
    to start messing with CGContextShowTextAtPoint() and friends.

    Why do I recommend avoiding your own drawing entirely if possible?  In
    addition to adding lots more work (and confusion) for yourself by
    tackling explicit drawing, you are going to introduce performance
    bottlenecks, especially when you start interacting with, say, a
    UIScrollView.  The UIKit classes have been highly optimized for their
    purpose-built tasks, and getting such performance out of your own
    drawing code can be quite challenging.

    (Oh, and for text in particular, you also lose important features like
    text selection that the framework classes provide!)

    In Cocoa, you should always try to accomplish a task through class
    composition rather than subclassing (which is of course required for
    custom drawing).  Probably 90% of what people start doing in -drawRect:
    can be done by appropriate composition of framework classes.

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • > Have a look at this doc-page:
    > https://developer.apple.com/library/mac/ipad/#documentation/graphicsimaging
    /

    > conceptual/drawingwithquartz2d/dq_text/dq_text.html
    > It discusses your options when drawing text with Quartz2D, and also mentions
    > other techniques.

    > One option of note is the NSString additions for UIKit for
    > drawing text, which does most of what you want "out of the box".

    I did read the document you mentioned (thanks for the link), it was
    where I got the CGContextXXX versions from. Unfortunately, although it
    mentions 'the Cocoa way' and the additions (or I might have seen about
    the additions in other bits I read), there were no samples or links to
    them (at least, not in the copy of that page that is part of the
    documentation that comes with Xcode).

    Having had a go from the CGContextXXX sample myself and got gibberish, I
    felt I had to ask here for some help. The problem might simply be the
    name of the font I'm using. 'Arial' produces the gibberish, 'Chicago'
    (harvested from another sample I found on the web) gives me a blank.

    Again, the docs for CGContextSelectFont() don't provide a link to what
    names are acceptable for the parameter.

    Luckily, I found another sample later that did it the Cocoa way and
    after a little trial and error, I got it working.

    --
    Jason Teagle
  • > You probably want UILabel.  UITextView is a much more heavyweight class
    > that supports editing as well as display.

    (My bad, a label is called a TextView on Android - got mixed up.)

    > These both are just wasting CPU cycles and adding extraneous code.
    >
    > Simply use:
    >
    > NSString *textToDraw = @"my string";

    OK, I guess I didn't expect anyone to take me literally there. I
    obviously wouldn't use that technique for a single string - I did say
    'one of the parameters'. I only cut out other parameters for clarity.

    (I've since discovered that the %@ and @"xxx" way works just fine, so
    I'll stick with that. My doubts came from CGContextXXX not working
    properly.)

    > Whoa, let's back up here.  There is no way that using Quartz/Core
    > Graphics is *easier* than using the Cocoa text drawing functionality
    > (particularly the NSString UIKit additions).  If you "fall flat" using
    > the latter, you shouldn't even consider making life yet harder for yourself.

    It wasn't that it was easier - it was that the CGContextXXX sample I had
    (from the Apple docs) compiled but didn't quite work (so almost a
    success), but the Cocoa sample I found (not from their docs - if it's in
    them somewhere I haven't found it yet) didn't even compile.

    > What did you try in Cocoa and what were the results?
    ...
    > NSFontAttributeName is declared in the NSAttributedString *AppKit*
    > additions, which isn't available on iOS.  Core Text on iOS has

    That was what was wrong - couldn't compile it.

    I've since found a working Cocoa sample - the problem of not being able
    to use the font I wanted was that I hadn't yet found
    -drawAtPoint:withFont:. I've got it working now.

    (And no, I haven't forgotten that using UILabel is preferable.)

    --
    Jason Teagle
  • > CGContextShowTextAtPoint(contextRef, 0, 40,
    > [textToDraw UTF8String], [textToDraw length]);

    One bug I see here: you're passing a wrong length parameter.
    CGContextShowTextAtPoint expects the length of the char array (number of bytes of the UTF-8 encoded string), but you'e passing the number of Unicode characters in the string. That's not the same; one character may be represented by multiple chars in UTF-8 encoding.

    E.g. use it this way:

    const char *myUTF8String = [textToDraw UTF8String];
    CGContextShowTextAtPoint(contextRef, 0, 40,
    myUTF8String, strlen(myUTF8String));

    Regards,
    Mani
  • On 24 May 2012, at 15:36, Manfred Schwind wrote:
    >> CGContextShowTextAtPoint(contextRef, 0, 40,
    >> [textToDraw UTF8String], [textToDraw length]);
    >
    > One bug I see here: you're passing a wrong length parameter.
    > CGContextShowTextAtPoint expects the length of the char array (number of bytes of the UTF-8 encoded string), but you'e passing the number of Unicode characters in the string. That's not the same; one character may be represented by multiple chars in UTF-8 encoding.
    >
    > E.g. use it this way:
    >
    > const char *myUTF8String = [textToDraw UTF8String];
    > CGContextShowTextAtPoint(contextRef, 0, 40,
    > myUTF8String, strlen(myUTF8String));

    That's not true.  CGContextShowText... routines expect the length to be in bytes, and the encoding to be what you specified in your CGContextSelectFont call.  At the moment, you have a choice between the 8-bit MacRoman, and 'kCGEncodingMacRoman' and the cryptic 'kCGEncodingFontSpecific'.  You should never pass UTF-8 text to a CGContextShowText... routine - if there's anything in there that isn't in the intersecting subset of ASCII and MacRoman, you're likely to just get garbled symbols out.

    You can pass byte strings that you've converted to MacRoman, but MacRoman encoding represents quite a small subset of Unicode by modern standards - I would not recommend using it.

    The CG levels don't do any sort of character to glyph mapping beyond this, so it's /impossible/ to display unicode text with them without parsing the fonts and doing the character to glyph mapping yourself (and then potentially worrying about font substitution if there are no glyphs to represent the characters you want etc...) then using the CGContextShowGlyphs... routines.

    Really, just stick to UIKit string drawing, or CoreText if you want more control than that gives you.  These frameworks might be more expensive than CoreGraphics in runtime terms, but that's because they're doing all the things that are necessary to render text well nowadays, not because they're inefficient.

    Jamie.
  • On May 24, 2012, at 9:36 AM, Manfred Schwind wrote:

    >> CGContextShowTextAtPoint(contextRef, 0, 40,
    >> [textToDraw UTF8String], [textToDraw length]);
    >
    > One bug I see here: you're passing a wrong length parameter.
    > CGContextShowTextAtPoint expects the length of the char array (number of bytes of the UTF-8 encoded string), but you'e passing the number of Unicode characters in the string. That's not the same; one character may be represented by multiple chars in UTF-8 encoding.
    >
    > E.g. use it this way:
    >
    > const char *myUTF8String = [textToDraw UTF8String];
    > CGContextShowTextAtPoint(contextRef, 0, 40,
    > myUTF8String, strlen(myUTF8String));
    >

    Also, CGContextShowTextAtPoint is designed only to display text in MacRoman encoding (and a corresponding font that has MacRoman character data tables).

    If the string has anything other than ASCII text, the UTF8String won't be in MacRoman (and in general, MacRoman is a very small subset of what Unicode supports, so even if you got the string in MacRoman encoding, there is a very good chance it still won't draw correctly because somebody used a character outside of MacRoman).

    From the docs:
    > If setting the font to a MacRoman text encoding is sufficient for your application, use the CGContextSelectFont function. Then, when you are ready to draw the text, you call the function CGContextShowTextAtPoint. The function CGContextSelectFont takes as parameters a graphics context, the PostScript name of the font to set, the size of the font (in user space units), and a text encoding.
    >
    > To set the font to a text encoding other than MacRoman, you can use the functions CGContextSetFont and CGContextSetFontSize. You must supply a CGFont object to the function CGContextSetFont. You call the function CGFontCreateWithPlatformFont to obtain a CGFont object from an ATS font. When you are ready to draw the text, you use the function CGContextShowGlyphsAtPoint rather thanCGContextShowTextAtPoint.
    >

    So for general case drawing, you'll need to generate glyphs from the text, which is an extremely complicated procedure to do without some sort of type-setter support (such as found in CoreText or (not available on iOS) NSTypesetter).

    Basically, CGContextShowTextAtPoint is designed to show simple debugging information and very limited/controlled strings, not a general purpose drawing routine.

    Just use NSString's drawAtPoint:withFont: and the like...

    Glenn Andreas                      <gandreas...>
    The most merciful thing in the world ... is the inability of the human mind to correlate all its contents - HPL
  • On May 24, 2012, at 10:36 AM, Manfred Schwind <lists...> wrote:

    >> CGContextShowTextAtPoint(contextRef, 0, 40,
    >> [textToDraw UTF8String], [textToDraw length]);
    >
    > One bug I see here: you're passing a wrong length parameter.

    Another bug: CGContextShowztextAtPoint takes characters encoded in MacRoman, not UTF8.

    The real bug: you should not be using CGContextShowTextAtPoint. Ever.

    --Kyle Sluder
  • On May 24, 2012, at 5:50 AM, Jason Teagle wrote:

    > (I've since discovered that the %@ and @"xxx" way works just fine, so I'll stick with that.

    Good :) The problem with using "%s" is that it doesn't work well with non-ASCII characters. Cocoa has to assume some character encoding to map the bytes to Unicode, and the rules for what encoding it chooses are both obscure and not very useful. (In short: it depends on the user's language/locale, and the default for English is the obsolete MacRoman encoding, not UTF-8 or even ISO-Latin-1.)

    To reiterate: the only time you should use "%s" is if the C string you're passing in is guaranteed to be pure ASCII. Which is not very common.

    In other circumstances, use "%@" and convert the C string to an NSString using -[NSString initWithCString:encoding:].

    —Jens
  • On May 24, 2012, at 2:49 AM, Jason Teagle wrote:

    > First, a quick question about +stringWithFormat: If I want to have a literal string as one of the parameters to the format, which is correct / best:
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%s",
    > "my string"];
    >
    > or
    >
    > NSString *textToDraw = [NSString stringWithFormat: @"%@",
    > @"my string"];

    Given what's been said elsewhere about these being cut-down example snippets and not anything real...  Never use the "%s" format specifier.  It interprets the C string argument in a completely unreliable encoding.  If the argument contains or could contain any non-ASCII characters, you're likely to get a nasty surprise.  Worse, the nasty surprise may not show up until your code is run on a system set to a language/locale other than your usual one.

    Regards,
    Ken
  • Thanks for all the replies regarding %s vs. %@, and the rather
    disastrous attempt at using CGContextXXX methods. Even if I had fixed
    all the other issues, I was using CGContextSelectFont incorrectly
    anyway. I didn't have a hope. That, unfortunately, is down to
    documentation not being complete / clear enough - or hiding the big
    picture in a multitude of different places making it hard to piece
    together.

    If the CGContextXXX methods shouldn't be used, then they shouldn't be in
    the API - or should be marked as deprecated. There's nothing in the docs
    (with Xcode 3.X, at least - maybe it's changed since) to indicate that
    these were the worst way possible to render text. If you're trying to
    learn this stuff from the docs it's pretty discouraging to be led
    astray. People (elsewhere, not saying anyone here did it) like to tell
    us to RTFM but that just shows you where that got me.

    If they had just given me a clean, working sample using
    -drawAtPoint:withFont: (in the 'How to draw text' section) none of this
    would have happened... but then I wouldn't have learned as much {:v)

    Onward and upward...

    --
    Jason Teagle
  • On 24 May 2012, at 1:57 PM, Jason Teagle wrote:

    > If the CGContextXXX methods shouldn't be used, then they shouldn't be in the API - or should be marked as deprecated. There's nothing in the docs (with Xcode 3.X, at least - maybe it's changed since) to indicate that these were the worst way possible to render text. If you're trying to learn this stuff from the docs it's pretty discouraging to be led astray. People (elsewhere, not saying anyone here did it) like to tell us to RTFM but that just shows you where that got me.

    First, "the CGContextXXX methods" (Quartz/Core Graphics) are irreplaceable API in constant use. Believe me, nobody has told you to ignore Core Graphics.

    What you have been told is that the Core Graphics primitive _for drawing text_ should not be used _for your purpose._ CG text drawing is exactly what you'd need if you were hand-rendering PDFs. You weren't anywhere near there.

    (Even the more-common applications of Core Graphics often have simpler, higher-level equivalents in UIKit. You've already found the UIKit extensions to NSString. See also UIBezierCurve for easier shape drawing, and Core Animation for composing an interface out of independent, persistent elements.)

    Second — and I think you have been told this here — trolling the bottom-level documentation to pick out API that looks relevant to your concepts is the wrong way to get into Cocoa. Crawling through the API at micro scale is what got you into this corner.

    We're not being patronizing when we urge you to read the conceptual documents FIRST. Even if you already know everything from elsewhere. Cocoa is not Windows/Android gone wrong. It is based on its own coherent conceptual foundation, and you really do have to understand the common themes before you can grasp the details.

    For a better exploration for why the documentation is as it is, and how to approach it, see <http://www.cocoabuilder.com/archive/cocoa/206957-the-challenge-for-cocoa-on
    -line-documentation.html
    > — at least read the first post, by the incomparable Erik Buck.

    — F

    --
    Fritz Anderson -- Xcode 4 Unleashed: Now in stores! -- <http://x4u.manoverboard.org/>
previous month may 2012 next month
MTWTFSS
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
Go to today