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

我在 StackOverflow 上查看了一些关于 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];
}

【问题讨论】:

    标签: ios objective-c filter nsmutablearray nspredicate


    【解决方案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,而这正是您在自己的答案中所做的......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    相关资源
    最近更新 更多