我编写了一个NSTextView子类,它对文本本身进行频繁的编程修改(有点像IDE的代码格式化 - 例如自动插入紧密括号)。
我最初使用的是NSTextView的insertText:
。这实际上看起来完全正常。但是在阅读NSTextView documentation时(我有时为了好玩),我注意到Discussion section for insertText:
此方法是插入用户键入的文本的入口点,通常不适用于其他目的。最好通过直接对文本存储进行操作来对文本进行编程修改。
哦,我的坏,我想。因此,我尽职尽责地将所有insertText调用更改为对底层NSTextStorage(replaceCharactersInRange:withString:
)的调用。这似乎工作正常,直到我注意到它完全搞砸了Undo(当然,因为Undo是由NSTextView处理的,而不是NSTextStorage)。
所以在我把文件存储器中的buncha撤销代码拖走之前,我想知道我是不是Punk'd,真的insertText:
并不是那么糟糕?
是的,所以我的问题是:NSTextView的insertText:
调用真的“不适合”编程修改NSTextView的文本,如果是,为什么?
答案 0 :(得分:5)
insertText:
是NSResponder
的一种方法 - 通常这些方法会被视为响应用户事件的方法。在某种程度上,它们意味着“用户行为”。当文档告诉您如果要以编程方式更改内容时直接编辑NSTextStorage
,则“以编程方式”一词用于区分用户意图和应用程序操作。如果您希望您的更改可以撤消,就好像它们是用户操作一样,那么insertText:
似乎可以使用。也就是说,大多数情况下,如果修改不是由用户操作启动的,那么用户就不会认为它是一个可撤销的操作,并且使其成为可撤销操作的单位会导致混淆。
例如,假设我在文本视图中粘贴了一个单词“foo”,然后您的应用程序将该单词着色为红色(无论出于何种原因)。如果我然后选择撤消,我希望我的动作是撤消的东西,而不是着色。着色不是我的用户意图的一部分。如果我必须再次击中Cmd-Z以实际撤消我的动作,我就会想,“WTF?”
NSUndoManager
支持通过beginUndoGrouping
和endUndoGrouping
对活动进行分组。这可以允许用户意图单元(粘贴操作)与应用程序着色分组为单个“撤消单元”。在最简单的情况下,您可能想要尝试的是将groupsByEvent
上的NSUndoManager
设置为YES,然后找到一种方法来触发应用程序的动作在runLoop的同一传递中发生用户行动。这将导致NSUndoManager
自动对其进行分组。
除此之外,如果您的程序化修改需要异步或某种形式,您需要自己以某种方式管理这些分组,但这可能是非常重要的实现。
答案 1 :(得分:1)
我不知道您是否在几年前看过my similar question,但我可以告诉您@ipmcc是正确的,并尝试手动管理撤销堆栈,同时对{{1进行编程更改非常重要。我花了好几个星期就失败了。
但你的问题和@ ipmcc的答案让我觉得我试图做的事情(听起来就像你想要做的几乎完全一样)实际上可能更多地回应用户意图比计划变更的文档意味着什么。因此,使用NSTextStorage
的原始解决方案可能是正确的方法。我放弃我的项目已经有很长时间了,我不记得我是否曾经尝试过,但我认为没有,因为我试图使用委托方法构建我的编辑器,而没有子类化{{ 1}}。
在我的例子中,作为一个例子,如果用户选择一些文本并点击打开或关闭括号键,而不是用括号替换所选文本的默认行为,我想要做的是包装所选括号内的文字。如果用户然后点击cmd-Z,我希望括号消失。
答案 2 :(得分:1)
自从我发布这个问题以来,我已经推进了我原来的实现,它看起来效果很好。所以我认为这个问题的答案是:
insertText:完全适合程序化修改 文本,用于此特定用例。
我认为该注释指的是文本的纯编程修改,例如,设置textview的所有文本。我绝对可以看出insertText:
不合适。但是,出于我的预期目的 - 在直接响应用户操作时添加或编辑字符 - insertText:
是完全合适的。
为了使用触发它们的用户交互使我的文本修改成为原子(正如@ipmcc在他的回答中提到的那样),我在insertText:
覆盖中进行自己的撤销处理。我在@pjv's similar question写了这篇文章。还有一个sample project on github,我可能会在某个时候写出来on my blog。