使用 NSPredicates 过滤 NSMutableArray

Posted

技术标签:

【中文标题】使用 NSPredicates 过滤 NSMutableArray【英文标题】:Filtering NSMutableArray with NSPredicates 【发布时间】:2013-02-27 08:49:10 【问题描述】:

我在 *** 上查看了一些关于 NSPredicates 的主题,尽管它们都指向正确的方向,但我一定遗漏了一些重要的东西。

我有一个包含产品列表的 NSMutableArray。 每个产品都有多个属性,例如品牌、类别和类型。

现在我希望我的用户能够使用 NSPredicates 过滤该 NSMutableArray,因为如果任何选定的过滤器为空,则不应使用该过滤器。

但是,反过来,例如,如果所有过滤器都打开:过滤器具有类别 B 和类型 C 的品牌 A,它应该只显示具有类别 B 和类型 C 的品牌 A。

我是否应该取消选择 Cat B,它会过滤品牌 A 和 Type C。

我写了一些代码,但它主要返回一个空的 NSMutableArray,所以我猜我的 NSPredicates 是关闭的。

我还发现在运行谓词之前我需要默认为“所有产品”NSMutableArray,否则在选择新的过滤器选项时它会过滤已经过滤的数组。我应该使用带有一些布尔魔法的多个数组,还是可以使用 NSPredicates 解决这个问题?

这是我的代码:

-(void)filterTable

    NSPredicate *brandPredicate;
    NSPredicate *categoryPredicate;
    NSMutableArray *compoundPredicateArray;

    if( ![self.selectedBrand isEqual: @"Show All Brands"] || !(self.currentBrand == NULL))
    
    brandPredicate = [NSPredicate predicateWithFormat:@"brand CONTAINS[cd] %@",self.currentBrand];
    compoundPredicateArray = [ NSMutableArray arrayWithObject: brandPredicate ];
    

    if( ![self.currentCategory isEqual: @"Show All Categories"] || !(self.currentCategory == NULL)) 
    
        categoryPredicate = [NSPredicate predicateWithFormat:@"category CONTAINS[cd] %@",self.currentCategory];
        [ compoundPredicateArray addObject: categoryPredicate];
    

    NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
                              compoundPredicateArray ];

    [self.tableData filterUsingPredicate:predicate];
    [self.popNTwinBee dismissPopoverAnimated:YES]; // PopoverController
    [self.tableView reloadData];

【问题讨论】:

【参考方案1】:

您的代码中有几个概念性错误。

首先,您应该在声明时初始化 NSMutableArray 谓词:

NSMutableArray *compoundPredicateArray = [NSMutableArray array];

现在你只在你的第一个 if() 中实例化它,所以如果没有设置品牌过滤器,可变数组甚至不会被实例化,所以稍后添加对象(例如在第二个过滤 if() ) 无效,复合谓词创建为空。 在您的第一个 if() 中,您将拥有:

[compoundPredicateArray addObject:brandPredicate];

您的第二个问题是,正如您正确想象的那样,当您使用 filterUsingPredicate 时,您正在过滤之前已经过滤的内容。

您应该做的是始终将未过滤的数据保存在 NSArray 中,并在其上使用 filteredArrayUsingPredicate 方法来检索新过滤的 NSArray,您将使用它来显示数据。

【讨论】:

谢谢!我会试试看,然后告诉你进展如何。 那仍然没有达到应有的效果。我认为我的 if 语句已关闭,因为当 if 语句应该为假时,它们不会被跳过。我把isEqual: 改成了isEqualToString: 顺便说一句。 isEqualToString 是正确的方法。但是在 if 语句中使用 OR 而不是 AND 可能是错误的。作为第二条评论,我建议将对象与 nil 和没有 NULL 进行比较:第一个用于指向对象的指针,第二个用于非对象的东西。 感谢您的提示!我做一个 OR 因为有一个空白品牌或有字符串 @"Show All Brands" 基本上意味着跳过品牌过滤而只使用类别过滤 如果你想应用过滤器,这就是你应该放 AND 的原因!换句话说,当品牌不为空(不为零)且不等于@“显示所有品牌”时,您想要过滤。【参考方案2】:

好吧,我仔细查看了代码并想出了这个:

从this site 得到这个方便的小代码块。

NSArray+filter.h

#import <Foundation/Foundation.h>

@interface NSArray (Filter)
- (NSArray*)filter:(BOOL(^)(id elt))filterBlock;
@end

NSArray+filter.m

#import "NSArray+filter.h"

@implementation NSArray(Filter)
- (NSArray*)filter:(BOOL(^)(id elt))filterBlock
 // Create a new array
    id filteredArray = [NSMutableArray array]; // Collect elements matching the block condition
    for (id elt in self)
        if (filterBlock(elt))
        [filteredArray addObject:elt];
    return  filteredArray;

@end

并相应地编辑了我的方法。

TableViewController.m

- (void)filterTable 
    WebServiceStore *wss = [WebServiceStore sharedWebServiceStore];
    self.allProducts = [wss.allProductArray mutableCopy];
    NSArray *filteredOnBrand;
    NSArray *filteredOnCategory;
    NSArray *filteredOnCategoryAndBrand;

    if (![self.currentBrand isEqualToString:@"All Brands"] && !(self.currentBrand == nil)) 
    
        filteredOnBrand = [self.allProducts filter:^(id elt) 
        
            return [[elt brand] isEqualToString:self.currentBrand];
        ];
        [self.tableData removeAllObjects];
        [self.tableData addObjectsFromArray:filteredOnBrand];
    

    if ([self.currentBrand isEqualToString:@"All Brands"] || self.currentBrand == nil) 
    
        filteredOnBrand = [self.allProducts mutableCopy];
        [self.tableData removeAllObjects];
        [self.tableData addObjectsFromArray:filteredOnBrand];
    

    if (![self.currentCategory isEqualToString:@"All Categories"] && !(self.currentCategory == nil)) 
    
        filteredOnCategory = [self.allProducts filter:^(id elt) 
      
            return [[elt category] isEqualToString:self.currentCategory];
      ];
        [self.tableData removeAllObjects];
        [self.tableData addObjectsFromArray:filteredOnCategory];
    
    if (![self.currentCategory isEqualToString:@"All Categories"] && !(self.currentCategory == nil) && ![self.currentBrand isEqualToString:@"All Brands"] && !(self.currentBrand == nil)) 
        filteredOnBrand = [self.allProducts filter:^(id elt) 
            return [[elt brand] isEqualToString:self.currentBrand];
        ];
        filteredOnCategoryAndBrand = [filteredOnBrand filter:^(id elt) 
            return [[elt category] isEqualToString:self.currentCategory];
        ];
        [self.tableData removeAllObjects];
        [self.tableData addObjectsFromArray:filteredOnCategoryAndBrand];
    

当然,您还应该在之后重新加载表格数据,但我为此使用了自定义方法,我忽略了。

【讨论】:

这个解决方案与我在回答的评论中提出的解决方案有什么不同?我建议用 AND 更改 OR,而这正是您在自己的答案中所做的......

以上是关于使用 NSPredicates 过滤 NSMutableArray的主要内容,如果未能解决你的问题,请参考以下文章

使用 2 个 NSPredicates?

如何使用两个 NSPredicates 获取请求 - Swift 2

在 Swift 中将两个 NSPredicates 与独立的 NSSortDescriptors 结合起来

NSPredicates 和双对 n 关系

在 NSFetchRequest 上设置 2 个 NSPredicates

核心数据关系、NSPredicates 和 NSFetchedResultsController