NSUndoManager与Core Data一起使用时无法撤销的问题

7
我正在尝试创建一个iPhone应用程序,用户可以添加条目。当用户按下新条目时,将弹出一个框要求输入相关信息。然后,他可以选择“取消”或“保存”以丢弃数据或将其保存到磁盘中。
为了保存数据,我使用Core Data框架,它的效果非常好。但是,我无法让“取消”按钮起作用。当窗口弹出要求输入信息时,我在托管对象上下文(MOC)中创建一个新对象。然后当用户按下取消时,我尝试使用属于MOC的NSUndoManager。
我还想使用嵌套撤消组来实现,因为可能存在嵌套组。
为了测试这一点,我编写了一个简单的应用程序。该应用程序只是启用了Core Data的“基于窗口的应用程序”模板。对于Core Data模型,我创建了一个名为“Entity”的单个实体,带有整数属性“x”。然后在applicationDidFinishLaunching中,我添加了以下代码:
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

  // Override point for customization after app launch    

  unsigned int x=arc4random()%1000;
  [self.managedObjectContext processPendingChanges];
  [self.managedObjectContext.undoManager beginUndoGrouping];

  NSManagedObject *entity=[NSEntityDescription insertNewObjectForEntityForName:@"Entity" 
                                                        inManagedObjectContext:self.managedObjectContext];
  [entity setValue:[NSNumber numberWithInt:x] forKey:@"x"];
  NSLog(@"Insert Value %d",x);

  [self.managedObjectContext processPendingChanges];
  [self.managedObjectContext.undoManager endUndoGrouping];
  [self.managedObjectContext.undoManager undoNestedGroup];

  NSFetchRequest *fetchRequest=[[NSFetchRequest alloc] init];
  NSEntityDescription *entityEntity=[NSEntityDescription entityForName:@"Entity"
                                                inManagedObjectContext:self.managedObjectContext];
  [fetchRequest setEntity:entityEntity];
  NSArray *result=[self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
  for(entity in result) {
    NSLog(@"FETCHED ENTITY %d",[[entity valueForKey:@"x"] intValue]);
  }

    [window makeKeyAndVisible];
}

这个想法很简单。尝试插入一个新的实体对象,然后撤销它,在MOC中获取所有实体对象并打印出来。如果一切正常,最终不应该有任何对象。

然而,我得到了这个输出:

[Session started at 2010-02-20 13:41:49 -0800.]
2010-02-20 13:41:51.695 Untitledundotes[7373:20b] Insert Value 136
2010-02-20 13:41:51.715 Untitledundotes[7373:20b] FETCHED ENTITY 136

从您可以看到的情况来看,在尝试撤销对象的创建后,该对象存在于MOC中。 有什么建议我做错了吗?


你好,我遇到了同样的问题。你找到解决方法了吗?你尝试使用“撤消”而不是“undoNestedGroup”了吗?谢谢, gonso - gonso
3个回答

14

你的问题是由于iPhone托管对象上下文默认没有撤消管理器,不像OS X一样。你需要明确地添加一个。

将应用程序委托中生成的代码更改为以下内容以获取managedObjectContext属性:

- (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil) {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        //add the following 3 lines of code
        NSUndoManager *undoManager = [[NSUndoManager alloc] init];
        [managedObjectContext setUndoManager:undoManager];
        [undoManager release];
        [managedObjectContext setPersistentStoreCoordinator: coordinator];
    }

    return managedObjectContext;

}

做出这个改变后,第二个日志信息不再被打印。

希望这可以帮到你...

Dave


2
在谷歌搜索类似问题时发现了这个。Dave,我假设undoManager默认为nil是有充分的理由的吧?比如说,如果我继续实现它,会不会导致内存相关的问题? - Henry Cooke

4

我尝试了Dave的方法,但对我没有用。

我最终在苹果的示例CoreDataBooks中找到了解决方案。

诀窍是创建一个与您应用程序上下文共享协调器的新上下文。要丢弃更改,您不需要做任何事情,只需丢弃新上下文对象即可。由于您共享协调器,因此保存更新您的主上下文。

这是我的适应版本,其中我使用静态对象作为临时上下文来创建新的ChannelMO对象。

//Gets a new ChannelMO that is part of the addingManagedContext
+(ChannelMO*) getNewChannelMO{

    // Create a new managed object context for the new channel -- set its persistent store coordinator to the same as that from the fetched results controller's context.
    NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
    addingManagedObjectContext = addingContext;

    [addingManagedObjectContext setPersistentStoreCoordinator:[[self getContext] persistentStoreCoordinator]];

    ChannelMO* aux = (ChannelMO *)[NSEntityDescription insertNewObjectForEntityForName:@"ChannelMO" inManagedObjectContext:addingManagedObjectContext];
    return aux;
}

+(void) saveAddingContext{
    NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
    [dnc addObserver:self selector:@selector(addControllerContextDidSave:) 
                name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];

    NSError *error;
    if (![addingManagedObjectContext save:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
    [dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];

    // Release the adding managed object context.
    addingManagedObjectContext = nil;
}

我希望它有所帮助

Gonso


如果保存失败怎么办?你该如何撤销已经放入上下文并导致失败的更改?例如,验证。 - malhal

0

应该可以工作。你是否正确地将撤销管理器分配给了你的托管对象上下文?如果你已经正确地完成了这个步骤,它默认启用了撤销注册,并且你应该可以继续进行。这里有一篇关于核心数据的好文章here。这里还有一个关于核心数据和NSUndoManager的好教程here。希望能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接