带有 NSLinkAttributeName 的 NSAttributedString 中的颜色属性被忽略

Posted

技术标签:

【中文标题】带有 NSLinkAttributeName 的 NSAttributedString 中的颜色属性被忽略【英文标题】:Color attribute is ignored in NSAttributedString with NSLinkAttributeName 【发布时间】:2016-10-07 23:10:18 【问题描述】:

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

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

我已向 Apple 发送了一份错误报告,但与此同时,如果有人对 Xcode 8 中的修复或解决方法有想法,那就太好了。

【问题讨论】:

使用NSTextView时,必须设置linkTextAttributes属性。当 NSTextField 获得焦点并可以编辑时,文本将显示在 NSTextView 中,该 linkTextAttributes 使用其 linkTextAttributes(默认为蓝色,带下划线和手形光标)。 @Willeke 谢谢,不幸的是我已经尝试过覆盖linkTextAttributes,这是一个很好的解决方案,但是它将相同的属性应用于属性字符串中的所有链接,我需要能够设置不同的属性。问题是,你对我另一个问题的回答直到最近才起作用,这里最大的谜团是为什么你回答的完全相同的代码不再起作用了。 :) 哦。该链接在 Xcode 7 中为橙色。在 Xcode 8 中为蓝色。我正在归档雷达...抱歉打扰您了,@Willeke... "htpp"??????? 而且下划线也是新的。它曾经没有下划线(如您的第一个屏幕截图所示)。基本上他们使文本视图链接看起来像一个标签链接。这可能是故意的;以前很混乱。 【参考方案1】:

Apple 开发者已回答:

请注意,我们的工程团队已根据提供的信息确定此问题按预期运行

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

很遗憾,以前的行为(属性字符串范围,NSLinkAttributeName 以自定义颜色呈现)没有明确支持。它恰好起作用,因为 NSTextField 仅在存在字段编辑器时才呈现链接;如果没有字段编辑器,我们会退回到 NSForegroundColorAttributeName 指定的颜色。

10.12 版更新了 NSLayoutManager 和 NSTextField 以使用默认链接外观呈现链接,类似于 ios。 (see AppKit release notes for 10.12.)

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

(强调我的)

【讨论】:

谢谢!非常有用的信息。 @Apple:请给我们这个着色器好吗!所以我们可以同时保持一致性和定制化!【参考方案2】:

此答案不是解决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。我刚刚得到了我正在寻找的解决方案。再次感谢! 好主意!我需要添加两件事以使其最终与文本字段一起使用:(1)设置自定义文本视图的文本容器的lineFragmentPadding; (2) 将段落样式添加到将lineBreakMode 设置为NSLineBreakStrategyPushOut 的属性字符串(对于>= 11.0,使用NSLineBreakStrategyStandard)。这些东西修复了多行标签的一些故障。

以上是关于带有 NSLinkAttributeName 的 NSAttributedString 中的颜色属性被忽略的主要内容,如果未能解决你的问题,请参考以下文章

UILabel 和 NSLinkAttributeName:链接不可点击

带有多个链接的 NSAttributedString 的 UILabel,带有行限制,显示尾部截断,带有未见文本的 NSBackgroundColorAttributeName

使用带有 uuencode 的“sendmail”发送邮件,并带有主题

带有和不带有聚合的 sql 查询

带有滚动的 Div 和带有绝对位置的内容

带有 RecyclerView 的 DialogFragment 比带有 Recyclerview 的 Fragment 慢