Сохранение и вставка атрибутивной строки с пользовательским NSTextBlock

Я пытаюсь создать пользовательский NSTextBlock, очень похожий на тот, который Apple сделала на WWDC 18 (23 минут через).

Полный демонстрационный проект здесь.

Итак, это прекрасно работает, когда я редактирую и помечаю абзац своим стилем абзаца, к которому прикреплен текстовый блок.

введите описание изображения здесь

Но когда я вырезаю и вставляю его (или архивирую/разархивирую с диска), он его теряет. РЕДАКТИРОВАТЬ: Это фактически превращает мой подкласс TweetTextBlock в NSTableViewTextBlock, что также объясняет границы.

введите описание изображения здесь

Реализация

Вот полный проект Xcode. Используйте пункт верхнего меню Format, чтобы активировать функцию markTweet.

Вот как я добавляю атрибуты в абзац

    @IBAction func markTweet(_ sender : Any?){
    print("now we are marking")
    let location = textView.selectedRange().location


    guard let nsRange = textView.string.extractRange(by: .byParagraphs, at: location) else { print("Not in a paragraph"); return }

    let substring = (textView.string as NSString).substring(with: nsRange)

    let tweetParagraph = NSMutableParagraphStyle()
    tweetParagraph.textBlocks = [TweetTextBlock()]

    let twitterAttributes : [AttKey : Any] = [
        AttKey.paragraphStyle : tweetParagraph,
        AttKey.font : NSFont(name: "HelveticaNeue", size: 15)
    ]

    textView.textStorage?.addAttributes(twitterAttributes, range: nsRange)
}

А это мой подкласс NSTextBlock

import Cocoa

class TweetTextBlock: NSTextBlock {

    override init() {
        super.init()
        setWidth(33.0, type: .absoluteValueType, for: .padding)
        setWidth(70.0, type: .absoluteValueType, for: .padding, edge: .minX)

        setValue(100, type: .absoluteValueType, for: .minimumHeight)

        setValue(300, type: .absoluteValueType, for: .width)
        setValue(590, type: .absoluteValueType, for: .maximumWidth)


        backgroundColor = NSColor(white: 0.97, alpha: 1.0)

    }


    override func drawBackground(withFrame frameRect: NSRect, in controlView: NSView,
        characterRange charRange: NSRange, layoutManager: NSLayoutManager) {

        let frame = frameRect
        let fo = frameRect.origin

        super.drawBackground(withFrame: frame, in: controlView, characterRange:
        charRange, layoutManager: layoutManager)

        // draw string
        let context = NSGraphicsContext.current
        context?.shouldAntialias = true

        let drawPoint: NSPoint = CGPoint(x: fo.x + 70, y: fo.y + 10)


        let nameAttributes = [AttKey.font: NSFont(name: "HelveticaNeue-Bold", size: 15),  .foregroundColor: NSColor.black]
        var handleAttributes = [AttKey.font: NSFont(name: "HelveticaNeue", size: 15),  .foregroundColor: NSColor(red: 0.3936756253, green: 0.4656872749, blue: 0.5323709249, alpha: 1)]

        let nameAStr = NSMutableAttributedString(string: "Johanna Appleseed", attributes: nameAttributes)
        let handleAStr = NSAttributedString(string: "  @johappleseed ·  3h", attributes: handleAttributes)
        nameAStr.append(handleAStr)
        nameAStr.draw(at: drawPoint)

        let im = NSImage(named: "profile-twitter")!
        im.draw(in: NSRect(x: fo.x + 10, y: fo.y + 10, width: 50, height: 50))

        }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

}

Что я пробовал

Я думаю, что это может произойти, потому что TextKit не знает, как архивировать атрибуты из пользовательского блока. Но я попытался переопределить init:fromCoder и encode. Им не звонят. Не копировать, вставлять, архивировать, разархивировать. Так что я полагаю, что это было не так. Это наводит меня на мысль, что вся эта пользовательская логика рисования не может быть сохранена в строке с атрибутами и что все это происходит в менеджере компоновки. Это имеет смысл. Но как тогда сохранить блок?

ОБНОВЛЕНИЕ: я попытался прочитать атрибуты. У него есть стиль абзаца, и этот стиль абзаца имеет элемент в свойстве массива textBlocks. Но этот текстовый блок является NSTextBlock, а не моим подклассом (я пробовал if block is TweetTextBlock, который возвращает false)

ОБНОВЛЕНИЕ 2: я попытался переопределить такие свойства, как classForArchiver, а затем прочитать их, например. print("twb: Class for archiver", block.classForArchiver). Здесь интересно то, что текстовый блок превратился в NSTextTableBlock! Я так увлекся этим, что ищу способ сохранить className где-нибудь в текстовом блоке. Пока единственное, о чем я могу думать, это свойство tooltip, но оно видно пользователю, и я мог бы использовать его для чего-то другого.

ОБНОВЛЕНИЕ 3: всплывающая подсказка также не сохраняется. Это странно. Следующий большой хак, который я могу придумать, — установить цвет текста на HSB (n, 0, 0), где n — идентификатор подкласса NSTextBlock. Будем надеяться, что мне не придется туда идти.

ОБНОВЛЕНИЕ 4. Скорее всего, это вызвано тем, что архивирование и копирование/вставка преобразуют строку в RTF. Вот public.rtf из моего буфера обмена

{\rtf1\ansi\ansicpg1252\cocoartf2509
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red245\green245\blue245;}
{\*\expandedcolortbl;;\csgray\c97000;}
\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0

\f0\fs30 \cf0 THIS text is in a TweetTextBlock}

person Morten J    schedule 30.10.2019    source источник
comment
спасибо за публикацию ваших шагов по отладке. вы в итоге нашли решение?   -  person chourobin    schedule 24.11.2019


Ответы (1)


Похоже, что NSAttributedString как-то виноват. Я попытался создать подкласс NSMutableParagraphStyle и использовать его, но он НЕ кодируется и не декодируется (инициализация).

Можно просто аннотировать текстовый запуск с помощью пользовательского Attribute.Key, указывающего очерчивание содержимого блока и его «тип», а затем выполнить постобработку AttributedString после вставки.

Кроме того, стандартные типы Pasteboard могут не поддерживать и заархивировать NSAttributedString. Скорее (и я предполагаю), что текстовый тип с наивысшей точностью может быть RTF, что может объяснить тот факт, что методы TextBlock NSCoding вообще не вызываются.

Глядя на NSPasteboard.PasteboardType, я голосую за вариант 2.

person Jason Jobe    schedule 01.02.2020