objective-c - NSTextView 选择突出显示所有字符甚至段落缩进

标签 objective-c macos cocoa swift nstextview

找不到任何线索如何管理它。

默认情况下,NSTextView 选择突出显示其文本容器的整个大小。它忽略行间距、头部或尾部缩进等。但在 Pages 应用程序选择中不会突出显示那些辅助部分,它仅突出显示字符。即使文本容器的高度较小(前后段落间距),它也会突出显示行的所有高度。

我想实现该行为,但不知道从哪里开始。我在这里搜索过,我搜索过 Apple 文档,我尝试过示例项目。什么都没有。

也许有人可以指导我正确的方向?谢谢!

最佳答案

我发现 hamstergene 的回答不正确。事实上,NSTextView 逐行突出显示其文本容器边界。

因此,如果您使用段落的头部缩进,那么段落前导的空白区域将突出显示。如果您选择 EOL 字符,那么段落的结尾空格也会突出显示。

我的解决方案是取消段落样式的头部和尾部缩进(我将它们缓存在私有(private)变量中,并在访问我的文本存储以进行打印时将它们放回原处)并通过覆盖 lineFragmentRectForProposedRect: atIndex: writingDirection: remainingRect 我的 NSTextContainer 子类的方法。

但后来我发现了很多合适的方法。只需覆盖 func fillBackgroundRectArray(_ rectArray: UnsafePointer<NSRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: NSColor)在 NSLayoutManager 中,计算你的 rects 并用这些 rects 调用 super。如果您正确计算选择矩形,您将获得与 Apple Pages 或 MS Word 中一样的精确选择行为。

简单易行!

更新 这是我计算选择矩形的代码:

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
}

这个执行速度极快的计算的重要部分是我正在使用我自己的 ParagraphTextStorage 实现。其目的是在编辑文本存储时实时计算段落范围。了解正确的段落范围后,我可以在计算所选矩形时使用简单的整数(如 NSRange 的整数)。否则我不得不做一堆子字符串来了解是否选择了换行符。而且这些操作真的很慢。

我的 ParagraphTextStorage 的实现在这里:https://github.com/CineDev/ParagraphTextKit

关于objective-c - NSTextView 选择突出显示所有字符甚至段落缩进,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24407203/

相关文章:

objective-c - 如何将新的 UIViewController 从 React-Native 推送到 Objective-C Native 桥

Mac OS X Lion 上的 Python MySQL

c# - C# 开发人员应该为 Mac OS X 使用什么(非 Mono)语言/IDE?

ios - 应用升级后只运行一次代码

objective-c - ASIHTTPRequest SDK 5.1

ios - "' 未定义 ' is not an object"来自 React Native 中的 Native 模块

macos - 高写入 I/O "Pulsing",带有 dtrace 错误

swift - NSCollectionViewItem 的圆角

objective-c - 屏幕颜色反转在 OS X 中如何工作?

objective-c - 在坐标处绘制窗口或在 NSStatus 项之外绘制