Как подготовить ForSegue с помощью NSFetchedResults к другому табличному представлению с целью c

Я знаю, что, вероятно, есть очень простое решение. Я использую пример Apple iphoneCoreDataRecipes. Я пытаюсь перейти от исходной таблицы (RecipeTypeTableViewController) типов RecipeTypes (например, напитков, десертов, блюд) к следующему табличному представлению со списком рецептов этого типа (RecipeListTableViewController). Я создал новый класс RecipeType, думая, что могу просто использовать его для возврата рецептов выбранного RecipeType. Пока лучшее, что я могу получить, это разрешить выбор, но он показывает один и тот же список всех рецептов. Исходный код начинается с RecipeListTableViewController, использующего NSFetchedResults для отображения всех рецептов. Теперь у меня есть начальное представление RecipeTypes, но я не уверен, как передать выбранный тип в RecipeListTableViewController. Мне нужно изменить RecipeListTableViewController, чтобы показать список выбранного RecipeType, но я не уверен, как это реализовать. Я думаю, что в основном хочу передать NSMutableArray fetchedresults в другое табличное представление, но не уверен, в каком направлении двигаться. И я не хочу портить другие рабочие действия в RecipeListTableViewController, т.е. поиск и редактирование рецептов. Вот изображения действий таблицы [RecipeTypeTableView][1] и [RecipeListTableview][2]. Заранее благодарим вас за любые предложения.

Удалил эти

Файл: RecipeType.h

Файл: RecipeType.m

Я использую тип NSManagedObject * из моего класса рецептов NSManagedObject для создания начального представления типов рецептов (закусок, блюд и т. д.) в RecipeTypeTableViewController —

Файл: Recipe.h

@interface Recipe : NSManagedObject
@property (nonatomic, strong) NSManagedObject *type;

Файл: RecipeTypeTableViewController.h

//  RecipeTypeTableViewController.h
//  Recipes
#import <UIKit/UIKit.h>
@interface RecipeTypeTableViewController : UITableViewController <NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong) NSManagedObject *type;
@end

Файл: RecipeTypeTableViewController.m — часть подготовки к переходу

#import "RecipeTypeTableViewController.h"
#import "Recipe.h"
#import "RecipeListTableViewController.h"
@interface RecipeTypeTableViewController () <NSFetchedResultsControllerDelegate>
@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
@end

@implementation RecipeTypeTableViewController

static NSString *MyIdentifier = @"showRecipes";

- (void)viewDidLoad {
[super viewDidLoad];
// register this class for our cell to this table view under the specified identifier 'ARecipeType'
self.title = @"Category";
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"ARecipeType"];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangePreferredContentSize:)name:UIContentSizeCategoryDidChangeNotification object:nil];
// 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;
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {

    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    //abort();
}
}

- (void)viewWillAppear:(BOOL)animated {

[super viewWillAppear:animated];
self.title = @"Category";

}

- (void)didChangePreferredContentSize:(NSNotification *)notification
{
[self.tableView reloadData];
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
}
#pragma mark - Table view data source

//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//#warning Incomplete implementation, return the number of sections
//return [[self.recipeTypeFetchedResultsController sections] count];

//    return 1;
//}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Number of rows is the number of recipe types
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
//return self.recipeTypes.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ARecipeType" forIndexPath:indexPath];

// Configure the cell
NSManagedObject *recipeType = [self.recipeTypes objectAtIndex:indexPath.row];
cell.textLabel.text = [recipeType valueForKey:@"name"]; 
return cell;
}

#pragma mark - Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {

// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"RecipeType" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entity);

    // 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:@"Recipe"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;


    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }
}

return _fetchedResultsController;
}

#pragma mark - Navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:@"showRecipes"]) {
    NSLog(@"Setting RecipeType for the RecipeListTableViewController");
    NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    Recipe *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath];
    RecipeListTableViewController *recipeListViewController = segue.destinationViewController;
    recipeListViewController.type = selectedType;
    recipeListViewController.managedObjectContext = self.managedObjectContext;
}
}

Файл: RecipeListTableViewController.h — часть NSPredicate и кеш FRC

#import <UIKit/UIKit.h>
#import "RecipeAddViewController.h"

@interface RecipeListTableViewController : UITableViewController <RecipeAddDelegate, NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong) NSManagedObject *type;

Файл: RecipeListTableViewController.m

#import "RecipeListTableViewController.h"
#import "RecipeDetailViewController.h"
#import "Recipe.h"
#import "RecipeTableViewCell.h"
#import "Recipe+Extensions.h"
#import "TypeSelectionViewController.h"
#import "IngredientDetailViewController.h"
#import "Ingredient.h"
#import "WhereViewController.h"
#import "FavoriteListTableViewController.h"
#import "RecipeTypeTableViewController.h"
#import "RecipeType.h"


