swiftmacosnstextviewnslayoutmanager

Control number of characters on each line in NSTextView


I am trying to limit the number of characters on each line in an NSTextView to a multiple of 10.

Let's say the current width displays 40 characters. Now when the user makes the window wider, the first 10 characters on the second line shouldn't move to the end of the first line until the width to fit 50 characters has been reached.

This screams to use NSLayoutManager but I have not yet found what method, maybe

optional func layoutManager(
    _ layoutManager: NSLayoutManager,
    shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<NSRect>,
    lineFragmentUsedRect: UnsafeMutablePointer<NSRect>,
    baselineOffset: UnsafeMutablePointer<CGFloat>,
    in textContainer: NSTextContainer,
    forGlyphRange glyphRange: NSRange
) -> Bool

My text only has letters of the alphabet, using a monospaced font.

Any suggestions?


Solution

  • So, yes, adding an exclusionPath did the trick (in MyTextView):

    override func setBoundsSize(_ newSize: NSSize) {
        super.setBoundsSize(newSize)
            
        textContainer?.exclusionPaths = exclusionPaths()
    }
    
    func exclusionPaths() -> [NSBezierPath] {
        guard let tc = textContainer, let lm = tc.layoutManager else { return [] }
    
        let groupInterval = 10 
        let groupGlyphRect = lm.boundingRect(forGlyphRange: NSMakeRange(0, groupInterval), in: tc)
            
        let viewWidth = textContainer?.textView?.frame.width ?? 0
        var currentX = 0.0
     
        while currentX < viewWidth {
            currentX += groupGlyphRect.width
    
            let remainder = viewWidth - currentX
            if remainder < groupGlyphRect.width {
                let remainderRect = CGRect(x: currentX, y: 0, width: remainder, height: CGFloat.greatestFiniteMagnitude)
                return [(NSBezierPath(rect: remainderRect))]
            }
        }
            
        return []
    }