【问题标题】:Core Data Undo/Redo - Action depends on what was undone核心数据撤消/重做 - 操作取决于撤消的内容
【发布时间】:2012-02-12 21:38:23
【问题描述】:

我的 iPad 应用程序(一个 OpenGL 绘图应用程序)中有一个有点复杂的数据模型,我正在努力实现撤消/重做功能。我喜欢 Core Data 将免费撤消数据模型更改这一事实,但我不知道内置功能是否足以满足我的需求。

我已经看到很多在绘图应用程序中实现撤消/重做的示例,但它们通常执行以下操作:

  1. 将托管对象上下文告诉undo
  2. 从更改的数据模型中重新绘制页面上的所有内容

这非常低效 - 在我的应用程序中,我需要能够根据正在“撤消”的特定对象执行撤消操作,这通常意味着只刷新画布的一部分。

所以我的问题是:我可以注册自己的撤消操作并将其与内置撤消操作一起使用吗?例如,假设我在用户画线时执行以下操作:

- (void)drawLineFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint
{
    // Register the undo operation.
    [[[managedObjectContext undoManager] prepareWithInvocationTarget:self] 
                                               removeObjectWithIndex:nextObjectIndex];

    // Draw the line object.  
    [self drawLineObjectWithIndex:nextObjectIndex fromPoint:startPoint toPoint:endPoint];

    // Save the new object to the data model.
    [MyCoreDataHelper saveLineObjectWithIndex:nextObjectIndex fromPoint:startPoint toPoint:endPoint];

    nextObjectIndex++;
}

当我要撤消此操作时,会触发调用适当地更改模型吗?或者这种情况是否需要我放弃内置的托管对象上下文撤消系统并仅使用NSUndoManager 自己滚动,包括自己删除和编辑数据模型?除非我能分辨出内置的撤消/重做实际上是在撤消什么,否则看起来这可能会变得非常混乱和复杂......

编辑:我怀疑(如果上面的想法可行)我需要将它包装到一个撤消组中,以便我注册的撤消操作将与数据模型更改一起分组?

另一个编辑: 另外,我可以保证撤消操作的执行顺序吗?换句话说,如果我在保存核心数据更改并注册我的撤消操作后调用undo,我能否确保在调用我的撤消操作之前恢复已删除的实体?

【问题讨论】:

  • 如果只需要刷新部分画布,不能用-(void)setNeedsDisplayInRect:(CGRect)invalidRect
  • 不,有很多原因。首先,我的绘图是使用 OpenGL 完成的。此外,绘图操作涉及查找每个点精灵位置的冗长计算,即使我为每个绘图笔画预先计算了这些,渲染整个屏幕的所有笔画也可能需要很长时间。您是否知道是否可以识别撤消更改了哪些数据?感谢您的建议。

标签: ios core-data nsmanagedobjectcontext undo-redo


【解决方案1】:

那么您知道是否可以识别撤消更改了哪些数据?

您为什么不查看NSManagedObjectContext (Apple Docs) 的通知

具体查看通知的 userInfo,其中包含 NSInsertedObjectsKeyNSUpdatedObjectsKeyNSDeletedObjectsKey 键。

另外,我可以保证撤消操作的执行顺序吗?

嗯,撤消通过堆栈(后进先出)工作,因此您“应该”能够跟踪您的步骤。显然,如果您希望事情正确展开,您必须按照注册撤消的顺序非常具体。几行代码可以帮助我跟踪撤消堆栈,以了解神秘面纱背后的原因:

id undoStack, redoStack;
object_getInstanceVariable(undoManager, "_undoStack", &undoStack);
object_getInstanceVariable(undoManager, "_redoStack", &redoStack);
NSLog(@"%@", [undoStack description]);
NSLog(@"%@", [redoStack description]);

(Props to this site for the above code)

【讨论】:

  • 谢谢,这很有帮助。我会尝试一些事情并更新我的进度。
【解决方案2】:

使用flyweight 结构。 It is generally used 用于撤消/重做操作。您的要求需要您自己完成。

享元可以存储任意数量的数据,并且您可以随时将它们从撤消序列中删除。例如,您的享元可以在每次编辑之前存储像素数据。这会占用大量内存,但您可以轻松撤消某些操作,而无需重新绘制整个场景。

【讨论】:

  • 感谢您的建议。我无法尝试存储像素数据——这可能会在可能只进行少量绘图后使用几 MB 的内存。我已经有了一种通过操纵 OpenGL 顶点缓冲区数据来撤消/重做单个笔画的方法;我已经测试过它并且知道它是有效的。我的问题是我无法实现一种通用的“只是重绘屏幕”的方法——我需要能够说:“哦,用户刚刚撤消了橡皮擦……我将调用 -undoEraserStroke:方法”。
  • 享元结构允许您将不同类型的享元附加到享元链中。撤消非常方便,并且为顶点缓冲区数据的撤消/重做提供了有效的方法,您可以非常轻松地创建享元链。还有 ArrayList 值得研究。它是任何类型的无限可扩展数组(通过 ArrayList 声明 int)。这可能会达到您正在寻找的目的。 (支持对象作为一种类型,完全删除索引等)
  • ArrayList<E>?那不是Java吗?当我确定NSUndoManager 已针对手头的任务进行了优化时,我认为完全推出自己的撤消/重做系统并不是一个好主意。在最坏的情况下,我认为我最终会使用我自己的NSUndoManager,而不是附加到托管对象上下文的那个,并手动更改核心数据模型。这样我仍然可以很容易地利用基于调用的撤消机制。当然,理想情况下,我希望自动处理数据模型更改。
  • 是的,它是 Java,但我试图提供一个示例。我对 iOS 开发环境不太熟悉,但我正在尽我所能提供一些你可以移植的东西。 ArrayList<E> 写起来很简单,如果您专门为此目的编写它。对不起!因此,尽管如此,我并不完全确定 NSUndoManager 的工作原理,只知道如果需要,我将如何编写自定义撤消/重做程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-31
  • 2011-08-07
  • 2014-07-07
  • 2012-06-22
  • 2011-04-03
  • 1970-01-01
  • 2010-12-10
相关资源
最近更新 更多