@interface RecipeListTableViewController () <NSFetchedResultsControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating>

@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, strong) NSArray *filteredList;
@property (nonatomic, strong) NSFetchRequest *searchFetchRequest;
@property (nonatomic, strong) UISearchController *searchController;

typedef NS_ENUM(NSInteger, RecipesSearchScope)
{
searchScopeRecipe = 0,
searchScopeIngredients = 1
};


@end

@implementation RecipeListTableViewController


#pragma mark - Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {

// Set up the fetched results controller if needed.
if (_fetchedResultsController == nil) {
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Recipe" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

[NSFetchedResultsController deleteCacheWithName:@"Recipe"];

    // Create predicate
    //if (self.type) {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"type == %@", self.type];
    [fetchRequest setPredicate:predicate];
    //}


    // 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:@"Recipe"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }
}

return _fetchedResultsController;
}

/

Когда у меня кеш FRC установлен на «Рецепт». Он падает. Это показало эту подсказку для просмотра кеша CoreData: FATAL ERROR: постоянный кеш информации о разделе не соответствует текущей конфигурации. Вы незаконно изменили запрос выборки NSFetchedResultsController, его предикат или описание сортировки...

Если я установлю кеш на ноль или добавлю [NSFetchedResultsController deleteCacheWithName:@"Recipe"]; до установки предиката все работает так, как ожидалось. У меня есть этот кеш в начальном контроллере представления и втором контроллере представления. Может быть, в этом проблема - мне нужен кеш только в начальном контроллере представления?




Ответы (1)


Во-первых, я думаю, что ваша модель данных нуждается в небольшой доработке. Предположительно, с каждым RecipeType может быть связано много Recipes. Таким образом, отношение от RecipeType к Recipe должно быть ко многим. Если вы измените это в редакторе модели данных и перегенерируете классы модели, у вас должен получиться класс RecipeType, который выглядит примерно так:

@class Recipe;

@interface RecipeType : NSManagedObject
// not sure what purpose this property serves; it might now be superfluous...
@property (nonatomic, strong) NSManagedObject *type;

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSSet<Recipe *> *recipes;

@end 

Я пока предположу, что каждый Recipe может принадлежать только одному RecipeType. Таким образом, обратное отношение от Recipe к RecipeType должно быть к одному, и поэтому класс Recipe будет иметь свойство:

*property (nonatomic, strong) RecipeType *type;

Затем вы хотите, чтобы ваш RecipeListTableViewController отображал только те Recipes, которые относятся к соответствующему RecipeType. Для этого вам нужно добавить предикат к полученному контроллеру результатов. В методе fetchedResultsController добавьте:

if (self.recipeType) {
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"type == %@", self.recipeType];
}

(Вам также нужно будет изменить свой поиск, чтобы аналогичным образом ограничить поиск релевантным RecipeType). В RecipeTypeTableViewContoller вашему prepareForSegue нужно только правильно передать RecipeType:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {    
    if ([[segue identifier] isEqualToString:@"showRecipes"]) {
        NSLog(@"Setting RecipeType for the RecipeListTableViewController");
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        RecipeType *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath];
        RecipeListTableViewController *recipeListViewController = segue.destinationViewController;
        recipeListViewController.recipeType = selectedType;
        recipeListViewController.managedObjectContext = self.managedObjectContext;
    }
}

Всякий раз, когда вы добавляете новый Recipe, вам нужно будет назначить его правильному RecipeType. Итак, в RecipeListViewController измените свой prepareForSegue, чтобы установить отношения:

