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

时间:2015-05-18 18:47:00

标签: ios uitableview cocoa-touch core-data nsmanagedobject

我试图从表视图中抑制NSManagedObject。

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

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

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

@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] 给我一个警告"不兼容的指针类型发送' myObject *'参数类型' NSManagedObject &#39 ;;但我怀疑这与后面的内容有关)

方法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]);
}

现在假设我首先加载我的表,它包含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 viewcommitEditingStyle: delete data but continue to display row

编辑我也知道有一种更好的方法来抑制单元格调用[tableView deleteRowsAtIndexPath ..](它在这里工作)但我真的很想了解我得到的输出和什么coreData正在处理我背后的对象......

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

2 个答案:

答案 0 :(得分:2)

从我看到的,我认为myContainer与myItem有很多关系。这很好,因为在删除myItems对象后,Core Data会自动从该关系中删除对象,但不是立即。与此处的一些人不同,您不必保存上下文或刷新任何内容以使这些更改反映在关系中,您只需在删除后调用上下文中的processPendingChanges,如此:

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

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

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

- (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无序,多对多关系,这意味着您无法保证在此处获得您真正期望的项目,因为" allItems" NSSet上的方法理论上可以以任何顺序返回。您应该将这些对象存储在(可能是已排序的)数组中并进行访问,或者您可以将subItems关系更改为有序关系,然后将属性更改为NSOrderedSet。

希望这有帮助!

答案 1 :(得分:0)

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

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

这也是你回到上一个/更远的视图然后回来的原因,它有效。因为正在调用viewWillAppear并更新数组。

更新:

在我看来,你从parentContainer获取对象。这可能意味着parentContainer需要知道您删除了一个项目来更新数组。我建议您创建一个委托,向其发送通知,或者只是在parentContainer中创建一个公共方法,并在每次重新加载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,“当提交更改时,将从unquing表中删除对象。如果对象尚未保存到持久存储中,则只需从对象中删除接收者。“

因此,您需要保存要删除的上下文以提交到商店,否则该对象将被标记为“已删除”