如何使用 NSFetchedResults prepareForSegue 使用 objective c 向下钻取到另一个表视图

How to prepareForSegue with NSFetchedResults drilldown to another tableview with objective c

我知道可能有一个非常简单的解决方案。我正在使用苹果 iphoneCoreDataRecipes 示例。我正在尝试从最初的 table(RecipeTypeTableViewController) RecipeTypes(即饮料、甜点、主菜)向下钻取到下一个 table 视图,其中列出了这些类型的 Recipes (RecipeListTableViewController)。我创建了一个新的 class RecipeType,认为我可以将它用于 return 所选 RecipeType 的食谱。到目前为止,我能得到的最好的是允许选择,但它显示了所有食谱的相同列表。原始代码以使用 NSFetchedResults 显示所有食谱的 RecipeListTableViewController 开始。现在我有了 RecipeTypes 的初始视图,但我不确定如何将所选类型传递给 RecipeListTableViewController。我需要更改 RecipeListTableViewController 以显示所选 RecipeType 的列表,但我不确定如何实现它。我想我基本上想将一个 NSMutableArray of fetchedresults 传递给另一个 tableview 但不确定要采取哪个方向。而且我不想搞乱 RecipeListTableViewController 中的其他工作操作,即搜索和编辑食谱。这是到目前为止 table 操作的图像 [RecipeTypeTableView][1] 和 [RecipeListTableview][2]。预先感谢您的任何建议。

删除了这些

文件:RecipeType.h

文件:RecipeType.m

我使用食谱中的 NSManagedObject *type Class NSManagedObject 在 RecipeTypeTableViewController 上创建食谱类型(Appetizer、Bev 等)的初始视图 -

文件: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 缓存设置为 "Recipe" 时。它崩溃了。它显示了查看缓存 CoreData 的线索:致命错误:部分信息的持久缓存与当前配置不匹配。您非法改变了 NSFetchedResultsController 的获取请求、它的谓词或它的排序描述...

如果我设置缓存为nil,或者添加[NSFetchedResultsController deleteCacheWithName:@"Recipe"];在谓词集之前,事情按预期进行。我在初始视图控制器和第二个视图控制器中有这个缓存。也许这就是问题所在——我只需要在初始视图控制器中缓存?

首先,我认为您的数据模型需要改进一下。每个 RecipeType 大概可以关联许多 Recipes。所以从RecipeTypeRecipe的关系应该是对多。如果您在数据模型编辑器中更改它,并重新生成模型 classes,您应该有一个 RecipeType class 看起来像这样:

@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。所以从 RecipeRecipeType 的逆关系应该是 到一个 ,并且 Recipe class 因此会有一个 属性:

*property (nonatomic, strong) RecipeType *type;

接下来,您希望 RecipeListTableViewController 仅显示与相关 RecipeType 相关的 Recipes。为此,您需要向获取的结果控制器添加一个谓词。在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;
}