...
else if ([segue.identifier isEqualToString:kAddRecipeSegueID]) {
    // add a recipe
    //
    Recipe *newRecipe = [NSEntityDescription insertNewObjectForEntityForName:@"Recipe" inManagedObjectContext:self.managedObjectContext];
    newRecipe.type = self.recipeType;

    UINavigationController *navController = segue.destinationViewController;
    RecipeAddViewController *addController = (RecipeAddViewController *)navController.topViewController;
    addController.delegate = self;  // do didAddRecipe delegate method is called when cancel or save are tapped
    addController.recipe = newRecipe;
}
person pbasdf    schedule 28.08.2016
comment
Ваш ответ получает пятерку за точность. Обнаружение верхнего кода. Спасибо, это работает. Но тогда это не так, и я принимаю это не потому, что вы правы/не правы. - person Chuck; 29.08.2016
comment
Я перенастроил RecipeType. Удаление подкласса и использование существующего *типа NSManagedObject из класса рецептов. Я получаю список типов рецептов, и когда я выбираю «Закуска», он показывает 2 рецепта закусок. Потрясающий. - person Chuck; 29.08.2016
comment
Но затем, когда я возвращаюсь к списку категорий и выбираю другой тип, он вылетает. Я перезапускаю симулятор приложения и выбираю другой тип, и он передает правильный список. Но я выбираю тип, у которого нет рецептов, он показывает пустую таблицу, а если я вернусь, чтобы выбрать другой тип, даже если есть несколько рецептов, он дает пустую таблицу. Извините за многократный комментарий - вернитесь счастливым. - person Chuck; 29.08.2016
comment
[код] @interface Рецепт: NSManagedObject [код] @property (неатомарный, сильный) NSManagedObject *type; [code]@end [code]// Создать предикат if (self.type) { fetchRequest.predicate = [NSPredicate predicateWithFormat:@type == %@, self.type]; //[fetchRequest setPredicate:predicate]; }[код] - person Chuck; 29.08.2016
comment
[code]- (void)prepareForSegue было 100% Спасибо, если ([[идентификатор перехода] isEqualToString:@showRecipes]) { NSLog(@Setting RecipeType для RecipeListTableViewController); NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; Рецепт *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath]; RecipeListTableViewController *recipeListViewController = segue.destinationViewController; recipeListViewController.type = selectedType; recipeListViewController.managedObjectContext = self.managedObjectContext;[код] - person Chuck; 29.08.2016
comment
@Chuck Не уверен, что понимаю твои комментарии. Вы уладили проблемы? Дайте мне знать, если есть что-то выдающееся. - person pbasdf; 29.08.2016
comment
Извините, да, я знаю, что пошел кра-кра с комментариями. Ваш ответ на переход и предикат выглядят точными. Спасибо. После повторной настройки класс рецептов имеет тип NSManaged Object *, который я использовал, и исключил все ссылки на подкласс RecipeType. Новая конфигурация показывает список типов, и когда я выбираю один из них, отображаются рецепты правильного типа. Но когда я возвращаюсь к списку типов и выбираю другой тип, происходит сбой. Я могу перезапустить симулятор и выбрать другой тип, и он вернется правильно. - person Chuck; 30.08.2016
comment
Но если я выбираю тип, у которого нет рецептов, он дает пустую таблицу, как и ожидалось, и если я возвращаюсь к списку типов и выбираю другой тип, он возвращает все остальные типы как пустые. Так что похоже, что это работает, но только один раз. Может быть, таблицу типов рецептов нужно сбросить при возврате? Любые предложения Еще раз спасибо. - person Chuck; 30.08.2016
comment
Не знаю, куда теперь поставить @property (nonatomic, strong) NSSet <Recipe *> *recipes;. Он у меня есть на RecipeTypeTVC.h Также @class Recipe здесь. И @dynamic на RecipeTypeTVC.m. И не знаете, где это когда-либо используется? - person Chuck; 30.08.2016
comment
этап подготовки теперь получает тип Recipe *selectedType = [self.fetchedResultsController objectAtIndexPath:indexPath]; RecipeListTableViewController *recipeListViewController = segue.destinationViewController; recipeListViewController.type = selectedType; recipeListViewController.managedObjectContext = self.managedObjectContext; - person Chuck; 30.08.2016
comment
ОК, установил для моего NS Cache значение nil - теперь работает как шарм `NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest manageObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];' - person Chuck; 30.08.2016
comment
так что затем приводит к следующему, как сохранить кеш и реализовать deleteCacheWithName ? - person Chuck; 30.08.2016
comment
@Chuck Определение свойства recipes должно быть в .h для подкласса объекта Type, но если вы не используете подкласс, в этом определении нет необходимости. Вы должны убедиться, что связь между Type и Recipe настроена правильно как ко многим. Не уверен, почему кеш FRC должен влиять на вещи; можешь показать ошибку от краша? Кроме того, лучше отредактировать свой вопрос и добавить туда код/ошибки вместо добавления комментариев. - person pbasdf; 30.08.2016
comment
Вау! Спасибо, что дали такой добрый совет для постера во второй раз. Я действительно боялся публиковать, опасаясь, что кто-то просто посмеется и скажет мне изучить Apple dox. Вы были очень милы. Я переработаю ошибку и вскоре опубликую ее, а также покажу изменения кода в моем вопросе в соответствии с вашими предложениями. Только еще один момент здесь. Нужно ли, чтобы кеши FRC находились в обоих классах табличных представлений с типами и рецептами? Или мне нужно только ссылаться на кеш в первой таблице? И нужен ли вообще кеш? - person Chuck; 31.08.2016
comment
@Chuck Кэш FRC содержит сведения о том, какие объекты находятся в каких разделах, поэтому вам не следует использовать одно и то же имя кеша для отдельных FRC (если только они не показывают точно одну и ту же информацию). В вашем случае они показывают совершенно разную информацию, поэтому, если вы хотите, чтобы оба использовали кеш, используйте разные имена кеша. Но кеши не нужны. Они ускоряют работу, если у вас большое количество объектов, но я сомневаюсь, что они будут иметь какое-либо заметное значение в вашем случае. - person pbasdf; 31.08.2016