#swift #cocoa #xcode8 #nsattributedstring #nstextfield
#swift #какао #xcode8 #nsattributedstring #nstextfield
Вопрос:
В an NSAttributedString
диапазон букв имеет атрибут ссылки и пользовательский атрибут цвета.
В Xcode 7 с Swift 2 это работает:
В Xcode 8 с Swift 3 пользовательский атрибутивный цвет для ссылки всегда игнорируется (на скриншоте он должен быть оранжевым).
Вот код для тестирования.
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, это было бы здорово.
Комментарии:
1. При использовании
NSTextView
необходимо установитьlinkTextAttributes
свойство. Когда aNSTextField
сфокусирован и может быть отредактирован, текст отображается в aNSTextView
, в котором используется егоlinkTextAttributes
(по умолчанию синий, подчеркнутый и ручной курсор).2. @Willeke Спасибо, к сожалению, я уже пробовал переопределять linkTextAttributes, это хорошее решение, но оно применяет одни и те же атрибуты ко всем ссылкам в приписываемой строке, и мне нужно иметь возможность устанавливать разные. Дело в том, что ваш ответ на мой другой вопрос работал до недавнего времени, большая загадка здесь в том, почему тот же самый код, на который вы ответили, больше не работает. 🙂
3. Ох. В Xcode 7 ссылка оранжевого цвета. В Xcode 8 он синий. Я регистрирую радар… Извините, что побеспокоил вас этим, @Willeke…
4.
"htpp"
???????5. И подчеркивание тоже новое. Раньше у него не было подчеркивания (как показывает ваш первый снимок экрана). По сути, они сделали ссылку на просмотр текста похожей на ссылку на ярлык. Вероятно, это сделано намеренно; раньше это было довольно запутанно.
Ответ №1:
Разработчик Apple ответил:
Пожалуйста, знайте, что наша команда инженеров определила, что эта проблема работает так, как предполагалось, на основе предоставленной информации.
И они объясняют, почему это работало раньше, но больше не работает:
К сожалению, предыдущее поведение (диапазоны приписываемых строк с отображением NSLinkAttributeName в пользовательском цвете) явно не поддерживалось. Это сработало, потому что NSTextField отображал ссылку только при наличии редактора полей; без редактора полей мы возвращаемся к цвету, указанному NSForegroundColorAttributeName .
В версии 10.12 обновлены NSLayoutManager и NSTextField для отображения ссылок с использованием внешнего вида ссылки по умолчанию, аналогичного iOS. (см. Примечания к выпуску AppKit для 10.12.)
Для обеспечения согласованности предполагается, что диапазоны, представляющие ссылки (указанные через NSLinkAttributeName ), будут отображаться с использованием внешнего вида ссылки по умолчанию. Таким образом, текущее поведение является ожидаемым поведением.
(выделение мое)
Комментарии:
1. Спасибо! Чрезвычайно полезная информация.
2. @Apple: Можем ли мы иметь эту таблицу оттенков, пожалуйста! Таким образом, мы можем сохранить согласованность и настройку одновременно!
Ответ №2:
Этот ответ не является решением проблемы NSLinkAttributeName
игнорирования пользовательских цветов, это альтернативное решение для включения цветных интерактивных слов NSAttributedString
.
С помощью этого обходного пути мы вообще не используем NSLinkAttributeName
, поскольку он навязывает стиль, который нам не нужен.
Вместо этого мы используем пользовательские атрибуты и подкласс NSTextField
/ NSTextView
, чтобы определять атрибуты по щелчку мыши и действовать соответствующим образом.
Очевидно, существует несколько ограничений: вы должны иметь возможность подклассировать поле / представление, переопределять mouseDown
и т.д., Но «это работает для меня» в ожидании исправления.
При подготовке вашего NSMutableAttributedString
, где вы бы установили an NSLinkAttributeName
, вместо этого установите ссылку в качестве атрибута с пользовательским ключом:
theAttributedString.addAttribute("CUSTOM", value: theLink, range: theLinkRange)
theAttributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.orange, range: theLinkRange)
theAttributedString.addAttribute(NSCursorAttributeName, value: NSCursor.arrow(), range: theLinkRange)
Цвет и содержимое для ссылки заданы. Теперь мы должны сделать его интерактивным.
Для этого создайте подкласс your 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
вместо an 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)
}
}
Комментарии:
1. Спасибо @Eric Aya. Я только что получил решение, которое я ищу. Еще раз спасибо!
2. Отличная идея! Мне нужно было добавить две вещи, чтобы заставить его работать с текстовыми полями в конце: (1) Установить
lineFragmentPadding
текстовый контейнер пользовательского текстового представления; (2) Добавьте стиль абзаца к приписываемой строке, для которойlineBreakMode
установлено значениеNSLineBreakStrategyPushOut
(для >= 11.0, используйтеNSLineBreakStrategyStandard
). Эти вещи исправляют несколько сбоев с многострочными метками.