我试图从表视图中抑制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 view和commitEditingStyle: delete data but continue to display row
编辑我也知道有一种更好的方法来抑制单元格调用[tableView deleteRowsAtIndexPath ..](它在这里工作)但我真的很想了解我得到的输出和什么coreData正在处理我背后的对象......
我无法理解这里发生的事情。我尝试了以前代码的几个变种,但似乎没有解决我的问题。
答案 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表中删除对象。如果对象尚未保存到持久存储中,则只需从对象中删除接收者。“
因此,您需要保存要删除的上下文以提交到商店,否则该对象将被标记为“已删除”