【问题标题】:NSTextView cursor doesn't appear when typing text on macOS 10.14在 macOS 10.14 上键入文本时不出现 NSTextView 光标
【发布时间】:2019-03-13 05:00:37
【问题描述】:

我在 macOS 10.12 Mojave 上使用 NSTextView 观察到一个奇怪的问题。

我正在像这样更改didChangeText() 中的 textStorage 属性:

self.textStorage?.beginEditing()

ARTokenManager.getToken(text: text, language: language) { (tokens) in
    // This line reset the attributes
    // If I remove it, the cursor appear properly
    // But the attributes are conserved 
    self.textStorage?.setAttributes([NSAttributedString.Key.font: self.font!,
                                     NSAttributedString.Key.foregroundColor: self.defaultTextColor], range: range)
    for token in tokens {
        let attributeRange = NSRange(location: token.range.location + range.location, length: token.range.length)
        self.textStorage?.addAttributes(token.attributes, range: attributeRange)
    }
}

self.textStorage?.endEditing()

当我删除 setAttributes 方法时,一切都按预期工作,但我无法解释原因。我可能错误地重置了属性。此问题仅适用于 Mojave。

有人有同样的问题或可以解释我做错了什么吗?

谢谢。

【问题讨论】:

    标签: swift macos appkit nstextview


    【解决方案1】:

    经过一番研究,我发现我的问题更多是关于 NSTextView 的语法高亮。我知道这是很多 macOS 开发人员都在问的问题,并且有很多解决方案。这可能不是最好的,但这就是我解决这个问题的方法。

    NSTextStorage

    为此,我使用了 NSTextStorage 的子类。这是所有语法工作将完成的地方。 NSTextStorage 不是面向协议的,因此您必须按照 Apple 文档的建议自行覆盖方法:

    class SyntaxTextStorage: NSTextStorage {
    
        private var storage: NSTextStorage
    
        override var string: String {
            return storage.string
        }
    
        override init() {
            self.storage = NSTextStorage(string: "")
            super.init()
        }
    
        override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
            return storage.attributes(at: location, effectiveRange: range)
        }
    
        override func replaceCharacters(in range: NSRange, with str: String) {
            beginEditing()
            storage.replaceCharacters(in: range, with: str)
            edited(.editedCharacters, range: range, changeInLength: str.count - range.length)
            endEditing()
        }
    
        override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
            beginEditing()
            storage.setAttributes(attrs, range: range)
            edited(.editedAttributes, range: range, changeInLength: 0)
            endEditing()
        }
    
    }
    

    这是创建文本存储的基础。

    NSTextStorage + NSTextView

    下一步是将文本存储设置到 textView 中。为此,您可以使用 textView layoutManager 中可访问的 replaceTextStorage() 方法。

    class SyntaxTextView: NSTextView {
    
        var storage: SyntaxTextStorage!
    
        override func awakeFromNib() {
            super.awakeFromNib()
            configureTextStorage()
        }
    
        private func configureTextStorage() {
            storage = SyntaxTextStorage()
            layoutManager?.replaceTextStorage(storage)
        }
    
    }
    

    语法

    最后一步是完成语法工作。这个过程的CPU成本非常高。有很多方法可以做到最好的表现。我建议你实现一个类,它将返回NSAttributedStringNSRange 的列表。文本存储的工作应该只是将样式应用于您的文本。 就个人而言,我使用processEditing 方法来执行我的文本分析:

    override func processEditing() {
        super.processEditing()
        syntaxCurrentParagraph()
    }
    

    我建议您在后台进行语法分析,然后,如果自上次分析以来没有文本更改,请将更改应用于您的文本。始终在我的文本存储中,我实现了一个 syntax 将样式应用于文本的方法:

    private func syntax(range: NSRange, completion: ((_ succeed: Bool) -> Void)? = nil) {
        guard range.length > 0 else {
            completion?(true)
            return
        }
    
        // Save your data to do the job in background
        let currentString = self.string
        let substring = currentString.substring(range: range)
        let mutableAttributedString = NSMutableAttributedString(string: substring, attributes: NSAttributedString.defaultAttributes as [NSAttributedString.Key : Any])
    
        DispatchQueue.global(qos: .background).async {
            ARSyntaxManager.tokens(text: substring, language: self.language) { (tokens) in
                // Fill you attributed string
                for token in tokens {
                    mutableAttributedString.addAttributes(token.attributes, range: token.range)
                }
    
                DispatchQueue.main.async {
                    // Check if there is no change
                    guard self.string.count == currentString.count else {
                        completion?(false)
                        return
                    }
    
                    completion?(true)
    
                    // Apply your change
                    self.storage.replaceCharacters(in: range, with: mutableAttributedString)
                    self.displayVisibleRect()
                }
            }
        }
    }
    

    就是这样。希望对大家有所帮助。

    【讨论】:

      【解决方案2】:

      我找到了解决方案。我必须在NSTextStorageDelegate 中使用didProcessEditing 方法而不是didChangeText

      【讨论】:

      • 您是否使用 SavannaKit 执行语法高亮?我面临同样的问题,你和我有兴趣了解更多关于你的解决方案(使用 didProcessEditing 没有区别)
      • @AP。检查我的最后一个答案。我已经解释了更多我的过程。希望对您有所帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-11
      • 1970-01-01
      • 2019-03-02
      • 2015-10-16
      相关资源
      最近更新 更多