NSTextView的insertText:*真的*不适合以编程方式修改文本吗?



我已经编写了一个NSTextView子类,该子类对自己内部的文本进行频繁的编程修改(有点像IDE的代码格式——例如,自动插入大括号)。

我最初的实现使用了NSTextView的insertText:。事实上,这似乎完全有效。但是,在阅读NSTextView文档(有时我这样做是为了好玩)时,我在insertText: 的讨论部分注意到

这种方法是插入用户键入的文本的入口点,通常不适合用于其他目的。文本的程序化修改最好通过直接操作文本存储器来完成。

哦,我的糟糕,我想。因此,我尽职尽责地将所有insertText调用更改为对底层NSTextStorage(主要是replaceCharactersInRange:withString:)的调用。这似乎还可以,直到我注意到它完全破坏了Undo(当然,因为Undo是由NSTextView处理的,而不是由NSTextStorage处理的)。

所以,在我把一个buncha-undo代码放进我的文本存储之前,我想知道我是否已经被朋克化了,insertText:真的没那么糟糕吗?

是的,所以我的问题是:NSTextView的insertText:调用真的"不适合"对NSTextView文本进行编程修改吗?如果是,为什么?

insertText:NSResponder的一种方法——通常,这些方法被认为是响应用户事件的方法。在某种程度上,它们意味着"用户操作"。当文档告诉您,如果您想以编程方式更改内容,请直接编辑NSTextStorage时,"以编程方式"一词被用来区分用户意图和应用程序操作。如果您希望您的更改是可撤消的,就像它们是用户操作一样,那么insertText:似乎可以使用。也就是说,大多数情况下,如果修改不是由用户操作发起的,那么用户不会认为它是一个可撤销的操作,将其作为一个可撤消的操作单元会导致混乱。

例如,假设我将一个单词"foo"粘贴到您的文本视图中,然后您的应用程序将该单词涂成红色(无论出于何种原因)。如果我选择撤消,我希望我的操作是撤消的东西,而不是着色。着色不是我用户意图的一部分。如果我必须再次点击Cmd-Z才能真正撤销我的操作,我会想,"WTF?"

NSUndoManager支持通过beginUndoGroupingendUndoGrouping对事件进行分组。这可以允许用户意图的单位(粘贴操作)与应用程序着色一起分组为一个撤消的"单位"。在最简单的情况下,您可能想在这里尝试的是将NSUndoManager上的groupsByEvent设置为YES,然后找到一种方法来触发应用程序的操作,使其与用户操作在runLoop的同一过程中发生。这将导致NSUndoManager自动对它们进行分组。

除此之外,比如说,如果您的程序修改需要异步进行或其他什么,您需要以某种方式自己管理这些分组,但实现起来可能并不容易。

我不知道你是否看到几年前我的类似问题,但我可以告诉你,@ipmcc是正确的,在对NSTextStorage进行编程更改的同时尝试手动管理撤消堆栈是非常重要的。我花了几个星期的时间,但失败了。

但你的问题和@ipmcc的回答让我认为,我试图做的事情(听起来和你试图做的几乎完全一样)实际上可能更多地是响应用户意图,而不是文档中所说的程序更改。因此,也许使用insertText:的原始解决方案是正确的方法。我放弃我的项目已经很久了,我不记得我是否尝试过,但我不认为我尝试过,因为我试图只使用委托方法构建编辑器,而不将NSTextView子类化。

在我的例子中,如果用户选择了一些文本并点击了开括号键或闭括号键,而不是用括号替换所选文本的默认行为,我想做的是将所选文本包装在括号中。如果用户点击cmd-Z,我希望括号消失。

自从我提出这个问题以来,我已经推进了我的原始实现,它看起来工作得很好。所以我认为这个问题的答案是:

insertText:完全适合程序化修改对于这个特定的用例,

我相信这个注释指的是对文本的纯程序化修改,例如,设置文本视图的所有文本。我可以肯定地看到insertText:不适合这样做。然而,就我的预期目的而言——直接响应用户操作添加或编辑字符——insertText:是完全合适的。

为了使我的文本修改与触发它们的用户交互成为原子(正如@ipmcc在他的回答中提到的那样),我在insertText:覆盖中进行了自己的撤消处理。我在@pjv的类似问题中写到了这一点。github上还有一个示例项目,我可能会在某个时候把它写在我的博客上。

最新更新