找不到任何线索来管理这个问题。
NSTextView默认情况下选择整个文本容器的大小。它忽略了行间距、头部或尾部缩进等等。但在Pages应用程序中,选择不会突出显示那些辅助部分,只会突出显示字符。即使文本容器的高度较小(段前段后间距),它也会突出显示该行的所有高度。
我想实现这种行为,但不知道从哪里开始。我已经在这里搜索过,在Apple的文档中搜索过,在示例项目中尝试过。什么都没有。
也许有人可以引导我走向正确的方向?谢谢!
找不到任何线索来管理这个问题。
NSTextView默认情况下选择整个文本容器的大小。它忽略了行间距、头部或尾部缩进等等。但在Pages应用程序中,选择不会突出显示那些辅助部分,只会突出显示字符。即使文本容器的高度较小(段前段后间距),它也会突出显示该行的所有高度。
我想实现这种行为,但不知道从哪里开始。我已经在这里搜索过,在Apple的文档中搜索过,在示例项目中尝试过。什么都没有。
也许有人可以引导我走向正确的方向?谢谢!
public override func fillBackgroundRectArray(_ rectArray: UnsafePointer<CGRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: OSColor) {
// if characters are selected, make sure that we draw selection of those characters only, not the whole text container bounds
guard let textView = textContainer(forCharacterIndex: charRange.location)?.textView,
NSIntersectionRange(textView.selectedRange(), charRange).length > 0,
let textStorage = self.textStorage as? ParagraphTextStorage else {
super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color)
return
}
let selectedGlyphRange = self.glyphRange(forCharacterRange: charRange, actualCharacterRange: nil)
var selectionRectArray: [CGRect] = []
enumerateLineFragments(forGlyphRange: selectedGlyphRange) { (rect, usedRect, textContainer, glyphRange, stop) in
let lineCharRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
let intersection = NSIntersectionRange(charRange, lineCharRange)
// if selected all characters of the line, then we already have teir layout rects
if intersection == lineCharRange {
let paragraphIndex = textStorage.paragraphIndex(at: intersection.location)
let paragraphRange = textStorage.paragraphRanges[paragraphIndex]
let hasNewLineChar = lineCharRange.max == paragraphRange.max && paragraphRange.max < textStorage.length ||
paragraphRange.max == lineCharRange.max && intersection.max == textStorage.length && paragraphIndex < textStorage.paragraphRanges.count - 1
let newLineCharSize = hasNewLineChar ? self.newLineCharSize : .zero
let lineRect = CGRect(x: usedRect.origin.x + textView.textContainerInset.width + textContainer.lineFragmentPadding,
y: usedRect.origin.y + textView.textContainerInset.height - (rect.height - usedRect.height),
width: usedRect.width + newLineCharSize.width - textContainer.lineFragmentPadding * 2,
height: rect.height)
selectionRectArray.append(lineRect)
} else {
// calculate rect for partially selected characters of the line
let partialRect = self.usedLineRect(forCharacterRange: intersection, in: textContainer)
selectionRectArray.append(partialRect)
}
}
super.fillBackgroundRectArray(selectionRectArray, count: selectionRectArray.count, forCharacterRange: charRange, color: color)
}
public func usedLineRect(forCharacterRange charRange: NSRange, in textContainer: NSTextContainer) -> CGRect {
guard let textView = textContainer.textView, let textStorage = textStorage as? ParagraphTextStorage else { return .zero }
let glyphRange = self.glyphRange(forCharacterRange: charRange, actualCharacterRange: nil)
let textContainer = self.textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) as! ModernTextContainer
let paragraphIndex = textStorage.paragraphIndex(at: charRange.location)
let paragraphRange = textStorage.paragraphRanges[paragraphIndex]
let hasNewLine = paragraphRange.max == charRange.max && charRange.max < textStorage.length ||
paragraphRange.max == charRange.max && charRange.max == textStorage.length && paragraphIndex < textStorage.paragraphRanges.count - 1
let newLineCharSize = hasNewLine ? self.newLineCharSize : .zero
// if new line is in range, boundingRect will return the whole width of the text container, fix that
let noNewLineGlyphRange = hasNewLine ? NSRange(location: glyphRange.location, length: glyphRange.length - 1) : glyphRange
let charRect = boundingRect(forGlyphRange: noNewLineGlyphRange, in: textContainer)
let lineRect = lineFragmentRect(forGlyphAt: noNewLineGlyphRange.location, effectiveRange: nil, withoutAdditionalLayout: true)
#if os(macOS)
// respect the flipped coordinate system with abs function
let rect = CGRect(x: charRect.origin.x + textView.textContainerInset.width,
y: abs(charRect.origin.y + textView.textContainerInset.height - (lineRect.height - charRect.height)),
width: charRect.width + newLineCharSize.width,
height: lineRect.height)
#else
let rect = CGRect(x: charRect.origin.x + textView.textContainerInset.left,
y: abs(charRect.origin.y + textView.textContainerInset.top - (lineRect.height - charRect.height)),
width: charRect.width + newLineCharSize.width,
height: lineRect.height)
#endif
return rect
}
NSTextView
,使用NSLayoutManager
来处理所有的布局和绘制。 NSLayoutManager
处理所有的unicode / bidi怪异之处,给出字形运行和单个字形的精确像素坐标,以及绘制它们的方法。 临时属性可能不足以实现不同的选择高度; 在这种情况下,您应该能够自己绘制选择(在文本字形下方的背景上)。 然而,对于这样一个小的UI细节,这肯定会是很多工作。