iOS Core Data Predicate 用于根据相关数据进行过滤

Posted

技术标签:

【中文标题】iOS Core Data Predicate 用于根据相关数据进行过滤【英文标题】:iOS Core Data Predicate for filtering based on related data 【发布时间】:2014-07-03 23:09:47 【问题描述】:

好吧,我是一个谓词菜鸟。他们对我来说只是陌生的。

关于应用:

我有一个处理游戏比赛的应用。有玩家、签到和比赛的实体。 这个想法是将玩家添加到应用程序中,然后可以签到进行比赛,并存储比赛结果。

关系:

玩家>签到(每个玩家可以在不同日期多次签到)

来自:玩家实体 关系:playerCheckins 逆向:checkInPlayer 目标:签入实体

球员>比赛(每场比赛可以有两名球员,球员每次比赛可以有多场比赛)

来自:玩家实体 关系:playerMatches 逆向:matchPlayers 目标:匹配实体

我有一个共享集合视图,其中列出了应用程序中的所有玩家。它在玩家签到以及将他们添加到新的比赛条目时使用。到目前为止,这一切都很好。

我想做什么:

我希望玩家收藏视图根据他们的签到状态过滤列出的玩家。例如,在签到新玩家时,玩家集合视图应该只显示当天尚未签到的玩家。 (CheckIns 对每个条目都有一个 date 属性)此外,在将球员添加到比赛时,集合视图应该只显示当天已经签到的球员。

当我以模态方式加载集合视图时,我计划将一个 NSString 属性添加到使用谓词文本设置的玩家集合视图中。这样,我可以根据我是否从匹配项中调用集合视图并分别签入视图来更改谓词。

这可能与我正在尝试做的事情有关吗?这些谓词字符串会是什么样子?

谢谢!

更新

我应该更清楚。我正在使用 fetchedresultscontroller 让玩家进入 collectionview,所以我正在寻找要在 fetch 请求中使用的谓词...

添加代码...

我的 CollectionViewController:

PlayersCollectionViewController.h
#import "CoreCollectionViewController.h"
#import "Player.h"

@protocol PlayersCollectionViewControllerDelegate;


@interface PlayersCollectionViewController : CoreCollectionViewController <NSFetchedResultsControllerDelegate>

@property (nonatomic, assign) id <PlayersCollectionViewControllerDelegate> delegate;
@property (nonatomic, strong) NSString *titleText;
@property (nonatomic, strong) NSString *predicate;

- (IBAction)cancel:(UIBarButtonItem *)sender;
- (IBAction)done:(UIBarButtonItem *)sender;

@end

@protocol PlayersCollectionViewControllerDelegate <NSObject>

@optional
-(void)ViewController:(UIViewController *)sender
     didSelectPlayer:(Player *)selectedPlayer;

@optional
-(void)ViewController:(UIViewController *)sender
      didSelectPlayer1:(Player *)selectedPlayer1;

@optional
-(void)ViewController:(UIViewController *)sender
      didSelectPlayer2:(Player *)selectedPlayer2;

@end

...以及实现:

#import "PlayersCollectionViewController.h"
#import "AppDelegate.h"
#import "PlayerCollectionViewCell.h"

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

@implementation PlayersCollectionViewController
@synthesize titleText, predicate;

static NSString * const reuseIdentifier = @"Cell";

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) 
        // Custom initialization
    
    return self;


-(NSManagedObjectContext*)managedObjectContext
    return [(AppDelegate*)[[UIApplication sharedApplication]delegate]managedObjectContext];


- (void)viewDidLoad

    [super viewDidLoad];

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

    // Register cell classes
    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];

    // Do any additional setup after loading the view.
    NSError *error = nil;
    if (![[self fetchedResultsController]performFetch:&error]) 
        NSLog(@"Error: %@", error);
        abort();
    


-(void)viewWillAppear:(BOOL)animated

    [self setTitle:titleText];
    [self.collectionView reloadData];


