Подтвердить что ты не робот

Связи NSFetchedResultsController и NSOrderedSet

У меня возникла проблема (если честно), с NSFetchedResultsController и новыми отношениями NSOrderedSet, доступными в iOS 5.

У меня есть следующая модель данных (хорошо, мой реальный не ящик и носок!), но это служит в качестве простого примера:

enter image description here

Ящик и носок - это NSManagedObjects в модели/хранилище основных данных. В Drawer отношение socks - отношение упорядочено к-многим к Sock. Идея состоит в том, что носки находятся в ящике в определенном порядке. В Sock отношение Drawer является обратным отношению socks.

В UIViewController я рисую UITableView на основе этих объектов. Я кормлю таблицу, используя NSFetchedResultsController.

- (NSFetchedResultsController *)fetchedResultsController1 {
    if (_fetchedResultsController1 != nil) {
        return _fetchedResultsController1;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Sock" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"drawer.socks" ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

    self.fetchedResultsController1 = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:@"SocksCache"];
    self.fetchedResultsController1.delegate = self;

    return _fetchedResultsController1;    
}

Когда я запускаю это, я получаю следующее сообщение об ошибке: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'

Это имеет смысл для меня, поскольку отношение < <29 > , а не одна сущность для сравнения для целей сортировки.

Я хочу достичь, чтобы socks появился в UITableView в порядке, указанном в отношении socks. Я действительно не хочу иметь порядок сортировки, но NSFetchedResultsController, который является отличным компонентом, настаивает на том, что он должен быть одним. Как я могу сказать, что он использует порядок носков на объекте Ящика. Я не хочу, чтобы в таблице отображались объекты Drawer.

Примечание. Я использую это только в приложении iOS5, поэтому доступны упорядоченные отношения.

Любой, кто может предложить мне какое-либо направление, будет очень благодарен. Спасибо за ваше время.

Изменить: Таким образом, представление таблицы, отображающее носки, делает это только для одного ящика. Я просто хочу, чтобы представление таблицы соблюдало порядок, в котором содержатся отношения с носками. Я не уверен, что установить критерии сортировки, чтобы убедиться, что это происходит.

4b9b3361

Ответ 1

Damien

Вы должны просто использовать NSFetchRequest форму массива упорядоченного набора. Он будет работать нормально. Контроллеру нужен атрибут для сортировки. Следовательно, вам также нужно указать это.

Эндрю

Ответ 2

Вы можете присвоить носителю индекс и присвоить им носки:

sock01.index = [sock01.drawer.socks indexOfObject:sock01];

Ответ 3

Насколько я понимаю эту функциональность, она позволяет упорядочить Sock в каждом Drawer. Поскольку Apple пишет в документации:

Вы должны использовать их только в том случае, если отношение имеет внутренний порядок, который имеет решающее значение для собственного представления - например, шаги в рецепте.

Это означает, что вы не можете получить все Sock с помощью отсортированного отношения. Сортировка Sock будет доступна только для каждого объекта Drawer.

Ответ 4

Я нашел эту нить, ища ответ на точный вопрос, заданный OP.

Я никогда не встречал примеров представления таких данных в tableView без добавления дополнительного поля сортировки. Конечно, добавление поля сортировки в значительной степени устраняет любую выгоду при использовании упорядоченных отношений. Поэтому, учитывая, что я получил эту работу, я подумал, что это может быть полезно другим людям с тем же вопросом, если я разместил здесь свой код. Это оказалось довольно простым (гораздо проще, чем использование дополнительного поля сортировки) и, по-видимому, с хорошей производительностью. То, что некоторые люди, возможно, не осознали (включая меня, изначально), состоит в том, что тип (NSOrderedSet) атрибута отношения "упорядоченный ко многим" имеет метод получения objectAtIndex и что NSMUtableOrderedSet имеет методы для вставки и удаления objectAtIndex.

Я избегал использования NSFetchedResultsController, как предлагали некоторые плакаты. Я не использовал массив, никакого дополнительного атрибута для сортировки и никакого предиката. Мой код имеет дело с tableView, в котором есть один объект маршрутизации и многопозиционные объекты, причем itinerary.places является полем "упорядоченные ко многим". Я включил редактирование/переупорядочение, но не удаляет ячейки. Метод moveRowAtIndexPath показывает, как я обновил базу данных с переупорядочением, хотя для хорошей инкапсуляции я, вероятно, должен переместить переупорядочение базы данных в файл категории для объекта, управляемого объектом. Здесь весь TableViewController.m:

//
//  ItineraryTVC.m
//  Vacations
//
//  Created by Peter Polash on 8/31/12.
//  Copyright (c) 2012 Peter Polash. All rights reserved.
//

#import "ItineraryTVC.h"
#import "AppDelegate.h"
#import "Place+PlaceCat.h"
#import "PhotosInVacationPlaceTVC.h"


@interface ItineraryTVC ()

@end

@implementation ItineraryTVC

#define DBG_ITIN YES

