重新排序单元格后如何更改获取结果控制器数组中对象的顺序

Posted

技术标签:

【中文标题】重新排序单元格后如何更改获取结果控制器数组中对象的顺序【英文标题】:How to change the order of objects in the array of fetchresultcontroller after reordering the cells 【发布时间】:2015-03-30 21:57:33 【问题描述】:

我有一个表格视图,我刚刚实现了一个可以帮助我重新排列单元格的类,就像表格视图委托附带的常规移动单元格方法一样。

现在,在对单元格重新排序后,我需要将保存单元格对象的数组更改为新顺序...我该怎么做?

这是我对单元格重新排序的方法:

- (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath  NSArray 


我有一个 coreDataStack 类来处理所有的核心数据(创建一个单例),它看起来像这样:

#import "CoreDataStack.h"

@implementation CoreDataStack

#pragma mark - Core Data stack

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

+ (instancetype)defaultStack 

    static CoreDataStack *defaultStack;
    static dispatch_once_t onceTocken;
    dispatch_once (&onceTocken, ^
        defaultStack = [[self alloc] init];
    );

    return defaultStack;



- (NSURL *)applicationDocumentsDirectory 
    // The directory the application uses to store the Core Data store file. This code uses a directory named "digitalCrown.Lister" in the application's documents directory.
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];


- (NSManagedObjectModel *)managedObjectModel 
    // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
    if (_managedObjectModel != nil) 
        return _managedObjectModel;
    
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Lister" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
    if (_persistentStoreCoordinator != nil) 
        return _persistentStoreCoordinator;
    

    // Create the coordinator and store

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Lister.sqlite"];
    NSError *error = nil;
    NSString *failureReason = @"There was an error creating or loading the application's saved data.";
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) 
        // Report any error we got.
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
        dict[NSLocalizedFailureReasonErrorKey] = failureReason;
        dict[NSUnderlyingErrorKey] = error;
        error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    

    return _persistentStoreCoordinator;



- (NSManagedObjectContext *)managedObjectContext 
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
    if (_managedObjectContext != nil) 
        return _managedObjectContext;
    

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) 
        return nil;
    
    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    return _managedObjectContext;


#pragma mark - Core Data Saving support

- (void)saveContext 
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) 
        NSError *error = nil;
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) 
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        
    



@end

每当我向核心数据添加新对象时,我都会这样做:

- (void)insertTeget 

    CoreDataStack *stack = [CoreDataStack defaultStack];
    Target *target = [NSEntityDescription insertNewObjectForEntityForName:@"Target" inManagedObjectContext:stack.managedObjectContext];
    if (self.myTextView.text != nil) 
        target.body = self.myTextView.text;
        target.time = [NSDate date];
    

    [stack saveContext];


在表格视图中,当我获取数据时,我是这样做的:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 


    static NSString *cellIdentifier = @"StackTableViewCell";

    Target *target = [self.fetchedResultController objectAtIndexPath:indexPath];

    StackTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (!cell)
    
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"StackTableViewCell" owner:self options:nil];
        cell = [topLevelObjects objectAtIndex:0];
    

    cell.cellLabel.text = target.body;

    cell.cellLabel.font = [UIFont fontWithName:@"Candara-Bold" size:20];

    cell.showsReorderControl = YES;



    // Configure the cell...

    return cell;

这是我在表视图控制器类中的 fetchresultconroller/fetch 请求配置:

- (NSFetchRequest *)targetsFetchRequest 

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"time" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    return fetchRequest;



- (NSFetchedResultsController *)fetchedResultController 

    if (_fetchedResultController != nil) 
        return _fetchedResultController;
    

    CoreDataStack *stack = [CoreDataStack defaultStack];

    NSFetchRequest *fetchRequest = [self targetsFetchRequest];

    _fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:stack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];

    _fetchedResultController.delegate = self;

    return _fetchedResultController;


我想要完成的是,每当用户创建一个目标对象时,它都会走到数组的末尾(所以它就像一个队列),如果用户移动单元格,那么我需要更改数据库数组...

移动单元法:

