16

问题

我需要了解 TextKit 的工作原理以及如何使用它来构建文本编辑器。我需要弄清楚如何仅绘制最终用户与之交互的可见文本,或者确定如何在 processEditing 方法中将属性仅应用于可见文本而不将属性应用于整个更改的文本范围。

背景

iOS 7 推出了 TextKit。我有一个完全实现 TextKit 的标记器和代码(请参阅 Apple 的 TextKitDemo 项目——下面提供了一个链接)......并且它可以工作。但是,它真的很慢。当文本被 NSTextStorage 解析时,它要求您在 processEditing 方法中在同一线程上对已编辑文本的整个范围进行着色。将工作卸载到线程并没有帮助。简直太慢了。我已经到了只能重新归因修改后的范围的地步,但如果范围太大,过程会很慢。在某些情况下,整个文档在进行更改后可能会失效。

以下是我的一些想法。请让我知道这些中的任何一个是否会起作用,或者可能会推动我朝着正确的方向前进。

1) 多个 NSTextContainers

阅读文档,我似乎可以在 NSLayoutManager 中添加多个 NSTextContainers。我假设通过这样做,我不仅应该能够定义可以在 NSTextContainer 中绘制的行数,而且我还应该能够知道最终用户可以看到哪个 NSTextContainer。我知道如果我走这条路,我将需要投入大量时间来看看它是否可行。初步测试表明您只需要一个 NSTextContainer。所以我必须继承 NSLayout 或创建一个包装器,其中布局管理器确定哪些文本进入哪个文本容器。呸。另外,我不知道 TextKit 是如何让我知道是时候绘制一个特定的 NSTextContainer 了……也许这不是它的工作原理!

2) 使用 NSLayoutManager 使范围无效

使用 invalidateLayoutForCharacterRange:actualCharacterRange: 使 layoutManager 无效。但这实际上是做什么的,它将如何减轻文本归因阶段的负担?它什么时候让我知道需要突出显示特定文本?另外,我看到 NSLayoutManager 会懒惰地绘制字形......如何?什么时候?这对我有什么帮助?我如何利用这个调用,以便我可以在它实际布置文本之前归因于支持字符串?

3) 覆盖 NSLayoutManager drawGlyphsForGlyphRange:atPoint: 方法。

我真的不想这样做。话虽如此,在 Mac OS X 中,NSAttributedStrings 具有临时属性的概念,其中样式信息仅用于表示。这大大加快了突出显示的过程!问题是,它在 iOS 7 TextKit 框架中不存在(或者它在那里,我只是不知道)。我相信,通过覆盖此方法,它将为我提供与使用临时属性相同的速度......因为我可以在此方法中回答所有布局、颜色和格式问题,而无需触及 NSTextStorage 属性细绳。唯一的问题是,我不知道这个方法相对于 NSLayoutManager 类中提供的其他方法是如何工作的。它是否保持宽度和高度的状态?当 NSTextContainer 太小时,它会修改它的大小吗?此外,它仅绘制已添加到文本缓冲区中的字符的字形。它不会重新绘制整个屏幕。只有一小部分......这很好。我对如何使用它有一些想法……但我真的不想布置字形。那是太多的工作,我还没有找到一个很好的例子来做到这一点。

我将非常感谢您提供的任何帮助。

作为感谢,我列出了我在过去几年中使用过的所有框架和参考资料,这些框架和参考资料帮助我达到了现在的水平,希望它们对你有所帮助。

语法高亮框架:

资源:

这些框架中的大多数都是相同的。它们要么不考虑范围的上下文切换(或者您必须编写包装器以提供上下文),要么它们不会在用户修改文本(例如字符串、多行注释等)时修复上下文范围。最后一个要求非常重要。因为如果分词器无法确定哪些范围受到更改的影响,您最终将不得不再次解析和属性整个字符串。唯一的例外是 Crimson 编辑器。这个分词器的问题是它在分词时不保存状态。在绘图时,算法使用标记来确定绘图状态。它从文档的顶部开始,直到到达可见的文本范围。不用说,

另一个问题是这些框架没有遵循 Apple 所做的相同 MVC 模式——这是意料之中的。具有完整工作编辑器的框架都使用由它们构建的 API(即 GTK、Windows 等)提供的钩子,这些钩子为它们提供关于何时何地绘制到屏幕哪个部分的信息。就我而言,TextKit 似乎要求您在 processEditing 中归因于整个更改范围。

也许我的观察是错误的。(我希望他们是!!)也许,例如 ParseKit,它将为我需要它做的工作而我根本不明白如何使用它。如果是这样,请告诉我!再次感谢!

4

1 回答 1

9

我想到了。我没有使用上述任何建议。话虽如此,我现在得到的表现简直令人难以置信。请记住,YMMV。您标记和缓存有关字符串的元数据的方式可能与我不同。但是,我能够输入一个 1400 行的 PHP 文件,并且只需 0.015 秒即可完成任何一项更改。简直不可思议。

这是我采取的方法:

我的 UIViewController 是 UITextViewDelegate 和 UIScrollViewDelegate 的代表。

当 UITextViewDelegate.textViewDidChange: 被调用时,我确定最终用户当前可见的文本范围。我通过使用现有的子类 UITextView 并将此方法添加到其中来做到这一点:

- (NSRange)visibleRangeOfText
{
    CGRect bounds = self.bounds;
    UITextPosition *start = [self characterRangeAtPoint:bounds.origin].start;
    UITextPosition *end = [self characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end;
    return NSMakeRange([self offsetFromPosition:self.beginningOfDocument toPosition:start],
                   [self offsetFromPosition:start toPosition:end]);
}

之后,我将范围传递给一个子类 NSTextStorage 对象,然后它将在其中执行魔术以确定需要突出显示哪些行。

UIScollViewDelegate 方法调用也是如此。根据正在查看的视图的哪个部分,我将可见范围内的内容传递给我的子类 NSTextStorage 调用,并确定这些行是否已经被属性化,等等。

我意识到我给读者留下了很多东西。我最终使用了我目前拥有的东西并对其进行了一些调整以与上述实现一起使用。

我想分享一些我在实现这个过程中发现的有趣的发现:

1) 如果您尝试突出显示当前行上方的任何文本,即光标所在的位置,您可能会看到光标在视图中“跳”起来,然后回到原来的位置。我几乎肯定这是由 NSTextStorage.processEditing 方法调用引起的。我能够将它带到系统仅突出显示已修改的行的位置......所以这个问题现在已经消失了。

2)最初我这样做是为了防止光标跳来跳去:

NSRange selectedRange = [textView selectedTextRange];
[textView setScrollEnabled:NO];
NSRange visibleRange = [textView visibleRangeOfText];
[textStorage applyAttributesToRange:visibleRange];
[textView setScrollEnabled:YES];

它起作用了……但是 [textView setScrollEnabled:NO] 调用对性能造成了巨大的打击。仅该命令就花了将近 3/4 秒的时间来完成一个 1400 行的文件。我不确定是什么导致它变慢,但我认为值得一提。

于 2013-11-13T05:52:48.890 回答