Getting a glyph boundingRect in draw#rect in UILabel
Asked Answered
E

2

7

Using Swift, I want to get the boundingRect of a glyph, in draw#rect in a UILabel.

The UILabel already has a size (say 300x300 in the example) and qualities such as the text being centered.

class RNDLabel: UILabel {

    override func draw(_ rect: CGRect) {

        let manager = NSLayoutManager()

        let store = NSTextStorage(attributedString: NSAttributedString(
            string: text!,
            attributes: [NSAttributedString.Key.font: font]))
        store.addLayoutManager(manager)

        let textContainer = NSTextContainer(size: rect.size)
        // note, intrinsicContentSize is identical there, no difference
        manager.addTextContainer(textContainer)

        let glyphRange = manager.glyphRange(
           forCharacterRange: NSRange(location: 0, length: 1),
           actualCharacterRange: nil)
        let glyphRect = manager.boundingRect(
           forGlyphRange: glyphRange, in: textContainer)

        print("glyphRect \(glyphRect)")         
        ...context?.addRect(glyphRect), context?.drawPath(using: .stroke)

        super.draw(rect)
    }

enter image description here

The green square is not correct - it should be more like these red squares!

enter image description here

There seems to be a number of problems:

  • surely the layoutManager I make, should get the qualities of the UILabel, eg "centered text"? (I believe you can't actually directly access the layoutManager of a UILabel, though?)

  • should we be using something like CTLineGetOffsetForStringIndex? Is that even possible in draw#rect

  • notice as well as not having the correct offset, the green box seems the wrong height anyway. (It looks more like a plain old intrinsicContentSize rather than a glyph bounding box.)

How to?

For the record, my overall aim is to move the glyph around, based on the actual glyph box (which of course is different for "y", "X", etc). But in general there are many useful reasons to know the box of a glyph.

Eyebrow answered 8/7, 2019 at 13:23 Comment(10)
Quite simply, isn't the problem that the TextKit stack is not directly germane to a UILabel? UILabel drawing is not TextKit drawing. What you're asking to do would be a lot more straightforward if this were a UITextView, or just a view that you draw yourself using TextKit.Fraught
@Fraught - thanks - (1) I just don't know. (2) I realize, well I have heard that, in general UItextView is more amenable. However (3) I do wanna do this in a UILabel, and, you'd think there'd be a way. You can see the code compiles and runs fine, it is perhaps just missing an offset?Eyebrow
Yes but you will have a hard time finding out what that offset is. "you'd think there'd be a way" No, I wouldn't. Maybe you would think that. Personally, if I had a goal like dragging glyphs around, I wouldn't be starting with a UILabel. I'm one of those people who prefers to use the framework rather than to fight it.Fraught
I see - are you thinking that because fundamentally they don't expose the layoutManager in UILabel? {It occurs to me, I guess one could make a fake, offscreen UITextView with the same dimensions and qualities, and find out the rect?? Or .... can't we just "set the qualities we know" (from the UILabel) in the layoutManager we make there ??}Eyebrow
Basically, yes. A UITextView can be made to act a lot like a UILabel (make it noneditable and nonscrollable) but with the important difference that the whole text kit stack is directly exposed. So that's where I'd start. What you do is up to you, of course.Fraught
"they don't expose the layoutManager in UILabel." Why do you believe that UILabel has a layout manager? I don't see one using lldb or Hopper. I could be missing something, but I wouldn't particularly expect Apple to have retrofitted UILabel to use NSLayoutManager. It's possible, but do you have some reason to expect it's true? Decompiling drawText(in:), it seems to call textRect(forBounds:), which seems to be doing layout by hand. I don't see any layout managers.Seaver
I've built what you're describing a few times in the past; I've always just done the layout with Core Text. If I were writing it again today, maybe I'd use TextKit. But I don't understand why you're trying to build this on UILabel. If it's just as a curiosity, that's great, but I'd spend some time in Hopper to see how UILabel works under the covers. It's mostly built on CoreGraphics and NSAttributedString, not CoreText or NSLayoutManager.Seaver
BTW, I thought "ah, why not? I'll just reverse engineer -[UILabel _drawTextInRect:baselineCalculationOnly:] enough to solve this problem" and hahahahahahah. The Hopper decompile is nearly 1000 lines of pseudo-C code filled with special cases and hahahah. Nah. Maybe it's a fun project, but I wouldn't bother with it. Just build a simple label with TextKit or CoreText that works the way you want, and drag it around.Seaver
Hi @RobNapier "But I don't understand why you're trying to build this on UILabel" (same to @matt) it couldn't be simpler to answer - the shock and surprise is a bit dumbfounding? - surely you've worked on large projects? Imagine some widely-used feature from previous teams that uses UILabel: naturally you'd try to just mod it. Your excellent investigation ("1000 lines ..!") seems to be the last word, thanks! So just add a textview on top.Eyebrow
Nah; you're right. If you've been digging around in the text layout system for a few years and know the history, it feels obvious that tweaking UILabel is never what you want, and that it's very custom (and particularly that it pre-dates CoreText by a bit, and TextKit by a lot), but it's not obvious if you come to it fresh. But trying to precisely match UILabel or to nontrivially modify it is a classic headache that's best avoided.Seaver
E
1

It turns out the answer to this question seems to be:

In fact, surprisingly or not, you basically have no access to glyph information in UILabel specifically.

So realistically you can't get those glyph "actual shapes" in UILabel.

In particular RobN. has pointed out that an investigation showed that in UILabel, _drawTextInRect:baselineCalculationOnly does the work and that is a big pile of ad hoc code.

A summary of the situation would seem to be that UILabel simply predates NSLayoutManager / Core Text and (as yet) just plain does not use those modern systems.

Only those modern systems give you this ...

enter image description here

... sort of access to glyph-by-glyph shapes.

Eyebrow answered 15/7, 2019 at 11:2 Comment(0)
F
3

Your original question is sort of an unquestion, because it says two completely opposite things.

  • On the one hand, you say UILabel.

  • On the other hand, you use terms like NSLayoutManager and NSTextStorage and NSTextContainer — the TextKit stack.

Those are opposites because UILabel is not drawn with the TextKit stack. So what you're asking to do is like trying to lift a piece of paper with a magnet; magnets do lift things, but what they lift are magnetic materials, and paper is not one of those. To be sure, UILabel must draw in some deterministic way, but what that way is, is completely unknown and opaque and has nothing to do with TextKit.

On the other hand, UITextView, or a view that your draw yourself using TextKit, does use TextKit, and now all the TextKit tools apply. So what you're asking to do would be a lot more straightforward if this were a UITextView (or a view that you draw yourself using TextKit).

This need not change anything very much about the appearance of the interface. A UITextView can be made to act a lot like a UILabel (make it noneditable and nonscrollable) but with the important difference that the whole text kit stack is directly exposed. So that's where I'd start. I'm one of those people who prefers to use the framework rather than to fight it.

Fraught answered 17/7, 2019 at 15:52 Comment(0)
E
1

It turns out the answer to this question seems to be:

In fact, surprisingly or not, you basically have no access to glyph information in UILabel specifically.

So realistically you can't get those glyph "actual shapes" in UILabel.

In particular RobN. has pointed out that an investigation showed that in UILabel, _drawTextInRect:baselineCalculationOnly does the work and that is a big pile of ad hoc code.

A summary of the situation would seem to be that UILabel simply predates NSLayoutManager / Core Text and (as yet) just plain does not use those modern systems.

Only those modern systems give you this ...

enter image description here

... sort of access to glyph-by-glyph shapes.

Eyebrow answered 15/7, 2019 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.