- (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath 

    int start = 0;
    int end = 0;
    if (fromIndexPath.row > toIndexPath.row) 
        start = (int)fromIndexPath.row;
        end = (int)toIndexPath.row;
     else 
        start = (int)toIndexPath.row;
        end = (int)fromIndexPath.row;
    

    for (int i = start; i <= end; ++i) 
        Target *target = [self.fetchedResultController objectAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        [target setOrder:@(i)];
    

    [[CoreDataStack defaultStack] saveContext];


    // a test to see if the order is changed
    [self.fetchedResultController performFetch:nil];

    NSArray *arr = [self.fetchedResultController fetchedObjects];
    for (int i=0; i<arr.count; i++)  
        Target *ta = [arr objectAtIndex:i];
        NSLog(@"%@",ta.body);
    

日志:

2015-04-14 10:29:13.405 Lister[3163:477453] One
2015-04-14 10:29:13.406 Lister[3163:477453] Two
2015-04-14 10:29:13.406 Lister[3163:477453] Three
2015-04-14 10:29:13.407 Lister[3163:477453] Four
2015-04-14 10:29:13.407 Lister[3163:477453] Five
2015-04-14 10:29:21.070 Lister[3163:477453] 

2015-04-14 10:29:21.071 Lister[3163:477453] One
2015-04-14 10:29:21.071 Lister[3163:477453] Two
2015-04-14 10:29:21.071 Lister[3163:477453] Three
2015-04-14 10:29:21.072 Lister[3163:477453] Four
2015-04-14 10:29:21.072 Lister[3163:477453] Five
2015-04-14 10:29:25.037 Lister[3163:477453] 

2015-04-14 10:29:25.039 Lister[3163:477453] One
2015-04-14 10:29:25.039 Lister[3163:477453] Two
2015-04-14 10:29:25.040 Lister[3163:477453] Three
2015-04-14 10:29:25.040 Lister[3163:477453] Four
2015-04-14 10:29:25.041 Lister[3163:477453] Five

另外,单元格的标签现在表现得很奇怪,如果将带有标签“一”的单元格移动到带有标签“二”的单元格的索引,那么“一”的标签将变为“二” .所以我得到了2个单元格具有相同标签的情况。

【问题讨论】:

NSFetchedResultsController 排序基于什么属性? 只是单元格的文本@ErikJohansson 您为什么不根据您的标准重新排序数组,然后重新加载表格以重新排列单元格? 【参考方案1】:

最简单的解决方案是

    为您的Target 实体添加一个属性,例如order 类型为Integer32

创建和插入新对象

    每当您创建一个新的Target 对象时,首先使用sortDescriptor 从数据库中获取现有对象,键为@"order"ascending=YES。取出这个获取数组的最后一个对象并检查它的顺序。现在在您的新Target 对象中增加订单并将其插入数据库。如果获取的数组返回0个对象,则设置order=@(0)

    - (void)insertTeget 
    
        CoreDataStack *stack = [CoreDataStack defaultStack];
    
        //Fetching objects from database
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
        NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES];
        [fetchRequest setSortDescriptors:@[sortDescriptor]];
        NSArray *existingObjects = [stack.managedObjectContext executeFetchRequest:fetchRequest error:nil];
    
        //Creating new object
        Target *target = [NSEntityDescription insertNewObjectForEntityForName:@"Target" inManagedObjectContext:stack.managedObjectContext];
        if (self.myTextView.text != nil) 
            target.body = self.myTextView.text;
            target.order = @([(Target *)existingObjects.lastObject order].integerValue + 1);
        
    
        [stack saveContext];
    
    

NSFetchedResultsController

    使用上面定义的sortDescriptor获取对象。

取自您的代码

    - (NSFetchRequest *)targetsFetchRequest 

        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];
        return fetchRequest;
    


    - (NSFetchedResultsController *)fetchedResultController 

        if (_fetchedResultController != nil) 
            return _fetchedResultController;
        

        CoreDataStack *stack = [CoreDataStack defaultStack];
        NSFetchRequest *fetchRequest = [self targetsFetchRequest];
        _fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:stack.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
        _fetchedResultController.delegate = self;
        return _fetchedResultController;
    

重新排列单元格

    现在,当您在表格视图中重新排列单元格时,您只需要运行一个 for 循环并更新它们的顺序。您只需更新两个 indexPath 之间的对象的order

    - (void)moveTableView:(FMMoveTableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath   
    
        int start = 0;
        int end = 0;
        if (fromIndexPath.row > toIndexPath.row) 
            start = fromIndexPath.row;
            end = toIndexPath.row;
         else 
            start = toIndexPath.row;
            end = fromIndexPath.row;
        
    
        for (int i = start; i <= end; ++i) 
            Target *target = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
            [target setOrder:@(i)];
        
    
        [[CoreDataStack defaultStack] saveContext];
    
    

注意:上面的解决方案假设你有order从0开始。


当您创建和插入新的Target 对象时,您需要实现NSFetchedResultsController 委托方法来为这些对象添加相应的行。由于我们已经定义了sortDescriptor,所以新的行将添加到tableView的末尾。

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
    [self.tableView beginUpdates];



- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type 

    switch(type) 
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                            withRowAnimation:UITableViewRowAnimationFade];
            break;
    



- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
    atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
    newIndexPath:(NSIndexPath *)newIndexPath 

    UITableView *tableView = self.tableView;

    switch(type) 

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;
    



- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
    [self.tableView endUpdates];

【讨论】:

