3

Code Sample

I have a NSLayoutManager, NSTextContainer & NSTextStorage as properties in a custom NSView (not a TextView) initialized in awakeFromNib() as follows:

    textStorage = NSTextStorage(attributedString: self.attributedString)

    layoutManager = NSLayoutManager()
    textContainer = NSTextContainer(containerSize: NSMakeSize(self.frame.size.width, 1))

    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)
    layoutManager.glyphRangeForTextContainer(textContainer)

The vertical containerSize of the NSTextContainer is deliberately set to 1 to see if it has the expected effect of hiding the text being rendered - it does not! It is making NO difference in the rendering of this text in the view - which is the subject of the question!

In drawRect I include the lines below to draw the text:

let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer)
self.lockFocus()
layoutManager.drawGlyphsForGlyphRange(glyphRange, atPoint: NSMakePoint(0, 0))
self.unlockFocus()

Findings

  1. It appears best to work with your custom view in a flipped co-ordinate system like the NSTextView else I feel like I'm going to be in for a world of pain! Irrespective of this, NSLayoutManager always starts drawing its text internally in a flipped co-ordinate system (just like NSTextView)
  2. The containerSize.width property of NSTextContainer has the following effect: it is binding on a line level for all levels including the first (I know it's obvious but stick with me...)
  3. The containerSize.height property of NSTextContainer has a curve-ball: it will NOT be binding on the first line even if the containing view does not have the room to display it vertically BUT will be binding on subsequent lines

*it took me a long time to come to this hypothesis about containerSize.height because I was only drawing one line! *

Question

  1. Are my conclusions regarding NSTextContainer correct?
  2. What is the best way to control text drawing from a vertical perspective? I am wanting to place my single line of the text at the bottom of the view (and not have it floating at the top as in the default)
Sam
  • 2,745
  • 3
  • 20
  • 42

1 Answers1

2

NSTextContainer has a containerSize property. The layout manager is laying out the text within that container. Presumably, the container is what is logically being positioned at (0, 0) in your view, but the text is laying out from its top. So, the container has slack.

You could resize the container based on the rect returned from -[NSLayoutManager usedRectForTextContainer:] to size it to fit.


Update:

I think code like this in your drawRect() should work for what you want to achieve:

layoutManager.ensureLayoutForTextContainer(textContainer)
var rect = layoutManager.usedRectForTextContainer(textContainer)
rect.origin.x += 2
rect.origin.y = NSMaxY(self.bounds) - NSHeight(rect) - 4

let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer)
layoutManager.drawGlyphsForGlyphRange(glyphRange, atPoint:rect.origin)
Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • The result of `layoutManager.usedRectForTextContainer(textContainer)` is coming out at (0,0) - naturally that's not going to help things. Strangely, even though I set my textContainer.containerSize to this (0,0) it does nothing to the ultimate view rendered!? – Sam Mar 11 '15 at 19:04
  • Stranger still: the layout of my view seems independent of the `textContainer.containerSize` in general. Have just been playing with a few starting values. – Sam Mar 11 '15 at 19:14
  • `-usedRectForTextContainer:` does not perform layout. You would have to ensure layout before getting a meaningful value from it, using `-ensureLayoutForTextContainer:`. Not sure about the other stuff. – Ken Thomases Mar 11 '15 at 19:20
  • Thanks. By calling `ensureLayoutForTextContainer(...)` I am now getting sensible values for the `containerSize`. However, still not changing how it renders. – Sam Mar 11 '15 at 19:54
  • FWIW `layoutManager.setLocation(NSMakePoint(2, 2), forStartOfGlyphRange: glyphRange)` seems to work in bringing the text down to start at the bottom of the view. Using (2,2) because (0,0) actually cuts off a little text. – Sam Mar 11 '15 at 20:16
  • Could your layout manager have more than one container, such that the one you have a reference to is not the first? That would explain why changing its size doesn't seem to affect layout. – Ken Thomases Mar 11 '15 at 21:05
  • I am now replicating the problem in a simplified example with a single view - just to avoid complications. `println(self.layoutManager.textContainers.count)` yields 1 when called in `drawRect` – Sam Mar 11 '15 at 21:42
  • Done a lot more work honing in on the problem and have hypothesised an explanation above... – Sam Mar 12 '15 at 10:51
  • Thanks. Yes, that does work. I am now figuring out how to take a lot of the processing above out of the drawRect method to optimise for scrolling...but that is another question! – Sam Mar 13 '15 at 08:24