NSFetchedResultsController не извлекает результаты, даже если элементы создаются в базе данных - PullRequest
3 голосов
/ 29 января 2012

Я новичок в iOS, поэтому извиняюсь, если это просто невероятно ... Я итеративно работал над небольшим доказательством концептуальных приложений перед тем, как приступить к реализации своего полного приложения, чтобы оно не было настолько подавляющим,Когда я создавал его, у меня нормально работало табличное представление, следуя учебному пособию «Ваше второе приложение для iOS» на веб-сайте Apple.Теперь я попытался создать его в приложении панели вкладок, и у меня возникают проблемы с NSFetchedResultsController, и я не уверен, связано ли это с тем, что я делаю неправильно в раскадровке, или с чем-то еще.

У меня есть контроллер панели вкладок, который подключается к контроллеру табличного представления (CatalogViewController.h / m), который встроен в контроллер навигации.Контроллер табличного представления настроен на статические ячейки.В первой статической ячейке у меня есть push-переход к другому контроллеру табличного представления (FoodCatalogViewController.h / m), который настроен на использование динамических прототипов - это представление, в котором я ожидаю увидеть объекты из моей базы данных (из сущности Food)- в настоящее время просто показывает имя и калории).В этом представлении есть кнопка «Добавить» для создания новых записей в базе данных - кнопка добавления имеет модальный переход к другому статическому табличному представлению (AddFoodViewController.h / m), которое встроено в его собственный контроллер навигации.Я знаю, что кнопка «Добавить» работает и что ее представление правильно подключается к базе данных (т. Е. Я правильно передаю / устанавливаю NSManagedObjectContext), потому что, если я открываю файл базы данных sqlite приложения с помощью «Браузер базы данных SQLite», яувидеть элементы, которые я добавил в симуляторе.Я просто не понимаю, почему они не отображаются в моем табличном представлении через NSFetchedResultsController.Я прошел по коду с помощью точек останова и подтвердил, что код executeFetch вызывается в функции fetchedResultsController моего FoodCatalogViewController.Я добавил отладочную строку NSLog в коде numberOfRowsInSection, и она кажется нулевой, поэтому я никогда не попадаю в cellForRowAtIndexPath или configureCell.Похоже, виновником является NSFetchedResultsController - я просто не знаю, почему он не получает результаты правильно, и что я могу сделать, чтобы отладить это дальше.Может ли кто-нибудь помочь мне с этим?

Чтобы передать информацию о базовых данных через иерархию, у меня есть следующие фрагменты кода:

AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;

    // Setup the Catalogs Tab
    UINavigationController *navigationController = [[tabBarController viewControllers] objectAtIndex:0];
    CatalogViewController *catalogViewController = [[navigationController viewControllers] objectAtIndex:0];
    catalogViewController.managedObjectContext = self.managedObjectContext;
    return YES;
}

CatalogViewController.m (первый контроллер табличного представления в последовательности - я передаю ему NSManagedObjectContext):

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"BrowseFoodCatalog"]) {        
        [[segue destinationViewController] setManagedObjectContext:self.managedObjectContext];
    }
}

FoodCatalogViewController.h (второй контроллер табличного представления в последовательности - я использую NSManagedObjectContext длянастроить NSFetchedResultsController):

@interface FoodCatalogViewController : UITableViewController <NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;

- (void) addFoodWithName:(NSString *)name calories:(NSNumber *)calories;

@end

FoodCatalogViewController.m (второй контроллер представления таблицы в последовательности - я использую NSManagedObjectContext для настройки NSFetchedResultsController):

@interface FoodCatalogViewController () <AddFoodViewControllerDelegate>
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
@end

@implementation FoodCatalogViewController

@synthesize fetchedResultsController = __fetchedResultsController;
@synthesize managedObjectContext = __managedObjectContext;


- (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:@"Food" 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 arrayWithObjects: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]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return __fetchedResultsController;
}    


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"FoodCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[managedObject valueForKey:@"name"] description];
    NSNumber *calorieNum = [managedObject valueForKey:@"calories"];
    cell.detailTextLabel.text = [[calorieNum stringValue] description];
}

ДополнительноИнформация
Не уверен, что это уместно, но для автоматического включения CoreData в мой проект я начал с шаблона Single View, но изменил его TemplateInfo.plist, добавив следующую строку под аналогичнымлиния для раскадровки:

<string>com.apple.dt.unit.coreDataCocoaTouchApplication</string>

Я нашел это онлайн сгде-то в чьем-то форуме или что-то.Может ли это как-то испортить CoreData?

Дополнительный код
По запросу, вот код, который я использую для добавления новых элементов в базу данных:

AddFoodViewController.h:

#import <UIKit/UIKit.h>
@protocol AddFoodViewControllerDelegate;

@interface AddFoodViewController : UITableViewController <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITextField *foodNameInput;
@property (weak, nonatomic) IBOutlet UITextField *caloriesInput;
@property (weak, nonatomic) id <AddFoodViewControllerDelegate> delegate;

- (IBAction)save:(id)sender;
- (IBAction)cancel:(id)sender;

@end

@protocol AddFoodViewControllerDelegate <NSObject>
- (void)addFoodViewControllerDidCancel:(AddFoodViewController *)controller;
- (void)addFoodViewControllerDidSave:(AddFoodViewController *)controller name:(NSString *)name calories:(NSNumber *)calories;
@end

