Say I have NSTextView
and custom NSRulerView
:
Now I want every wrapped line to indent say on 3 spaces (like on image below).
How to achieve it without modifying text storage attributes (i.e. Paragraph style)? Should I use NSLayoutManager
? Thanks!
Sample code I found here: https://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/. There is a zip-file attachment at the bottom of the page.
UPDATE 1.
To indent wrapped lines the NSATSTypesetter
subclassing needed instead of NSLayoutManager
customisation. Here is an example of LineWrappingTypesetter
:
import AppKit
class LineWrappingTypesetter: NSATSTypesetter {
var textString: TextString?
var lineInset: CGFloat = 0
override func getLineFragmentRect(_ lineFragmentRectIn: NSRectPointer!,
usedRect usedRectIn: NSRectPointer!,
remaining remainingRectIn: NSRectPointer!,
forStartingGlyphAt startingGlyphIndex: Int,
proposedRect: NSRect,
lineSpacing: CGFloat,
paragraphSpacingBefore: CGFloat,
paragraphSpacingAfter: CGFloat) {
func defaultImplementation() {
super.getLineFragmentRect(lineFragmentRectIn, usedRect: usedRectIn, remaining: remainingRectIn,
forStartingGlyphAt: startingGlyphIndex, proposedRect: proposedRect,
lineSpacing: lineSpacing, paragraphSpacingBefore: paragraphSpacingBefore,
paragraphSpacingAfter: paragraphSpacingAfter)
}
guard let textString = textString, lineInset > 0, let layoutManager = layoutManager else {
defaultImplementation()
return
}
let characterIndex = layoutManager.characterIndexForGlyph(at: startingGlyphIndex)
guard let cursor = textString.cursor(from: characterIndex) else {
defaultImplementation()
return
}
guard cursor.column > 1 else {
defaultImplementation()
return
}
var lineFragment = NSRect()
var used = NSRect()
var remaining = NSRect()
super.getLineFragmentRect(&lineFragment, usedRect: &used, remaining: &remaining,
forStartingGlyphAt: startingGlyphIndex, proposedRect: proposedRect,
lineSpacing: lineSpacing, paragraphSpacingBefore: paragraphSpacingBefore,
paragraphSpacingAfter: paragraphSpacingAfter)
lineFragment = lineFragment.insetLeftBy(lineInset)
used = used.insetLeftBy(lineInset)
var size = remaining.size.insetBy(dx: lineInset, dy: 0)
if size < CGSize.zero {
size = .zero
}
remaining = CGRect(origin: remaining.origin, size: size)
if lineFragmentRectIn != nil {
lineFragmentRectIn.pointee = lineFragment
}
if usedRectIn != nil {
usedRectIn.pointee = used
}
if remainingRectIn != nil {
remainingRectIn.pointee = remaining
}
}
}
Where: TextString
class discussed in this SO question.
Idea behind: We calling base implementation of method getLineFragmentRect
and adjusting line fragment rectangle if we processing glyph, which is not a first column character of the current line in the string.