NSAttributedString中的NSLinkAttributeName忽略颜色属性。

17
NSAttributedString中,一段字母具有链接属性和自定义颜色属性。
在使用Swift 2的Xcode 7中,它可以工作:

enter image description here

在Xcode 8中使用Swift 3,链接的自定义属性颜色总是被忽略了(截图中应该是橙色)。

enter image description here

这是用于测试的代码。

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

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

我已向苹果发送了一个错误报告,但如果有人在Xcode 8中有修复或解决方法的想法,那将是很棒的。


当您使用 NSTextView 时,必须设置 linkTextAttributes 属性。当 NSTextField 获得焦点并且可以编辑时,文本将显示在 NSTextView 中,该视图使用其 linkTextAttributes(默认为蓝色、带下划线和手形光标)。 - Willeke
@Willeke 谢谢,不幸的是我已经尝试过覆盖linkTextAttributes了,这是一个好的解决方案,但它将相同的属性应用于属性字符串中的所有链接,而我需要能够设置不同的属性。问题是,你对我的另一个问题的回答之前一直有效,这里的大谜团是为什么你回答的完全相同的代码现在不再起作用了。 :) - Eric Aya
哦。在Xcode 7中,链接是橙色的。在Xcode 8中,它是蓝色的。我正在提交一个雷达...抱歉打扰你了,@Willeke... - Eric Aya
哦,我明白了,因为你在桌面上。但是在iOS 10中,UITextView也存在颜色问题。你在这里做出了重要的发现。请在你的错误报告中提到,在iOS 10中,UITextView也存在同样的问题。 - matt
@EricAya 你需要一个游乐场的解决方案还是一个应用程序的解决方案? - Willeke
显示剩余8条评论
2个回答

13

苹果开发者回答:

请知道,我们的工程团队根据提供的信息确定这个问题符合预期的行为。

他们解释了之前它为什么可以工作而现在不能:

不幸的是,之前的行为(带有NSLinkAttributeName的属性字符串范围以自定义颜色呈现)并没有得到明确支持。它之所以能够工作,是因为NSTextField只有在字段编辑器存在时才会呈现链接;没有字段编辑器,我们会退回到由NSForegroundColorAttributeName指定的颜色。

版本10.12更新了NSLayoutManager和NSTextField,使用默认的链接外观呈现链接,类似于iOS。 (参见10.12的AppKit发布说明。)

为了促进一致性,表示链接的范围(通过NSLinkAttributeName指定)的预期行为是使用默认的链接外观来绘制。因此,当前的行为是符合预期的行为。

(强调是我的)


1
谢谢!非常有帮助的信息。 - Oleg
@Apple:可以让我们实现这个可调色的功能吗?这样我们就可以同时保持一致性和自定义性! - osxdirk

11

这个答案并不能解决 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)

链接的颜色和内容已设置。现在我们需要使其可点击。

为此,请对您的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)
    }

}

就这样。

在我的情况下,我需要使用不同的颜色和链接类型自定义不同的单词,所以我传递一个结构体来包含链接和附加元信息,而不是仅传递链接字符串,但思路是相同的。

如果您必须使用NSTextField而不是NSTextView,那么查找点击事件位置会有些棘手。解决方法是在NSTextField中创建一个NSTextView,然后像以前一样使用相同的技术。

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)
    }

}

谢谢 @Eric Aya。我刚得到了我一直在寻找的解决方案。再次感谢! - Bharat Nakum
1
太好了!最后我需要添加两个东西才能使它与文本字段一起工作:(1)设置自定义文本视图的文本容器的lineFragmentPadding;(2)向属性字符串添加一个段落样式,其中lineBreakMode设置为NSLineBreakStrategyPushOut(对于>= 11.0,请使用NSLineBreakStrategyStandard)。这些东西可以修复多行标签的一些小问题。 - goetz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接