为iOS 7创建文本编辑器

时间:2013-11-01 05:46:25

标签: ios objective-c syntax-highlighting parsekit textkit

问题

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

背景

iOS 7推出了TextKit。我有一个完全实现TextKit的tokenizer和代码(请参阅Apple的TextKitDemo项目 - 下面提供了一个链接)......它可以工作。但是,它真的很慢。在通过NSTextStorage解析文本时,它要求您在processEditing方法中在同一线程上着色已编辑文本的ENTIRE范围。将工作卸载到线程并没有帮助。它太慢了。我到了可以仅重新定义修改范围的点,但是如果范围太大,则过程将变慢。在某些情况下,整个文档可能会在更改后失效。

以下是我的一些想法。如果其中任何一个有用或者可能会朝着正确的方向推动我,请告诉我。

1)多个NSTextContainers

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

2)使NSLayoutManager无效范围

使用invalidateLayoutForCharacterRange:actualCharacterRange:使layoutManager无效。但这实际上做了什么以及如何卸载文本归属阶段?什么时候让我知道需要突出显示特定文本?另外,我看到NSLayoutManager会懒洋洋地绘制字形......怎么样?什么时候?这对我有什么帮助?如何点击此调用以便在实际布置文本之前我可以将支持字符串归属?

3)覆盖NS​​LayoutManager drawGlyphsForGlyphRange:atPoint:method。

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

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

谢天谢地,我列出了过去几年我用过的所有框架和参考文献,这些框架和参考资料帮助我到达了现在的位置,希望它们对您有所帮助。

语法高亮框架:

资源:

这些框架大多数都是一样的。它们要么不考虑上下文切换(或者你必须编写包装器来提供上下文),要么它们不修复上下文范围,因为用户修改了文本(例如字符串,多行注释等)。最后一项要求非常重要。因为如果标记生成器无法确定哪些范围受更改影响,则最终必须再次解析并归因整个字符串。唯一的例外是Crimson Editor。此标记化程序的问题在于它在标记化时不会保存状态。在绘制时,算法使用标记来确定绘图的状态。它从文档的顶部开始,直到达到可见的文本范围。不用说,我通过在文档的某些部分缓存文档的状态来优化它。

另一个问题是框架不遵循与Apple相同的MVC模式 - 这是预期的。具有完整工作编辑器的框架都使用由它们构建的API(即GTK,Windows等)提供的钩子,这些钩子为它们提供了绘制到屏幕的哪个部分的位置和时间的信息。在我的例子中,TextKit似乎要求您在processEditing中归因于整个更改的范围。

也许我的观察是错误的。 (我希望他们是!!)也许,ParseKit,例如,将适用于我需要它做什么,我只是不明白如何使用它。如果是这样,请告诉我!再次感谢!

1 个答案:

答案 0 :(得分: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行文件。我不确定是什么导致它变慢但我认为值得一提。