swiftuikitappkittextkit

Getting a range of glyphs lying wholly in the given rect in NSLayoutManager


I'm trying to retrieve a glyph range for the given bounds in NSLayoutManager. The built-in methods return the range for glyphs that are wholly or partially lying inside the bounds, and I need to find out which actually fit it.

For example:

enter image description here

let glyphRange = textView.layoutManager!.glyphRange(forBoundingRect: scrollView.contentView.bounds, in: textView.textContainer!)
let charRange = textView.layoutManager!.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)

let range = Range(charRange, in: string)

string[range] now produces a substring up to "not listening to explanations useless".

I've tried creating a substring and removing stuff word by word, until the height of the string fits my needs, but that becomes very slow. I'm writing a method (for both macOS and iOS) which has to take care of hundreds of such calculations in a very short time.

How could I return the range for glyphs wholly inside the bounds?


Solution

  • This can be achieved by enumerating line fragments. They are already laid out, so it comes with not much extra cost.

    let desiredHeight = 300
    var height = 0.0
    textView.layoutManager!.enumerateLineFragments(forGlyphRange: glyphRange) { rect, usedRect, container, range, stop in
        if usedRect.height + usedRect.origin.y <= desiredHeight {
            height += usedRect.height 
        }
    }
    
    let newBounds = NSRect(x: 0, y:0, width: width, height: height)
    let newGlyphRange = textView.layoutManager!.glyphRange(forBoundingRect: scrollView.contentView.bounds, in: textView.textContainer!)
    let newCharRange = textView.layoutManager!.characterRange(forGlyphRange: newGlyphRange, actualGlyphRange: nil)
    
    let newRange = Range(newCharRange, in: string)