gpt4 book ai didi

cocoa - 如何将 NSUndoManager 与核心数据结合使用并保持用户界面和模型同步?

转载 作者:行者123 更新时间:2023-12-03 17:38:08 25 4
gpt4 key购买 nike

核心数据支持开箱即用的撤消/重做。但它的表现出乎意料。

为了使我的用户界面与我的模型保持同步,我发送了通知。我的用户界面收到通知消息并更新受影响的 View 。

@objc(Entity)
class Entity : NSManagedObject
{
var title : String? {
get {
self.willAccessValueForKey("title")
let text = self.primitiveValueForKey("title") as? String
self.didAccessValueForKey("title")
return text
}
set {
self.willChangeValueForKey("title")
self.setPrimitiveValue(newValue, forKey: "title")
self.didChangeValueForKey("title")
self.sendNotification(self, key:"title")
print("title did change: \(title)")
}
}
}

现在我想向应用程序添加撤消/重做支持。核心数据有一个 NSUndoManager,所以我认为不需要额外的工作。或者至少不多。为了测试这一假设,我制作了一个包含两个 NSTextField 和一个核心数据实体(恰本地命名为 Entity)的测试应用程序。

NSViewController 子类可以访问实体实例(恰本地命名为 testObject)。我观察每次击键以通过 controlTextDidChange: 更新 testObject。

override func controlTextDidChange(obj: NSNotification)
{
guard let value = self.textField?.stringValue else { return }
self.testObject?.setValue(value, forKey: "title")
}

func valueDidChange(sender: Entity, key: String)
{
self.textField?.stringValue = sender.valueForKey("title") as? String ?? ""

}

ManagedObjectContent 和两个文本字段具有相同的 NSUndoManager (调试控制台中的相同指针)。

当我编辑 NSTextField 并执行撤消/重做操作时,NSTextField 和底层 NSManagedObject 属性都保持同步。正如预期的那样。

但是当我将焦点(第一响应者)更改为第二个 NSTextField (没有任何编辑)并撤消/重做操作时,第一个 NSTextField 被(正确)更新,但底层 NSManagedObject 属性没有更新。 title 属性永远不会被调用。

因此第一个 NSTextField 和实体实例在撤消/重做操作后具有不同的值。

更新底层核心数据实例而不是用户界面对我来说更有意义。这里出了什么问题?

旁注:因为我正在观察 NSManagedObject 的任何更改,并且因为 controlTextDidChange: 正在发送通知(因为它更新了 NSManagedObject),所以我收到了对 valueDidChange 的不必要的调用嗯>。有什么技巧可以避免这种情况或者如何改进我的架构?

最佳答案

我做了类似的事情,我发现最好的方法是将 UI Controller 代码(MVC 中的 C)分成两个单独的“路径”。

通过监听来自核心数据模型的通知来观察核心数据模型的变化 NSManagedObjectContextObjectsDidChangeNotification 过滤掉变化是否影响 Controller UI 并相应地调整显示。这条“路径”盲目地跟随 coreData 的变化,不需要与用户交互,也不需要撤消知识。

另一条路径记录了用户请求的变化并相应地修改了核心数据模型。例如,如果我们有一个步进器控件和一个旁边有数字的标签。用户单击步进器。然后 Controller 通过加一或减一来更新核心数据对象上的相关属性。这会使用核心数据模型自动生成撤消操作。如果用户更改影响核心数据中的多个属性,则所有更改都会包含在撤消分组中。然后,对核心数据对象的更改将触发其他 Controller 路径来更新所有 UI 内容(示例中的标签)。

现在撤消功能会自动相反。通过在 MOC 撤消管理器上调用撤消,coreData 将恢复对对象的更改,这将再次触发第一条路径,并且 UI 将自动遵循。

如果用户正在编辑文本字段,我通常不会费心跟踪按键的更改,而是仅在文本字段通知编辑已结束时捕获结果。通过这种方法,编辑后撤消会删除先前编辑 session 中的所有更改,这通常是所需的。如果还需要在文本字段内进行撤消(例如,键入 aa 和 cmd-z 来撤消第二个 a),则可以通过在文本字段编辑时向窗口提供另一个撤消管理器来实现 - 从而避免在同一撤消中进行所有击键撤消堆栈作为核心数据操作。

要记住的一件事是 coreData 有时会等待执行一些操作,这使得事情看起来不同步。在结束撤消分组之前在 MOC 上调用 -processPendingChanges 将解决此问题。

要考虑的另一件事是您要撤消的内容。您是否希望能够撤消用户 key 条目或撤消数据模型中的更改。我有时发现两者,但不是同时发现,因此我发现多个撤消管理器很有用,如前所述。保留文档撤消管理器仅用于对数据模型的更改,这是用户可能长期关心的事情。然后创建一个新的撤消管理器,并在用户处于编辑模式时使用它来跟踪各个按键。一旦用户通过离开文本字段或在对话框中按“确定”等方式确认他对整个编辑感到满意,就丢弃撤消管理器并获取编辑的最终结果并使用文档将其填充到核心数据中撤消管理器。对我来说,这两种类型的撤消是根本不同的,不应该在撤消堆栈中交织在一起。

下面是一些代码,首先是更改监听器的示例(在收到 NSManagedObjectContextObjectsDidChangeNotification 后调用:

-(void)coreDataObjectsUpdated:(NSNotification *)notif {

// Filter for relevant change dicts
NSPredicate *isSectorObject = [NSPredicate predicateWithFormat: @"className == %@", @"Sector"];

NSSet *set;
BOOL changes = NO;

set = [[notif.userInfo objectForKey:NSDeletedObjectsKey] filteredSetUsingPredicate:isSectorObject];
if (set.count > 0) {
changes = YES;
}
else {
set = [[notif.userInfo objectForKey:NSInsertedObjectsKey] filteredSetUsingPredicate:isSectorObject];
if (set.count > 0) {
changes = YES;
}
else {
set = [[notif.userInfo objectForKey:NSUpdatedObjectsKey] filteredSetUsingPredicate:isSectorObject];
if (set.count > 0) {
changes = YES;
}
}
}
if (changes) {
[self.sectorTable reloadData];
}

}

这是创建复合撤消操作的示例,编辑是在单独的工作表中完成的,并且此代码片段将所有更改作为具有名称的单个可撤消操作移动到核心数据对象中。

-(IBAction) editCDObject:(id)sender{ 

NSManagedObject *stk = [self.objects objectAtIndex:self.objectTableView.clickedRow];

[self.editSheetController EditObject:stk attachToWindow:self.window completionHandler: ^(NSModalResponse returnCode){

if (returnCode == NSModalResponseOK) { // Write back the changes else do nothing

NSUndoManager *um = self.moc.undoManager;
[um beginUndoGrouping];
[um setActionName:[NSString stringWithFormat:@"Edit object"]];

stk.overrideName = self.editSheetController.overrideName;
stk.sector = self.editSheetController.sector;

[um endUndoGrouping];
}
}];

}

希望这能给一些想法。

关于cocoa - 如何将 NSUndoManager 与核心数据结合使用并保持用户界面和模型同步?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35322590/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com