从 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。
希望对您有所帮助!
我正试图从 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。
希望对您有所帮助!