7

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

我最初的实现使用了 NSTextView 的insertText:. 这实际上似乎工作得很好。但是在阅读NSTextView 文档时(我有时会这样做),我在讨论部分注意到insertText:

此方法是插入用户键入的文本的入口点,一般不适合其他用途。最好通过直接对文本存储进行操作来对文本进行编程修改。

哦,我的错,我想。因此,我尽职尽责地将所有 insertText 调用更改为对底层 NSTextStorage ( replaceCharactersInRange:withString:,主要是) 的调用。这似乎工作正常,直到我注意到它完全搞砸了 Undo(当然,因为 Undo 是由 NSTextView 处理的,而不是 NSTextStorage)。

因此,在我拖出并在我的文本存储中放置一堆撤消代码之前,我想知道我是否可能被朋克,真的insertText:不是那么糟糕吗?

对,所以我的问题是:NSTextView 的insertText:调用是否真的“不适合”以编程方式修改 NSTextView 的文本,如果是,为什么?

4

3 回答 3

5

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

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

NSUndoManager支持通过beginUndoGrouping和对事件进行分组endUndoGrouping。这可以允许将用户意图单元(粘贴操作)与应用程序着色组合成单个撤消“单元”。在最简单的情况下,您可能想在此处尝试设置groupsByEventNSUndoManagerYES,然后找到一种方法来触发应用程序的操作,使其与用户操作在 runLoop 的同一通道中发生。这将导致NSUndoManager它们自动分组。

除此之外,假设您的程序修改需要异步进行或其他方式,您将需要自己管理这些分组,但这可能并不容易实现。

于 2013-02-06T13:28:27.137 回答
1

自从我发布了这个问题以来,我已经推进了我最初的实现,它似乎工作得很好。所以我认为这个问题的答案是:

insertText:完全适合文本的编程修改,针对这个特殊的用例

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

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

于 2013-05-26T05:02:47.357 回答
1

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

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

在我的情况下,例如,如果用户选择一些文本并点击左括号或右括号键,而不是用括号替换所选文本的默认行为,我想要做的是将所选文本包装在括号中. 如果用户然后点击 cmd-Z,我希望括号消失。

于 2013-05-25T13:15:37.797 回答