Атрибут цвета игнорируется в NSAttributedString с NSLinkAttributeName

#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 свойство. Когда a NSTextField сфокусирован и может быть отредактирован, текст отображается в a NSTextView , в котором используется его 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 ). Эти вещи исправляют несколько сбоев с многострочными метками.