将文本视图的属性字符串细分为(可参考的)部分

问题描述 投票:9回答:3

我正在尝试实现一个非常简单的文本编辑器,该编辑器既适用于macOS上的NSTextView,也适用于iOS上的UITextView。此文本编辑器有一个工具栏按钮“Section Break”,每次单击时都会在当前光标位置插入一个新部分。一节应该是:

  1. 可视化识别为一个部分(通过在两个后续部分之间添加可视分隔符并可能添加一些垂直空白),
  2. 可借鉴。用户应该能够在编辑器中看到所有部分的列表,并通过单击该列表中的项目,文本视图应立即滚动到该部分的开头。

another question,我问如何解决第一个问题,不幸的是,我还没有找到答案(即使有一个solution that works on macOS only)。

但是,这个问题集中在第二个方面: 如何在文本视图中维护所有部分的列表,其中每个部分都能准确引用相关文本?

此任务的复杂性在于用户可以随时复制和粘贴或简单地编辑文本的任何部分。因此,我不能保留一个简单的段落数字或类似的东西。

Screenshot of several sections in a text view


What I've tried and why this appears to be such a difficult task:

  1. 我有一个想法是继承NSTextStorage并使用一组可变属性字符串作为其内部存储。然后我会使用NSTextAttachment的一个特殊子类,并将其用作文本存储中的分节符指示符。问题是the text view only calls the following methods whenever the user edits textfunc replaceCharacters(in range: NSRange, with str: String) func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) 第一种方法只传递没有任何属性的普通字符串,第二种方法只获取属性。这意味着在第一种方法中,我无法判断替换字符是否实际上应该是分节符,因此我无法决定在内部文本存储数组中何处创建新元素。 在第二种方法中,我不知道用户是否实际添加了新文本(在这种情况下,我必须在我的文本存储数组中为每个分节符属性添加一个新元素),或者如果用户只是更改了现有的一些属性text(在这种情况下,之前已经创建了新的数组元素)。
  2. 我还考虑过在表视图或堆栈视图中使用多个文本视图的想法。但是,这不起作用,因为它会阻止用户跨多个后续部分选择(和删除)文本。
  3. 最后,我尝试了对NSLayoutManager进行子类化,但是关于它的文档很薄,对我来说似乎是错误的地方。 (毕竟,布局管理器的职责是文本的布局,而不是跟踪其结构。)

Any ideas?

ios uitextview nstextview nslayoutmanager nstextstorage
3个回答
2
投票

如何在文本视图中维护所有部分的列表,其中每个部分都能准确引用相关文本?

首先确定一个部分是什么。直观地说,它似乎是一块连续的文本,其中几个串在一起形成一个完整的文档。但是试着比这更深入:一节中的所有文字都有相同的风格吗?相同的利润率?您是否真的需要跟踪部分,或者它是否也可以跟踪分节符?

接下来,既然你已经决定通过使用TextKitNSTextView购买UITextView,并且由于你的需求超出了最常见的“带有一些可编辑文本的视图”这些类的用例,你需要了解更多关于底层类如何组合在一起。主要类别是NSTextStorageNSTextContainerNSLayoutManager,当然还有NSTextView(或UITextView)。

了解TextKit类的工作原理以及它们为您提供的功能将帮助您了解如何实现“部分”概念。以下是您可以采取的几种方式示例:

  • 构建自己的文本视图。文本视图所做的大部分工作实际上是由其他类完成的:文本容器,布局管理器和文本存储。您可以构建包含一系列文本容器的自己的视图,以便每个部分由不同的容器表示,每个容器都有自己的布局管理器和存储对象。这显然是最重要的工作,但它可以提供对部分显示方式的大量控制。此外,如果您构建视图以在两个平台上工作,则不必担心UITextViewNSTextView之间的差异。
  • 让文本视图完成所有工作。部分的轻量级实现可能涉及为文本视图或文本存储对象设置委托。这两个都在编辑时调用委托方法,因此如果您的委托维护一个表示节的索引或范围列表,它可以使用这些方法更新其截面边界数据。你仍然需要弄清楚如何在视觉上显示剖面边界,但这可能就像每个剖面边界的inserting an image一样简单。

2
投票

假设您正在使用NSTextAttachment子类,那么完成这两项任务应该相当容易,但让我们关注第二项:

你的第一个子类化NSTextStorage解决方案是好的,但它将存储暴露给许多不需要的逻辑,我的建议是让你的(NS|UI)TextViewDelegate实现func textDidChange(_ notification: Notification),当文本更改时你可以使用文本存储来枚举你的附件并保存稍后要引用的范围列表:

func textDidChange(_ notification: Notification)
{
    self.textView.textStorage?.enumerateAttribute(NSAttributedString.Key.attachment, in: NSMakeRange(0, self.textView.textStorage!.length), options: [], using: {
        (value:Any?, range:NSRange, stop:UnsafeMutablePointer<ObjCBool>) in
        // If the value is one of your attachment save the range in a list
    })
}

这样,无论用户在做什么(写,复制/粘贴等等),你都会有一个包含你的分隔符的有效范围列表:)

这只是一个例子,你重新构建列表的逻辑可以更精确,但我希望这可以让你开始。


-1
投票

我会玩NSRange。它可能与您的方法无关,但可以用作建议。

参考你的要点。

视觉上可识别为一个部分(通过在两个后续部分之间添加可视分隔符并可能添加一些垂直空白)可参考。用户应该能够在编辑器中看到所有部分的列表,并通过单击该列表中的项目,文本视图应立即滚动到该部分的开头。

UIButtons可以作为textView中的部分添加。按钮的宽度应该是textView中线条的宽度。

优点:

  • textView中的视图或按钮是可追踪的,如果文本粘贴在任何部分内,则可以更新其y轴。
  • 在选择按钮时,可以跟踪textView的内容偏移量,并且可以将部分滚动到目标位置。
  • 两个按钮之间的范围将为您提供部分内部相关文本的范围。同样,部分数组也是文本。
© www.soinside.com 2019 - 2024. All rights reserved.