【问题标题】:NSTextView selection highlights all characters even paragraph indentsNSTextView 选择突出显示所有字符,甚至段落缩进
【发布时间】:2014-08-15 22:19:03
【问题描述】:

找不到任何线索如何管理这个。

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

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

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

【问题讨论】:

    标签: objective-c macos cocoa swift nstextview


    【解决方案1】:

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

    因此,如果您使用段落的头部缩进,那么前导文本空白区域将突出显示。如果您选择 EOL 字符,则文本容器的尾部将突出显示。

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

    但后来我找到了很多正确的方法。只需覆盖 NSLayoutManager 的func fillBackgroundRectArray(_ rectArray: UnsafePointer<NSRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: NSColor),计算您的矩形并使用这些矩形调用 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 实现。其目的是在编辑文本存储时实时计算段落范围。知道正确的段落范围,我可以在计算选定的矩形时使用整数。否则我不得不做一堆子字符串来了解是否选择了换行符。而且操作真的很慢。

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

    【讨论】:

    • 嗨 Vitaliy,您能否分享一下您从选择矩形中排除不可见字符的计算方法?
    【解决方案2】:

    我们只能推测闭源页面使用什么,但我怀疑它使用的是NSTextView——作为文字处理器,它必须使用更高级的自定义解决方案。

    Cocoa Text Architecture Guide开始,你主要对NSLayoutManager类感兴趣(伴随着NSTextContainerNSTextStorage)。

    NSTextView 可能通过临时属性 (-[NSLayoutManager addTemporaryAttribute:value:forCharacterRange:]) 实现其选择。如果您继承 NSTextView 并拦截每个选择更改事件,您应该能够检测并删除负责从换行符显示选择的临时属性,而不会干扰文本视图的逻辑选择范围。

    如果由于某种原因上述建议不起作用,总是可以从头开始重新实现NSTextView,使用NSLayoutManager 来处理所有布局和绘图。 NSLayoutManager 处理所有 unicode/bidi 怪癖,给出字形运行和单个字形的精确像素坐标,以及绘制它们的方法。临时属性可能不足以实现不同的选择高度;在这种情况下,您应该能够自己绘制选择(在文本字形下的背景上)。不过,对于这么小的 UI 细节,这肯定会大量工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-11
      • 1970-01-01
      • 1970-01-01
      • 2015-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多