用自定义的NSTextBlock保存和粘贴带属性的字符串

9
我正在尝试创建一个自定义的NSTextBlock,就像苹果在WWDC 18上所做的那样(23分钟处)。

完整的演示项目在此处

好的,当我编辑并使用我的带有文本块附加的段落样式标记段落时,它可以很好地工作。

enter image description here

但是当我剪切和粘贴它(或从磁盘归档/取消归档),它就会失去它。编辑:它实际上将我的 TweetTextBlock 子类转换为一个 NSTableViewTextBlock,这也解释了边框。

enter image description here

实现

这是一个完整的Xcode项目。使用顶部菜单中的格式选项来触发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:fromCoderencode。它们没有被调用。不是在复制、粘贴、归档、解档时。所以我认为那不是问题所在。这让我想到,所有这些自定义绘图逻辑都不能保存在属性字符串中,而且这一切都发生在布局管理器中。这很有道理。但是,我该如何持久化这个块呢?

更新:我尝试读取属性。它具有一个段落样式,该段落样式在textBlocks数组属性中有一个项目。但是该文本块是一个NSTextBlock而不是我的子类(我尝试了if block is TweetTextBlock,它返回false)。

更新2:我尝试覆盖像classForArchiver这样的属性,然后使用例如print("twb: Class for archiver", block.classForArchiver)读取它们。有趣的是,文本块已经变成了NSTextTableBlock!我现在深入研究这个问题,正在寻找一种将className存储在文本块中的方法。到目前为止,我唯一能想到的是tooltip属性,但那对用户可见,我可能想用它来做其他事情。

更新3:tooltip也没有被保留。这很奇怪。我能想到的下一个大型黑客攻击是将文本颜色设置为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}

感谢您发布您的调试步骤。您最终找到解决办法了吗? - chourobin
1个回答

0

看起来NSAttributedString可能有问题。我尝试了子类化NSMutableParagraphStyle并使用它,但它没有被编码或解码(init)。

可能可以通过在文本运行中注释自定义Attribute.Key来指示块内容的分界线及其“类型”,然后在粘贴后对AttributedString进行后处理。

另外,开箱即用的Pasteboard类型可能不支持存档的NSAttributedString。相反,(我猜测)最高保真度的文本类型可能是RTF,这可能解释了TextBlock NSCoding方法根本没有被调用的事实。

查看NSPasteboard.PasteboardType,我投票选择第二个选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接