从 tableView 中删除 NSManagedObject 并正确地重新加载视图

Delete NSManagedObject from a tableView and correctly re-load view

我正试图从 table 视图中抑制 NSManagedObject。

我有一个 TableView 控制器,它是 UITableViewController 的子类,它拥有一个额外的 属性 类型的 myContainer 以及一个项目数组。

@property (nonatomic, strong) myContainer * parentContainer;
@property(nonatomic, strong) NSArray * allItems;

myContainer 是一个 NSManaged 对象,它包含一个 NSSet * 类型为 myItem 的对象:

@interface myContainer : NSManagedObject
@property (nonatomic, strong) NSSet *subItems;

我是这样实现删除功能的:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {

    NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext];
    myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row];
    [context deleteObject:selectedItem];
    [self viewWillAppear:NO];
    [self.view setNeedsDisplay];
}

}

(注意指令 [context deleteObject:selectedItem] 给我一个警告 "Incompatible pointer types sending 'myObject *' 到 'NSManagedObject ' 类型的参数;但我怀疑这是否与以下内容相关)

方法 viewWillAppear 是这样的:

- (void)viewWillAppear:(BOOL)animated {
    self.allItems = [self.parentContainer.subItems allObjects];
    NSLog(@"%lu objects",(unsigned long)[self.allItems count]);
    [super viewWillAppear:animated];
    [self.tableView reloadData];
}

为了理解会发生什么,我还对 cellForRowAtIndexPath 进行了子类化

- (UITableViewCell *)tableView:(UITableView *)tableView
     cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    // .. //
    myItem * selectedItem = self.allItems[indexPath.row];
    NSLog(@"%lu : %@",indexPath.row,[selectedItem description]);
}

现在假设我首先加载我的 table 并且它包含 3 个对象。我收到日志:

3 objects
0 : item0
1 : item1
2 : item2

我滑动并删除了第二项。视图更新但相应的单元格没有消失;它只是变得空虚。日志内容如下:

3 objects
0 : item0
1 : (null)
2 : item2

现在我会做任何其他会重新加载视图的事情:要么转到上一个视图然后返回,转到另一个视图然后返回,要么删除第二个对象。然后一切正常,日志为:

2 objects
0 : item0
1 : item2

我看到了以下两个似乎密切相关的问题,但他们没有收到任何似乎可以解决问题的答案:tablerow delete but still showon in table view and commitEditingStyle: delete data but continue to display row

EDIT 我也知道有更好的方法来抑制单元格调用 [tableView deleteRowsAtIndexPath ..](它在这里有效)但我真的会想了解我得到的输出以及 coreData 对我背后的对象做了什么...

我无法理解这里发生的事情。我尝试了以前代码的几种变体,但似乎没有解决我的问题。

对我来说,您似乎没有更新数据数组。您删除了第 1 项,但该数组仍然有对该 NSManagedObject 的引用,它可能被 coreData 标记为 "Deleted"。发生这种情况是因为您设置数组的代码的唯一部分是在 viewWillAppear 上。

相反,您应该在删除项目时重新获取数组,然后重新加载数据。

这也是每当您返回 previous/further 视图并返回时,它都有效的原因。因为正在调用 viewWillAppear 并更新数组。

更新:

在我看来,您是从 parentContainer 中获取对象的。这可能意味着 parentContainer 需要知道您删除了一个项目以更新数组。我建议你创建一个委托,向它发送通知,或者只是在你的 parentContainer 中创建一个 public 方法,并在你重新加载 tableView 时让这个 viewController 调用。

我的做法是封装重新加载 tableView 的逻辑:

- (void)reloadTableView {
    [self.parentContainer reloadSubItems];
    self.allItems = [self.parentContainer.subItems allObjects];
    [self.tableView reloadData];
}

然后更改 viewWillAppear 以调用它:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self reloadTableView];
}

而不是在 commitEditingStyle 中显式调用 viewWillAppear:

 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext];
        myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row];
        [context deleteObject:selectedItem];
        [self reloadTableView];
        //you dont need to call setNeedsDisplay here to update the
        //tableView unless you do other stuff related to view frames
        //and the like, for example, you changed the tableView's
        //frame. in this case you would call setNeedsLayout.
    }
}

您将在您的 parentContainer 中创建 reloadSubItems 并将获取逻辑添加到其中。

更新 2:

而不是编辑您的 parentContainer,您可以只从数组中删除要删除的项目。首先,您需要将 allItems 更改为 NSMutableArray 类型,而不是 NSArray.

您的 reloadTableView 方法如下所示:

 - (void)reloadTableView {
    [self.parentContainer reloadSubItems];
    self.allItems = [[NSMutableArray alloc] initWithArray:[self.parentContainer.subItems allObjects]];
    [self.tableView reloadData];
}

和 commitEditingStyle:

 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObjectContext *context = [[Catalog sharedCatalog] managedObjectContext];
        myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row];
        [self.allItems removeObject:selectedItem];
        [context deleteObject:selectedItem];
        [self reloadTableView];
    }
}

这样您就不需要告诉 parentContainer 更新项目,因为您将自己从数组中删除项目。

更新 3:

根据 deleteObjects: 上的 documentation,"When changes are committed, object will be removed from the uniquing tables. If object has not yet been saved to a persistent store, it is simply removed from the receiver."

所以您需要保存上下文以便将您的删除提交到存储区,否则该对象将被简单地标记为 "deleted"

据我所知,我假设 myContainer 与 myItem 之间存在一对多关系。这很好,因为 Core Data 会在删除 myItems 对象后自动从该关系中删除对象,但不会立即。不像这里的一些人所说的那样,您不必保存上下文或刷新任何内容以使这些更改反映在关系中,您只需要在删除后对上下文调用 processPendingChanges,如下所示:

[context deleteObject:selectedItem];
[context processPendingChanges];

processPendingChanges 将导致 Core Data 执行它仍然需要执行的任何关系更新,并且还将触发任何可能仍未决的与核心数据相关的通知。

你还需要确保你的 allItems 数组在这些变化发生时被重新填充(你目前似乎没有这样做)因为 Core Data 不会释放已被删除的对象或从普通数组中删除它们。它只会将其标记为已删除,并让它在您的数组中占用 space 。与 Erakk 在他的回答中所说的类似,您应该将此数组重新填充封装到与 table 视图重新加载相同的方法中。您也可以在此处而不是在删除之后调用 processPendingChanges,以确保在将对象从关系中拉出之前,任何和所有未决更改都反映在关系中。您不需要手动更新 myContainer 中的 subItems 关系。那是 Core Data 的工作。

- (void)reloadTableView {
    [self.parentContainer.managedObjectContext processPendingChanges];
    self.allItems = [[NSMutableArray alloc] initWithArray:[self.parentContainer.subItems allObjects]];
    [self.tableView reloadData];
}

正如其他一些人所说,您绝对不想直接调用 viewWillAppear:。您应该将您的代码移动到从两个地方调用的助手。

这一行也是一个潜在的问题:

 myItem *selectedItem = [self.parentContainer.subItems allObjects][indexPath.row];

subItems 是一种无序,对多关系,这意味着你不能保证从这里的数组中得到你真正期望的项目,因为 NSSet 上的 "allItems" 方法理论上可以以任何顺序返回。您应该将这些对象存储在一个(可能是排序的)数组中并访问它,或者您可以将 subItems 关系更改为有序关系,然后将 属性 更改为 NSOrderedSet。

希望对您有所帮助!