- (void)didReceiveMemoryWarning

    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.


/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.

*/

#pragma mark <UICollectionViewDataSource>

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

    return [[self.fetchedResultsController sections]count];



- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

    return [[self.fetchedResultsController fetchedObjects]count];


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath


    PlayerCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlayerCollCell" forIndexPath:indexPath];
    Player *player = [self.fetchedResultsController objectAtIndexPath:indexPath];

    // Configure the cell

    if (player.playerImage) 
        cell.playerImageView.image = [UIImage imageWithContentsOfFile:player.playerImageSmall];
    

    [cell.playerNameLabel setText:player.firstName];

    return cell;


-(BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath

    return YES;


-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath


    PlayerCollectionViewCell *selectedPlayerCell = (PlayerCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];

    [collectionView dequeueReusableCellWithReuseIdentifier:@"PlayerCollCell" forIndexPath:indexPath];

    selectedPlayerCell.backgroundColor = [UIColor lightGrayColor];

    Player *selectedPlayer = [self.fetchedResultsController objectAtIndexPath:indexPath];

    NSLog(@"Selected player %@", selectedPlayer.firstName);

    if ([self.title isEqualToString:@"Select Player 1"]) 
        [self.delegate ViewController:self didSelectPlayer1:selectedPlayer];
     else if ([self.title isEqualToString:@"Select Player 2"]) 
        [self.delegate ViewController:self didSelectPlayer2:selectedPlayer];
     else if ([self.title isEqualToString:@"Select Player"]) 
        [self.delegate ViewController:self didSelectPlayer:selectedPlayer];
    

    [self dismissViewControllerAnimated:YES completion:nil];


#pragma mark - ViewController methods
- (IBAction)cancel:(UIBarButtonItem *)sender 
    [self dismissViewControllerAnimated:YES completion:nil];


- (IBAction)done:(UIBarButtonItem *)sender  //not sure if this method and UI are needed...
    [self dismissViewControllerAnimated:YES completion:nil];



#pragma mark - Fetched Results Controller Section
-(NSFetchedResultsController*) fetchedResultsController

    if (_fetchedResultsController !=nil) 
        return _fetchedResultsController;
    

    NSFetchRequest *fetchedRequest = [[NSFetchRequest alloc]init];

    NSManagedObjectContext *context = [self managedObjectContext];

    NSEntityDescription *entity =[NSEntityDescription entityForName:@"Player" inManagedObjectContext:context];

    [fetchedRequest setEntity:entity];

    NSSortDescriptor *lastNameSortDescriptor = [[NSSortDescriptor alloc]initWithKey:@"lastName" ascending:YES];
    NSSortDescriptor *firsttNameSortDescriptor = [[NSSortDescriptor alloc]initWithKey:@"firstName" ascending:YES];

    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:lastNameSortDescriptor, firsttNameSortDescriptor, nil];

    fetchedRequest.sortDescriptors = sortDescriptors;


    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchedRequest
                                                                    managedObjectContext:context
                                                                      sectionNameKeyPath:nil
                                                                               cacheName:nil];

    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;



#pragma mark - Fetched Results Controller Delegates
/*
 -(void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
 [self.collectionView beginUpdates];
 

 -(void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
 [self.collectionView endUpdates];
 
 */

-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath 

    UICollectionView *collectionView = self.collectionView;

    switch (type) 
        case NSFetchedResultsChangeInsert:
            [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObjects:newIndexPath, nil]];
            break;

        case NSFetchedResultsChangeDelete: [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]];
            break;

        case NSFetchedResultsChangeUpdate: 
            PlayerCollectionViewCell *selectedPlayerCell = (PlayerCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];

            //selectedPlayerCell.backgroundColor = [UIColor lightGrayColor];

            Player *selectedPlayer = [self.fetchedResultsController objectAtIndexPath:indexPath];

            if (selectedPlayer.playerImage) 
                selectedPlayerCell.playerImageView.image = [UIImage imageWithContentsOfFile:selectedPlayer.playerImageSmall];
            

            [selectedPlayerCell.playerNameLabel setText:selectedPlayer.firstName];

        
            break;

        case NSFetchedResultsChangeMove: [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]];
            [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObjects:newIndexPath, nil]];
            break;

    



