Сортировка UITableView

В Cocoa Touch NSFetchedResultsController отвечает не только за извлечение, но и за сортировку данных.


Настройка fetch-запроса:


- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@”Customer
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@”name
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor,
nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means “no sections”.
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController
alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:@”Master];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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. If it is not possible to recover from the error, display an
alert
panel that instructs the user to quit the application by pressing the Home
button.
*/
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}

Один из вариантов сортировать данные, это добавить атрибут displayOrder и сортировать по нему.

В Core Data отношения обычно представляются множествами (NSSet), которые являются не упорядоченными структурами данных. Есть упорядоченные множества NSOrderedSet, но этот класс не является наследником NSSet.

Чтобы сделать строку перемещаемой надо сделать две вещи:
  1. явно сделать строку перемещаемой;
  2. создать метод для управления перемещениями.
Можно устанавливать свойства отдельно для каждой строки, а можно сделать все строки перемещаемыми. Также можно решить будут ли строки перемещаться между секциями.


- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:
(NSIndexPath *)indexPath
{
// The table view should not be re-orderable.
return NO;
}

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:
(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath {
}

С этими изменениями при нажатии на Edit можно будет переместить строку:


Тут есть 4 аспекта:
  • Создайте наследника NSManagedObject для сущности из которых состоит список перемещаемых элементов.
  • Получить множество объектов из persistent store и положите их в muttable-массив.
  • При перемещении объектов, обновляйте значения атрибута displayOrder.
  • Сохраните объекты muttable-массива в persistent store.

Когда вы создаете наследника NSManagedObject, вы получаете класс, который автоматически загружает данные из вашего persistent store. Вы можете получать доступ к свойствам класса напрямую используя KVC.


#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface Customer : NSManagedObject
@property (nonatomic, retain) NSString * address;
@property (nonatomic, retain) NSString * city;
@property (nonatomic, retain) NSNumber * customerSince;
@property (nonatomic, retain) NSNumber * displayOrder;
@property (nonatomic, retain) NSString * email;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * state;
@property (nonatomic, retain) NSString * zip;
@property (nonatomic, retain) NSSet *jobs;
@end
@interface Customer (CoreDataGeneratedAccessors)
- (void)addJobsObject:(NSManagedObject *)value;
- (void)removeJobsObject:(NSManagedObject *)value;
- (void)addJobs:(NSSet *)values;
- (void)removeJobs:(NSSet *)values;
@end


@implementation Customer
@dynamic address;
@dynamic city;
@dynamic customerSince;
@dynamic displayOrder;
@dynamic email;
@dynamic name;
@dynamic state;
@dynamic zip;
@dynamic jobs;
@end

Директива @dynamic заменяет @synthesize для Core Data свойств. @synthesize автоматически создает акцессоры (геттеры и сеттеры). @dynamic автоматически создает такие методы как addJobs и removeJobs, которые объявлены в заголовочном файле.

Стандартный путь для управления перемещаемыми строками это создать NSMutableArray массив элементы которого тоже можно перемещать. Во view-контроллере надо создать такое свойство:


@property (nonatomic, retain) NSMutableArray *customers;

Надо добавить @synthesize директиву для этого свойства. Во viewDidLoad надо переместить объекты полученные из Core Data persistent store в muttable-массив. Пример перемещения top-level объектов в mutable-массив:


- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Set up the edit and add buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self
action:@selector(insertNewObject)];
self.navigationItem.rightBarButtonItem = addButton;
// THIS IS THE CODE TO LOAD THE MUTABLE ARRAY
NSMutableArray *sortedElements = [[NSMutableArray alloc]
initWithArray:self.fetchedResultsController.fetchedObjects];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@”displayOrder ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]
initWithObjects:sortDescriptor, nil];
[sortedElements sortUsingDescriptors:sortDescriptors];
self.customers = sortedElements;
[self.tableView reloadData];
}

Перемещение связанных объектов в mutable-массив:


- (
void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@”displayOrder ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sort
Descriptor count:1];
Customer* myCustomer = (Customer*)self.detailItem;
NSMutableArray *sortedElements = [[NSMutableArray alloc]
initWithArray:[myCustomer.jobs allObjects]];
[sortedElements sortUsingDescriptors:sortDescriptors];
self.jobs = sortedJobs;
[self configureView];
}

Нужно поддерживать mutable-массив обновленным, когда происходит добавление (insertNewObject) или удаление элементов (tableView:commitEditingStyle). Далее вместо того чтобы получать выбранный объект из результатов извлечения контроллером в tableView:didSelectRowAtIndexPath: или configureCell:atIndexPath:, берите его из mutable-массива:


NSManagedObject *object = [self.customers objectAtIndex:indexPath.row]; 

Управление перемещениями:


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)
fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
Customer *customer = [self.customers objectAtIndex:fromIndexPath.row];
[customers removeObjectAtIndex:fromIndexPath.row];
[customers insertObject:customer atIndex:toIndexPath.row];
NSInteger start = fromIndexPath.row;
if (toIndexPath.row < start) {
start = toIndexPath.row;
}
NSInteger end = toIndexPath.row;
if (fromIndexPath.row > end) {
end = fromIndexPath.row;
}
for (NSInteger i = start; i <= end; i++) {
customer = [self.customers objectAtIndex:i];
customer.displayOrder = [NSNumber numberWithInteger: i];
}
}

Метод setEditing вызывается когда пользователь нажимает кнопку edit-done в UIViewController. Тут можно сделать сохранение перемещенных данных:


- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self.tableView beginUpdates];
[self.tableView endUpdates];
/*
If editing is finished, save the managed object context.
*/
if (!editing) {
NSManagedObjectContext *context = self.managedObjectContext;
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
*/
NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
abort();
}
}
}