使用 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的主要内容,如果未能解决你的问题,请参考以下文章
如何使用两个 NSPredicates 获取请求 - Swift 2
在 Swift 中将两个 NSPredicates 与独立的 NSSortDescriptors 结合起来