使用NSLinkAttributeName在NSAttributedString中忽略Color属性

aya*_*aio 12 cocoa nstextfield nsattributedstring swift xcode8

在一个NSAttributedString字母中,一系列字母具有链接属性和自定义颜色属性.

在带有Swift 2的Xcode 7中,它可以工作:

在此输入图像描述

在带有Swift 3的Xcode 8中,链接的自定义属性颜色始终被忽略(截图中应为橙色).

在此输入图像描述

这是测试代码.

Swift 2,Xcode 7:

import Cocoa
import XCPlayground

let text = "Hey @user!"

let attr = NSMutableAttributedString(string: text)
let range = NSRange(location: 4, length: 5)
attr.addAttribute(NSForegroundColorAttributeName, value: NSColor.orangeColor(), range: range)
attr.addAttribute(NSLinkAttributeName, value: "http://somesite.com/", range: range)

let tf = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 50))
tf.allowsEditingTextAttributes = true
tf.selectable = true
tf.stringValue = text
tf.attributedStringValue = attr

XCPlaygroundPage.currentPage.liveView = tf
Run Code Online (Sandbox Code Playgroud)

Swift 3,Xcode 8:

import Cocoa
import PlaygroundSupport

let text = "Hey @user!"

let attr = NSMutableAttributedString(string: text)
let range = NSRange(location: 4, length: 5)
attr.addAttribute(NSForegroundColorAttributeName, value: NSColor.orange, range: range)
attr.addAttribute(NSLinkAttributeName, value: "http://somesite.com/", range: range)

let tf = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 50))
tf.allowsEditingTextAttributes = true
tf.isSelectable = true
tf.stringValue = text
tf.attributedStringValue = attr

PlaygroundPage.current.liveView = tf
Run Code Online (Sandbox Code Playgroud)

我已经向Apple发送了一个错误报告,但与此同时,如果有人想知道Xcode 8中的修复或解决方法,那就太棒了.

aya*_*aio 9

Apple Developer回答:

请注意,我们的工程团队已根据所提供的信息确定此问题的行为符合预期.

他们解释了为什么它之前有效但不再有效:

遗憾的是,未明确支持先前的行为(使用自定义颜色的NSLinkAttributeName呈现来归因字符串范围).它碰巧工作,因为NSTextField只在场编辑器出现时呈现链接; 如果没有字段编辑器,我们将回退到NSForegroundColorAttributeName指定的颜色.

版本10.12更新了NSLayoutManager和NSTextField以使用默认链接外观呈现链接,类似于iOS.(有关10.12的AppKit发行说明,请参阅.)

为了提高一致性,预期的行为是表示使用默认链接外观绘制链接(通过NSLinkAttributeName指定)的范围.所以当前的行为是预期的行为.

(强调我的)


aya*_*aio 5

这个答案不能解决NSLinkAttributeName忽略自定义颜色的问题,它是一种替代解决方案,可以使用彩色可点击单词NSAttributedString.


通过这种解决方法我们根本不使用NSLinkAttributeName,因为它强制我们不想要的样式.

相反,我们使用自定义属性,并且我们将NSTextField/ 子类化为NSTextView检测鼠标单击下的属性并相应地执行操作.

显然有几个约束:你必须能够子类化字段/视图,覆盖mouseDown等,但是"它适用于我"在等待修复时.

在准备您的NSMutableAttributedString设置位置时NSLinkAttributeName,请使用自定义键将链接设置为属性:

theAttributedString.addAttribute("CUSTOM", value: theLink, range: theLinkRange)
theAttributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.orange, range: theLinkRange)
theAttributedString.addAttribute(NSCursorAttributeName, value: NSCursor.arrow(), range: theLinkRange)
Run Code Online (Sandbox Code Playgroud)

设置链接的颜色和内容.现在我们必须让它可以点击.

为此,将您的子类化NSTextView并覆盖mouseDown(with event: NSEvent).

我们将在窗口中获取鼠标事件的位置,在该位置的文本视图中查找字符索引,并在文本视图的属性字符串中询问此索引处的字符属性.

class MyTextView: NSTextView {

    override func mouseDown(with event: NSEvent) {
        // the location of the click event in the window
        let point = self.convert(event.locationInWindow, from: nil)
        // the index of the character in the view at this location
        let charIndex = self.characterIndexForInsertion(at: point)
        // if we are not outside the string...
        if charIndex < super.attributedString().length {
            // ask for the attributes of the character at this location
            let attributes = super.attributedString().attributes(at: charIndex, effectiveRange: nil)
            // if the attributes contain our key, we have our link
            if let link = attributes["CUSTOM"] as? String {
                // open the link, or send it via delegate/notification
            }
        }
        // cascade the event to super (optional)
        super.mouseDown(with: event)
    }

}
Run Code Online (Sandbox Code Playgroud)

而已.

在我的情况下,我需要自定义具有不同颜色和链接类型的不同单词,因此我不是仅仅将链接作为字符串传递,而是传递包含链接和其他元信息的结构,但这个想法是相同的.

如果你必须使用NSTextField而不是a NSTextView,那么找到click事件位置会有点棘手.一个解决方案是创建一个NSTextView内部NSTextField和从那里使用与以前相同的技术.

class MyTextField: NSTextField {

    var referenceView: NSTextView {
        let theRect = self.cell!.titleRect(forBounds: self.bounds)
        let tv = NSTextView(frame: theRect)
        tv.textStorage!.setAttributedString(self.attributedStringValue)
        return tv
    }

    override func mouseDown(with event: NSEvent) {
        let point = self.convert(event.locationInWindow, from: nil)
        let charIndex = referenceView.textContainer!.textView!.characterIndexForInsertion(at: point)
        if charIndex < self.attributedStringValue.length {
            let attributes = self.attributedStringValue.attributes(at: charIndex, effectiveRange: nil)
            if let link = attributes["CUSTOM"] as? String {
                // ...
            }
        }
        super.mouseDown(with: event)
    }

}
Run Code Online (Sandbox Code Playgroud)