@synthesize itinerary ;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.navigationItem.rightBarButtonItem = self.editButtonItem ;
}

- (void) viewWillAppear:(BOOL)animated
{

    [super viewWillAppear:animated] ;

    UIManagedDocument *doc = UIAppDelegate.vacationDoc;

    [doc.managedObjectContext performBlock:^
     {   // do this in the context thread (should be the same as the main thread, but this made it work)

         // get the single itinerary for this document

         self.itinerary = [Itinerary setupItinerary: doc ] ;
         [self.tableView reloadData] ;
     }];

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown );
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    return [self.itinerary.places count ];
}


- (UITableViewCell *) tableView: (UITableView *) tableView
          cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
    static NSString *CellIdentifier = @"Itinerary Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier ];


    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault   reuseIdentifier: CellIdentifier];
    }

    Place *place = [self.itinerary.places objectAtIndex:indexPath.row];

    cell.textLabel.text       = place.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d photos", [place.photos count]];

    return cell;
}


#pragma mark - Table view delegate


- (BOOL)    tableView: (UITableView *) tableView
canMoveRowAtIndexPath:( NSIndexPath *) indexPath
{
    return YES;
}

-(BOOL)     tableView: (UITableView *) tableView
canEditRowAtIndexPath: (NSIndexPath *) indexPath
{
    return YES ;
}

-(void)  tableView: (UITableView *) tableView
moveRowAtIndexPath: (NSIndexPath *) sourceIndexPath
       toIndexPath: (NSIndexPath *) destinationIndexPath
{
    UIManagedDocument * doc = UIAppDelegate.vacationDoc ;

    [doc.managedObjectContext performBlock:^
    { // perform in the context thread 

        // itinerary.places is the "ordered, to-many" relationship attribitute pointing to all places in itinerary
        NSMutableOrderedSet * places = [ self.itinerary.places  mutableCopy ] ;
        Place *place                 = [ places objectAtIndex:  sourceIndexPath.row] ;

        [places removeObjectAtIndex: sourceIndexPath.row ] ;
        [places insertObject: place   atIndex: destinationIndexPath.row ] ;

        self.itinerary.places = places ;

        [doc saveToURL: doc.fileURL   forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) {
            if ( !success ) NSLog(@"Error saving file after reorder, startPos=%d, endPos=%d", sourceIndexPath.row, destinationIndexPath.row) ;
        }];
    }];

}

- (UITableViewCellEditingStyle) tableView: (UITableView *) tableView
            editingStyleForRowAtIndexPath: (NSIndexPath *) indexPath
{
    return ( UITableViewCellEditingStyleNone ) ;
}

- (void) prepareForSegue:(UIStoryboardSegue *) segue   sender: (id) sender
{
    NSIndexPath *indexPath = [self.tableView    indexPathForCell: sender] ;
    PhotosInVacationPlaceTVC  * photosInVacationPlaceTVC = segue.destinationViewController ;

    Place *place = [self.itinerary.places objectAtIndex:indexPath.row ];

    photosInVacationPlaceTVC.vacationPlace        = place ;
    photosInVacationPlaceTVC.navigationItem.title = place.name ;

    UIBarButtonItem *backButton =
    [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backButton;

}


@end

Ответ 5

Как я только что ответил здесь, я предпочитаю просто добавлять новое свойство в свой NSManagedObject через категорию.

Просто добавьте метод:

- (NSUInteger)indexInDrawerSocks
{
    NSUInteger index = [self.drawer.socks indexOfObject:self];
    return index;
}

Затем в вашем NSFetchedResultsController используйте дескриптор сортировки:

fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"indexInDrawerSocks" ascending:YES]];

Ответ 6

Чтобы добавить некоторую ясность в ответ от adonoho (спасибо mate), который помог мне разобраться - вместо того, чтобы указывать отношения "многие" как любой сортировочный ключ, который я также не мог получить, укажите свойство -взаимодействие в предикате, чтобы выбрать, какие объекты вы хотите в выбранном контроллере результатов, и указать отношение "принадлежность" как ключ сортировки.

Это удовлетворяет основную цель получения результатов в NSFetchedResultsController со всей этой добротой и уважает порядок во многих отношениях.

В этом конкретном примере (выборка носков ящиком):

// socks belong-to a single drawer, sort by drawer specified sock order
fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"drawer" ascending:YES]];

// drawer has-many socks, select the socks in the given drawer
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"drawer = %@", drawer];

По существу, это использует заданный ящик, чтобы указать, какие носки должны войти в NSFetchedResultsController, и порядок, определенный отношением to-many.

Глядя на сгенерированный SQL (аннотированный из моего примера с использованием разных имен сущностей) с помощью отладки SQL ядра SQL:

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, ...fields..., t0.ZDRAWER, t0.Z_FOK_DRAWER FROM ZSOCKS t0 WHERE  t0.ZDRAWER = ?  ORDER BY  t0.Z_FOK_DRAWER

вы можете увидеть упорядочение SQL, используя столбец Z_FOK_DRAWER, который использует Core Data для позиции этого носка.