【问题标题】:Subdivide a text view's attributed string into (referable) sections将文本视图的属性字符串细分为(可参考的)部分
【发布时间】:2019-08-11 07:08:24
【问题描述】:

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

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

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

another question 中,我询问了如何解决第一个问题,不幸的是,我还没有找到这部分的答案(尽管 solution that works on macOS only)。

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

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


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

  1. 我的一个想法是继承NSTextStorage 并使用可变属性字符串数组作为其内部存储。然后,我将使用NSTextAttachment 的一个特殊子类,并将其用作我的文本存储中的分节指示符。问题在于the text view only calls the following methods whenever the user edits text:

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

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

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

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

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

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

有什么想法吗?

【问题讨论】:

    标签: ios uitextview nstextview nslayoutmanager nstextstorage


    【解决方案1】:

    假设您使用的是 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
        })
    }
    

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

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

    【讨论】:

      【解决方案2】:

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

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

      接下来,既然您已经决定使用NSTextViewUITextView 来购买TextKit,并且您的需求超出了最常见的“包含一些可编辑文本的视图”用例之外对于这些类,您需要了解更多关于底层类如何组合在一起的信息。主要类是NSTextStorageNSTextContainerNSLayoutManager,当然还有NSTextView(或UITextView)。

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

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

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

      【讨论】:

      • 感谢您的建议。我已经阅读了 TextKitText Programming GuideCocoa Text Architecture Guide 的文档、各种教程并观看了 WWDC 视频。不幸的是,提供的信息在细节上很薄弱。有一些关于不同组件如何交互的漂亮抽象图,但没有具体说明。
      • 例如,我很难弄清楚如何实现自己的文本视图,因为我能找到的每个示例都使用默认的NS/UITextViewNSTextStorage 类甚至具有该特定类型的属性 textView。这是我知道如何将所有 TextKit 类连接在一起的唯一方法。
      • Apple 描述了how to manually create a text storage and a layout manager,他们在creating a custom text view 上也有一些注释,但这不足以让我了解这些组件之间究竟是如何相互作用的。
      • 您是否知道任何自定义文本视图的示例实现?
      【解决方案3】:

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

      参考你的主要观点。

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

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

      优点:

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

      【讨论】:

      • 如果您有没有回答问题的建议,最好将其添加为评论而不是答案。我也不清楚你所说的 “我会和 NSRange 一起玩” 到底是什么意思。
      • 我也无法通过按钮方法遵循您的思路。您能否详细说明您将如何实现它(最好是通过代码示例)以及如何解决拥有可点击部分列表的问题?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多