我在使用UITableView时让我的Core Data实体玩得很好并且订购时遇到了一些麻烦。
我在StackOverflow上已经完成了一些教程和其他问题,但似乎没有明确或优雅的方法来做到这一点 - 我真的希望我错过了一些东西。
我有一个Core Data实体,它上面有一个名为“displayOrder”的int16属性。我使用已经在“displayOrder”上排序的NSFetchRequest来返回我的UITableView的数据。除了重新排序之外的一切都得到了尊重。这是我的(低效)moveRowAtIndePath方法:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSUInteger fromIndex = fromIndexPath.row;
NSUInteger toIndex = toIndexPath.row;
FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];
affectedObject.displayOrderValue = toIndex;
[self FF_fetchResults];
for (NSUInteger i = 0; i < [self.fetchedResultsController.fetchedObjects count]; i++) {
FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, i);
otherObject.displayOrderValue = i;
}
[self FF_fetchResults];
}
有人能指出我的示例代码的方向,或者看看我做错了什么? tableview显示更新OK,我可以通过我的日志消息看到displayOrder属性正在更新。它不是一直保存和重新加载,并且对于这个实现感觉非常“偏离”(除了我所有FFObjects的浪费迭代)。
提前感谢您提供的任何建议。
答案 0 :(得分:27)
我看了一下你的代码,这可能会更好:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
NSUInteger fromIndex = fromIndexPath.row;
NSUInteger toIndex = toIndexPath.row;
if (fromIndex == toIndex) {
return;
}
FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];
affectedObject.displayOrderValue = toIndex;
NSUInteger start, end;
int delta;
if (fromIndex < toIndex) {
// move was down, need to shift up
delta = -1;
start = fromIndex + 1;
end = toIndex;
} else { // fromIndex > toIndex
// move was up, need to shift down
delta = 1;
start = toIndex;
end = fromIndex - 1;
}
for (NSUInteger i = start; i <= end; i++) {
FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);
otherObject.displayOrderValue += delta;
}
[self FF_fetchResults];
}
答案 1 :(得分:2)
(这是对gerry3上面回答的评论,但我还不能评论其他用户的问题和答案。)
gerry3的一个小改进 - 非常优雅 - 解决方案。如果我没有弄错,那就行了
otherObject.displayOrderValue += delta;
如果displayOrderValue
不是基本类型,将实际执行指针算术。这可能不是你想要的。相反,为了设置实体的价值,我建议:
otherObject.displayOrderValue = [NSNumber numberWithInt:[otherObject.displayOrderValue intValue] + delta];
这应该正确更新您的实体属性并避免任何EXC_BAD_ACCESS崩溃。
答案 2 :(得分:1)
这是一个完整的解决方案,如何使用核心数据管理索引表。您的属性名为displayOrder
,我称之为index
。
首先,您最好将视图控制器和模型分开。为此,我使用模型控制器,它是视图和模型之间的接口。
您需要管理3种情况,用户可以通过视图控制器进行影响。
前两个案例添加和删除非常简单。删除调用名为renewObjectIndicesUpwardsFromIndex
的例程,以便在删除的对象之后更新索引。
- (void)createObjectWithTitle:(NSString*)title {
FFObject* object = [FFObject insertIntoContext:self.managedObjectContext];
object.title = title;
object.index = [NSNumber numberWithInteger:[self numberTotalObjects]];
[self saveContext];
}
- (void)deleteObject:(FFObject*)anObject {
NSInteger objectIndex = [anObject.index integerValue];
[anObject deleteObject];
[self renewObjectIndicesUpwardsFromIndex:objectIndex];
[self saveContext];
}
- (void)renewObjectIndicesUpwardsFromIndex:(NSInteger)fromIndex {
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d)", fromIndex];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError* fetchError = nil;
NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
NSInteger index = fromIndex;
for (FFObject* object in objects) {
object.index = [NSNumber numberWithInteger:index];
index += 1;
}
[self saveContext];
}
在我来到重新订购的控制器例程之前,这里是视图控制器中的部分。我使用类似于this answer的bool isModifyingOrder
。请注意,视图控制器调用控制器moveObjectOrderUp
和moveObjectOrderDown
中的两个函数。根据您在表格视图中显示对象的方式 - 最新的或最新的 - 您可以切换它们。
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
isModifyingOrder = YES;
NSUInteger fromIndex = sourceIndexPath.row;
NSUInteger toIndex = destinationIndexPath.row;
if (fromIndex == toIndex) {
return;
}
FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];
NSInteger delta;
if (fromIndex < toIndex) {
delta = toIndex - fromIndex;
NSLog(@"Moved down by %lu cells", delta);
[self.objectController moveObjectOrderUp:affectedObject by:delta];
} else {
delta = fromIndex - toIndex;
NSLog(@"Moved up by %lu cells", delta);
[self.objectController moveObjectOrderDown:affectedObject by:delta];
}
isModifyingOrder = NO;
}
这里是控制器中的部分。这可以写得更好,但为了理解这可能是最好的。
- (void)moveObjectOrderUp:(FFObject*)affectedObject by:(NSInteger)delta {
NSInteger fromIndex = [affectedObject.index integerValue] - delta;
NSInteger toIndex = [affectedObject.index integerValue];
if (fromIndex < 1) {
return;
}
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index >= %d) AND (index < %d)", fromIndex, toIndex];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError* fetchError = nil;
NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
for (FFObject* object in objects) {
NSInteger newIndex = [object.index integerValue] + 1;
object.index = [NSNumber numberWithInteger:newIndex];
}
affectedObject.index = [NSNumber numberWithInteger:fromIndex];
[self saveContext];
}
- (void)moveObjectOrderDown:(FFObject*)affectedObject by:(NSInteger)delta {
NSInteger fromIndex = [affectedObject.index integerValue];
NSInteger toIndex = [affectedObject.index integerValue] + delta;
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];
NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d) AND (index <= %d)", fromIndex, toIndex];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError* fetchError = nil;
NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
for (FFObject* object in objects)
{
NSInteger newIndex = [object.index integerValue] - 1;
object.index = [NSNumber numberWithInteger:newIndex];
}
affectedObject.index = [NSNumber numberWithInteger:toIndex];
[self saveContext];
}
不要忘记在视图控制器中使用第二个BOOL
进行删除操作,以防止移动通知执行任何操作。我将其称为isDeleting
并将其放在此处。
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
if (isModifyingOrder) return;
...
switch(type) {
...
case NSFetchedResultsChangeMove:
if (isDeleting == false) {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:localIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:localNewIndexPath]withRowAnimation:UITableViewRowAnimationFade];
}
break;
...
}
}
答案 3 :(得分:0)
我认为:
affectedObject.displayOrderValue = toIndex;
必须放在:
之后 for (NSUInteger i = start; i <= end; i++) {
FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);
otherObject.displayOrderValue += delta;
}
之前:
[self FF_fetchResults];