在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中的修复或解决方法,那就太棒了。
答案 0 :(得分:8)
Apple Developer已回答:
请注意,我们的工程团队已根据所提供的信息确定此问题的行为符合预期。
他们解释了为什么之前有效但不再有了:
不幸的是,之前的行为(使用自定义颜色的NSLinkAttributeName呈现属性字符串范围)未明确支持。它碰巧工作,因为NSTextField只在场编辑器出现时呈现链接;如果没有字段编辑器,我们将回退到NSForegroundColorAttributeName指定的颜色。
版本10.12更新了NSLayoutManager和NSTextField以使用默认链接外观呈现链接,类似于iOS。 (see AppKit release notes for 10.12.)
为了提高一致性,预期的行为是表示使用默认链接外观绘制链接(通过NSLinkAttributeName指定)的范围。所以当前的行为是预期的行为。
(强调我的)
答案 1 :(得分: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)
设置链接的颜色和内容。现在我们必须让它可以点击。
为此,继承您的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
,则找到点击事件位置会有点棘手。解决方案是在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)
}
}