我在理解 #2 时遇到了问题,请您尝试向我解释一下吗?非常感谢@Burhanuddin Sunelwala 在你的问题的最后,你提到了“我想要完成的是,每当用户创建一个目标对象时,它就会走到数组的末尾......” i>,所以每当你创建一个新的Target 对象时,你应该知道分配给它的顺序。因此,您获取按顺序排序的现有Target 对象并检查最后一个对象的顺序。现在您知道为新对象设置的顺序,即最后一个对象的顺序 + 1。 如果您不删除对象,那么您只需获取现有对象并进行计数。您可以将计数作为订单分配给新对象。如果你知道了,请告诉我。 我在问题中添加了我的错误,它是一个屏幕截图,你能看一下吗?你想让我实现 setOrder..? 您是否在 .xcdatamodeld 文件中将 Interger 类型的属性 order 添加到您的实体 Target 中?如果是,那么您需要在 Targer.h 文件中创建一个属性 - @property (nonatomic, strong) NSNumber *order; 并在您的 Target.m 文件中添加 - @dynamic order;。另外你为什么要创建Target 对象两次?我将用有问题的代码更新我的代码。【参考方案2】:

简单解决方案: Reffer Apple's Doc:

创建一个 NSMutableArray 来识别重新排序的数组。

第 1 步:在头文件或类文件中声明 NSMutableArray 属性 @property (strong, nonatomic) NSMutableArray *arrayTag

第二步:viewDidLoad中初始化

第 3 步:将此代码添加到 tableview 委托方法中

-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath

    NSString *stringToMove = [arrayTag objectAtIndex:sourceIndexPath.row];
    [arrayTag removeObjectAtIndex:sourceIndexPath.row];
    [arrayTag insertObject:stringToMove atIndex:destinationIndexPath.row];

【讨论】:

【参考方案3】:

试试这个

-(void)moveTableView:(UITableView *)tableView moveRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath  

    NSString *str1 =  [[yourArray  objectAtIndex:fromIndexPath.row] copy];
    NSString *str2 =  [[yourArray  objectAtIndex:toIndexPath.row] copy];

    [yourArray replaceObjectAtIndex:fromIndexPath.row withObject:str2];
    [yourArray replaceObjectAtIndex:toIndexPath.row withObject:str1];

    [tableView reloadData];

【讨论】:

【参考方案4】:

如果我正确理解了您的 Q,您必须更改模型的架构。

A.一、我的理解

您有一个项目列表。最后添加新项目。现在您想让用户能够以自定义方式重新排序项目。

B.你做什么

实际上,您正在使用创建日期属性进行排序。当然,您不能将其用于重新排序,因为这将意味着更改创建日期。因此,使用创建日期的整个方法都失败了:对于不由用户更改的列表来说已经足够了,但如果您有自定义订单,则不是。

C.你能做什么

如果您有自定义订单,则需要自定义属性来反映订单。如果列表是实例对象的属性,您可以使用NSOrderedSet 和Core Data 的有序关系来执行此操作。我不会因为有回报。但是,如果对您有用,您可以这样做。

否则你必须自己处理:

一个。将属性 order 添加到您的实体类型。

b.插入新对象时,检查现有列表的计数(取决于您持有它的方式)并将值设置为新实例的 order 属性。

c。获取时,使用该属性进行排序。

d。更改时,更改实例对象的属性以及源和目标之间的所有实例对象的属性。让我解释一下:

我们有一个这样的列表:

name          order
Amin          0
Negm          1
Awad          2
Answer        3

现在,例如,Answer 向上从位置 3 移动到位置 1(在 Negm 之前):

name          order
Amin          0
Answer        3
Negm          1
Awad          2

这意味着移动对象 (3) 的 order 属性必须更改为新的目标 (1),并且所有具有 >=1 到

name          order
Amin          0
Answer        1
Negm          2
Awad          3

在代码中

NSUInteger oldIndex = …; // 3
NSUInteger newIndex = …; // 1

movedObject.order = newIndex;

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Target"];
NSPredicate *betweenPredicate = [NSPredicate predicateWithFormat:@"order >= %ld AND order < %ld", newIndex, oldIndex];
NSArray *objectsToChange = [context executeFetchRequest:request error:NULL];

for( Target *target in objectsToChange )

  target.order = @([target.order unsignedIntegerValue] + 1);

如果一个项目被向下移动,你必须反过来做同样的事情。


如果您有不同的唯一对象列表,例如 iTunes 中的播放列表,您需要一个额外的实体类型而不是一个额外的属性。让我知道,我将发布我的一本书中的代码,包括移动有间隙的项目列表。

【讨论】:

【参考方案5】:

如果您希望 NSFetchResultsController 接受更改,则必须更改底层数据模型以反映新订单。

【讨论】:

如果您想继续使用 NSFetchedResultsController,您需要向您的数据模型(即 sortOrder)添加一个属性,您可以更新该属性以反映对象的排序顺序。这可能是一个好主意,也可能不是一个好主意,具体取决于您的数据的外观。

以上是关于重新排序单元格后如何更改获取结果控制器数组中对象的顺序的主要内容,如果未能解决你的问题,请参考以下文章

重新排列表格视图单元格后如何更新核心数据记录

重新排序单元格后重新加载自定义 UITableViewCell

点击单元格后不同的 UITableViewCell 子视图布局

在 tableView 中选择单元格后布局更改

如何重新排序获取的结果以进行显示?

将核心数据加载到数组中,然后填充表视图,重新排序数组对象不会持久