11

我正在尝试实现一个非常简单的文本编辑器,它应该可以NSTextView在 macOS 和UITextViewiOS 上使用。这个文本编辑器有一个工具栏按钮“ Section Break ”,每次点击它都会在当前光标位置插入一个新的部分。一个部分应该是:

  1. 在视觉上可识别为一个部分(通过在两个后续部分之间添加视觉分隔符并可能添加一些垂直空格),

  2. 可参考。用户应该能够在编辑器中看到所有部分的列表,并且通过单击该列表中的项目,文本视图应该立即滚动到该部分的开头。

另一个问题中,我问如何解决第一个问题,不幸的是,我还没有找到关于这部分的答案(即使有一个适用于 macOS 的解决方案)。

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

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

文本视图中几个部分的屏幕截图


我尝试过的以及为什么这似乎是一项艰巨的任务:

  1. 我的一个想法是子类NSTextStorage化并使用一组可变属性字符串作为其内部存储。然后我会使用一个特殊的子类,NSTextAttachment并将其用作我的文本存储中的分节指示符。问题在于文本视图仅在用户编辑文本时调用以下方法

    func replaceCharacters(in range: NSRange, 
                           with str: String)
    

    func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, 
                         range: NSRange)
    

    第一种方法只传递没有任何属性的纯字符串,第二种方法只获取属性。这意味着在第一种方法中,我无法判断替换字符是否实际上应该是分节符,因此我无法决定在我的内部文本存储数组中的哪个位置创建新元素。

    在第二种方法中,我不知道用户是否实际添加了新文本(在这种情况下,我必须在我的文本存储数组中为每个分节符属性添加一个新元素),或者用户是否只是更改了现有的某些属性文本(在这种情况下,之前已经创建了新的数组元素)。

  2. 我还考虑过在表格视图或堆栈视图中使用多个文本视图的想法。但是,这不起作用,因为它会阻止用户在多个后续部分中选择(和删除)文本。

  3. 最后,我尝试了 subclassing NSLayoutManager,但是关于它的文档真的很薄,对我来说似乎是错误的地方。(毕竟布局管理器的职责是文本的布局,而不是跟踪它的结构。)

有任何想法吗?

4

3 回答 3

2

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

首先确定一个部分什么。直觉上,它似乎是一块连续的文本,其中几个被串在一起形成一个完整的文档。但是尝试比这更深入:一个部分中的所有文本是否都具有相同的样式?相同的边距?您是否真的需要跟踪部分,或者只跟踪分节符也可以?

接下来,由于您已经决定通过使用and来购买TextKit,并且您的需求超出了这些类最常见的“其中包含一些可编辑文本的视图”用例,您需要了解更多关于如何基础类组合在一起。主要的类是、、,当然还有(或)。NSTextViewUITextViewNSTextStorageNSTextContainerNSLayoutManagerNSTextViewUITextView

了解 TextKit 类的工作原理以及它们为您提供的工具将帮助您弄清楚如何实现“部分”概念。以下是您可以采用的几个示例:

  • 构建您自己的文本视图。文本视图所做的大部分工作实际上是由其他类完成的:文本容器、布局管理器和文本存储。您可以构建自己的包含一系列文本容器的视图,这样每个部分都由不同的容器表示,每个容器都有自己的布局管理器和存储对象。这显然是最多的工作,但它可以对部分的显示方式进行大量控制。此外,如果您构建视图以在两个平台上工作,您将不必担心UITextView和之间的差异NSTextView

  • 让文本视图完成所有工作。部分的轻量级实现可能涉及为文本视图或文本存储对象设置委托。在编辑发生时,这两个调用委托方法,因此如果您的委托维护代表部分的索引或范围列表,它可以使用这些方法来更新其部分边界数据。您仍然需要弄清楚如何直观地显示部分边界,但这可能就像在每个部分边界处插入图像一样简单。

于 2019-03-29T13:31:02.200 回答
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
    })
}

这样,用户在做什么(写入、复制/粘贴等)都无关紧要,您将始终拥有一个包含分隔符的有效范围列表:)

这只是一个示例,您重新构建列表的逻辑可以更加完善,但我希望这可以帮助您入门。

于 2019-03-29T13:45:39.940 回答
-1

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

参考你的主要观点。

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

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

优点:

  • textView 中的视图或按钮是可追溯的,如果将文本粘贴到任何部分中,则可以更新其 y 轴。
  • 在选择按钮时,可以跟踪 textView 的内容偏移,并且可以将部分滚动到目标位置。
  • 两个按钮之间的范围将为您提供部分内相关文本的范围。同样,部分文本数组。
于 2019-03-24T18:18:58.880 回答