AddFoodViewController.m:

#import "AddFoodViewController.h"

@implementation AddFoodViewController
@synthesize foodNameInput;
@synthesize caloriesInput;
@synthesize delegate = _delegate;

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

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

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

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    if ((textField == self.foodNameInput) || (textField == self.caloriesInput )) {
        [textField resignFirstResponder];
    }
    return YES;
}

- (IBAction)save:(id)sender {
    int caloriesInt = [self.caloriesInput.text intValue];
    NSNumber *caloriesNum = [NSNumber numberWithInt:caloriesInt];
    [[self delegate] addFoodViewControllerDidSave:self name:self.foodNameInput.text calories:caloriesNum];
}

- (IBAction)cancel:(id)sender {
    [[self delegate] addFoodViewControllerDidCancel:self];
}
@end

FoodCatalogViewController.m (код протокола AddFoodViewControllerDelegate для добавления в базу данных):

- (void)addFoodViewControllerDidCancel:(AddFoodViewController *)controller {
    [self dismissViewControllerAnimated:YES completion:NULL];
}

- (void)addFoodViewControllerDidSave:(AddFoodViewController *)controller name:(NSString *)name calories:(NSNumber *)calories {
    if ([name length]) {
        [self addFoodWithName:name calories:calories];
        [[self tableView] reloadData];
    }
    [self dismissModalViewControllerAnimated:YES];
}

- (void) addFoodWithName:(NSString *)name calories:(NSNumber *)calories {
    // Create a new instance of the entity managed by the fetched results controller.
    NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
    NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
    NSLog(@"entity name is %@", [entity name]);
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

    // If appropriate, configure the new managed object.
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
    [newManagedObject setValue:name forKey:@"name"];
    [newManagedObject setValue:calories forKey:@"calories"];
    CFUUIDRef uuidRef = CFUUIDCreate(NULL);
    CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
    NSString* uuid = [NSString stringWithString:(__bridge NSString *)uuidStringRef];
    [newManagedObject setValue:uuid forKey:@"uuid"];

    // Save the context.
    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }   
}

Подробнее отладочная информация
Странно - похоже, что fetchedResultsController не работает должным образом в FoodCatalogViewController, даже несмотря на то, что managedObjectContext, кажется, работает ... Вот модифицированная функция fetchedResultsController из FoodCatalogViewController.m с некоторыми отладочными операторами NSLog и заменой self.fetchedResultsController, поскольку __f является Интересно, это вызвало проблему?)

Вот вывод из функции fetchedResultsController, вызываемой NSLog:

2012-01-29 10:22:21.118 UltraTrack[19294:fb03] Result: (
    "<Food: 0x6e651b0> (entity: Food; id: 0x6e64630 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p1> ; data: <fault>)",
    "<Food: 0x6e653e0> (entity: Food; id: 0x6e61870 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p3> ; data: <fault>)",
    "<Food: 0x6e65450> (entity: Food; id: 0x6e64420 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p4> ; data: <fault>)",
    "<Food: 0x6e654c0> (entity: Food; id: 0x6e64430 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p5> ; data: <fault>)",
    "<Food: 0x6e65530> (entity: Food; id: 0x6e64e80 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p2> ; data: <fault>)",
    "<Food: 0x6e655b0> (entity: Food; id: 0x6e64e90 <x-coredata://8A10B827-9F10-4760-934C-0061A982B73C/Food/p6> ; data: <fault>)"
)
2012-01-29 10:22:21.907 UltraTrack[19294:fb03] Number or objects: 6

А вот модифицированная функция fetchedResultsController:

- (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:@"Food" 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 arrayWithObjects: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;
    __fetchedResultsController = aFetchedResultsController;

    NSArray *result = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
    NSLog(@"Result: %@", result);

    NSError *error = nil;
    if (![__fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    NSLog(@"Number or objects: %d", [__fetchedResultsController.fetchedObjects count]);

    return __fetchedResultsController;
}

Кто-то предположил, что проблема заключалась в разделах, поэтому я жестко запрограммировал numberOfSectionsInTableView, чтобы вернуть 1, а затем первый объект из fetchResults, похоже, обрабатывается правильно, но я получаю следующее исключение:

2012-01-29 10:29:27.296 UltraTrack[19370:fb03] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 1 in section at index 0'

Если я жестко закодировал числоOfRowsInSection и вернул 1, то первый объект из моей базы данных правильно отображается в табличном представлении. В чем может быть проблема с информацией о разделах в fetchedResultsController? Могли ли я что-то неправильно настроить в раскадровке для табличного представления по разделам?

Вот две функции табличного представления, в которых я попробовал жесткое кодирование:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
    //NSLog(@"Number of sections in table view is %d", [[self.fetchedResultsController sections] count]);
    //return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSLog(@"Number or objects: %d", [self.fetchedResultsController.fetchedObjects count]);
    // If I return 1, the object is displayed correctly, if I return count, I get the exception
    //return 1;
    return [self.fetchedResultsController.fetchedObjects count];

    // Return the number of rows in the section.
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    NSLog(@"Number of rows being returned is %d", [sectionInfo numberOfObjects]);
    return [sectionInfo numberOfObjects];
}

1 Ответ

2 голосов
/ 29 января 2012

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

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

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

Если это все еще возвращает ноль строк, проверьте ваш fetchedObjects.count в методе получения fetchedResultsController.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...