@end

【问题讨论】:

【参考方案1】:

暂时离开您的视图控制器并获取结果控制器。您的谓词属于模型层,而不是控制器或视图层。 NSFetchRequest 或 NSFetchedResultsController 的谓词没有什么特别之处。它仍然只是一个 NSPredicate。

为什么要在模型层这样做?您基于“当天签到”的过滤问题与视图或向用户显示的内容无关。这纯粹是一个数据问题。所以在模型层解决它(奖励:现在很容易为此获取编写单元测试)。

如果你稍微作弊并使用你对数据的了解,你可以让这更容易。使 Player>CheckIn 关系成为一个有序的关系(旁注:Core Data 中的实体名称是单数的,就像类名一样,尽管一对多关系是复数的)。现在很容易获得玩家最近的签到:thePlayer.checkins[0],签到时间为[thePlayer.checkins[0] timeOfEvent]。只需确保将 CheckIn 添加到 Player 时,将其放在索引 0 处。

不过,您可以通过对数据模型进行一些非规范化来简化它。保持从PlayerCheckIn 的一对多关系。但是在PlayertimeOfLastCheckIn 上添加一个属性。确保在添加签入时更新timeOfLastCheckIn; “说一次”意味着您应该将这对步骤包装在一个方法中,并始终调用该方法来添加 CheckIn。

现在到 Xcode 数据建模器。使用替换变量在Player 上创建一个新的获取请求。在下拉列表中选择表达式并输入 timeOfLastCheckIn &gt; $fromdate。在Player (+playersCheckingInAfterDate:context:) 上编写一个方法来执行该获取请求,接受两个参数,NSManagedObjectContext 和一个NSDate,并返回一个Players 数组。您将使用fetchRequestFromTemplateWithName:substitutionVariables: 实例化一个获取请求,用对应于本地午夜的NSDate 替换$fromdate。如果您愿意,可以添加一个包装器方法来计算本地午夜的时间并将其传递给+playersCheckingInAfterDate:context:。您可以在一组类似的方法/获取模板中将&gt; 切换为&lt;,以查找今天尚未签到的玩家。

【讨论】:

谢谢。这是一个很好的建议。我陷入了对 MVC 的迷茫和迷失...在我的“必须学习”列表中......看起来我必须重新编写一些代码来设置视图的数据源,但这听起来是一个更好的方法。 实际上,我还将为 isCheckedIn 添加一个布尔值,并且不必处理日期。我正在其他地方管理签到状态,每次见面后都可以清除它...【参考方案2】:

我不确定您的签入状态究竟是如何工作的。假设它是 BOOL,请执行以下操作:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"checkedInStatus == 1"];

【讨论】:

感谢您的建议。集合视图的数据源使用的是 fetchedresults 控制器,而不是数组。所以我应该澄清一下,我正在寻找一个用于获取请求的谓词。 @user3369484 您应该添加更多信息。你的代码目前是什么样子的? .h 和 .m 为集合视图添加...核心数据堆栈是标准的并且在 AppDelegate.h/.m..

以上是关于iOS Core Data Predicate 用于根据相关数据进行过滤的主要内容,如果未能解决你的问题,请参考以下文章

Core Data NSPredicate for (select ... where not in (select))

用于一对多的 iOS Core 数据谓词

保存在 Core Data 中的 iOS 数据在启动后无法保存

Core Data-备用

深入浅出学Spring Data JPA toPredicate Predicate[] p = new Predicate[list.size()]; query.where(cb.and

IOS/Core-Data:添加多对多关系