【问题标题】:Filter my NSArray of NSDictionaries using several UIControls使用多个 UIControls 过滤我的 NSDictionaries NSArray
【发布时间】:2014-10-14 04:32:45
【问题描述】:

我有一个NSArrayNSDictionarys(字典包含几个键值对)。在我的故事板上,我有几个UISegmentedControls,在他们选择的事件中,我过滤掉了我的字典数组(很好用)。我将这些 SegmentedControls 的选定状态和值存储在实例变量中,这样我就可以判断它们是以前被选中还是第一次被选中。

我遇到的问题是尝试使用每个UISegmentControl 创建的多个谓词过滤这个字典数组。例如,如果我单击 SegmentControl 1,它会相应地过滤数组,如果我单击 SegmentControl 2,它会获取当前结果并过滤这些结果。但是,如果我从 SegmentControl 1 中选择另一个值,它不会返回任何值,因为所有这些值最初都被过滤掉了。由于我有 5 个这些段,我需要确保它可以过滤在我的 viewDidLoad 上生成的未过滤集(我的未过滤数据来自 JSON 源,并且在应用程序时存储一次,这更加突出了这一点)加载以防止它多次从 JSON 源中提取)。

我在尝试解决这个过滤器问题时遇到了麻烦。我不确定是不是因为整天看这个我的大脑糊涂了,还是因为我对 objc 的新手能力(我有很多 oop 程序经验,但这是我的第一个目标 c 应用程序)。我最初考虑过制作一个接受未过滤数组的函数/方法,以及所有选择的过滤器值以返回过滤后的数据,但我无法理解如果某些过滤器字段为 nil 会发生什么。解决这个问题的最佳方法是什么?这是一些代码:

@interface ViewController ()
{
    //Checks for UISegments
    Boolean isSegment1Selected;
    Boolean isSegment2Selected;
    Boolean isSegment3Selected;
    Boolean isSegment4Selected;

    //Instance FilterValues
    NSString *segment1Value;
    NSString *segment2Value;
    NSString *segment3Value;
    NSString *segment4Value;

    //Create arrays for dictionaries
    NSMutableArray *myArrayOfDictionaries;
    NSMutableArray *unfilteredArrayOfDictionaries;
    NSMutableArray *filteredArrayOfDictionaries;
}
@end

在我的 viewDidLoad() 上,我正在使用来自我的 JSON 源的数据填充 myArrayOfDictionaries

对于我用来过滤的每个UISegmentView,我都有 IBOutlet 事件。我给你看一个,因为除了名字之外它们几乎都一样:

/// UISegmentedControl Filters ///
- (IBAction)segment1:(UISegmentedControl *)sender {
    //    NSLog(isSegment1Selected ? @"YES" : @"NO");


    //Haven't used this if/else yet but guessing it will be part of the solution
    if (segment1Value == nil)
    {
        NSLog(@"Nothing");
    }
    else
    {
        NSLog(@"Something");
    }

    //Grab value of SegmentControl into instance variable and strip additional characters not needed from the end
    segment1Value = [[sender titleForSegmentAtIndex:sender.selectedSegmentIndex] substringToIndex:[sender titleForSegmentAtIndex:sender.selectedSegmentIndex].length -1 ];


    //Create predicate filter with key and value
    NSPredicate *predicateForSegment1 = [NSPredicate predicateWithFormat:@"(%K == %@)", segment1_key, segment1Value];


    //Check if filteredArray already has objects
    if ([filteredArrayOfDictionaries count] == 0)
    {
        //If filteredArray is empty, fill it with the filtered data using predicate from selected Segmented index control.  Then clear the array and copy filtered array to filtered array
        filteredArrayOfDictionaries = [[unfilteredArrayOfDictionaries filteredArrayUsingPredicate:predicateForSegment1] mutableCopy];

    }
    else
    {

        //If filtered array is full, empty it first then fill it with new filtered values.
        NSArray *tmpArray = [[NSArray alloc] initWithArray:filteredArrayOfDictionaries];
        [filteredArrayOfDictionaries removeAllObjects];
        filteredArrayOfDictionaries = [[tmpArray filteredArrayUsingPredicate:predicateForImpellerDiameter] mutableCopy];


    }

    [myArrayOfDictionaries removeAllObjects];
    [myArrayOfDictionaries addObjectsFromArray:filteredArrayOfDictionaries];
    [tableData reloadData];

}

我所有的UISegmentControls 都是这样的。我开始编写一个函数来调用每个 SegmentControl 事件以返回过滤后的数组,但我不确定这是否是最好的方法。这是我目前所拥有的,我不确定我应该传入谓词还是只传递字符串。

- (NSArray*)filteredResults:(NSArray*)unfilteredArray forSegment1:(NSPredicate*)segment1 forSegment2:(NSPredicate*)segment2 forSegment3:(NSPredicate*)segment3 forSegment4:(NSPredicate*)segment4;

我这样做的方式是否正确?这一切都变得非常混乱。我知道当我选择 Segment1 然后选择 Segment2 时,它会正确过滤。但是,一旦我更改已选择的段的值,它就不会返回任何内容,因为段值已被第一次选择过滤掉。之前选择了段控件时,是否可以比较过滤后的数组和未过滤的数组?我真的希望这是有道理的。感谢您的浏览和阅读。

【问题讨论】:

  • 尝试使用:NSArray *tmpArray = [[NSArray alloc] initWithArray: unfilteredArrayOfDictionaries];相反:NSArray *tmpArray = [[NSArray alloc] initWithArray: filteredArrayOfDictionaries];
  • 但这会从任何其他选定的段控件中删除任何过滤,对吧?
  • 抱歉我看不清楚:(

标签: ios objective-c nsdictionary nspredicate


【解决方案1】:

这是 MVC 范式如何迅速成为“大规模视图控制器”架构的完美示例。

您的视图控制器现在负责维护 5 个独立的数据源并管理它们之间的状态。可怜的家伙!

您应该考虑实现聚合数据源,特别是在您的情况下是分段数据源。这允许您将所有数据源逻辑抽象到它所属的模型对象中。

分段数据源负责管理子数据源以及出售适当的选定数据源。这种类型的设计使更改代码以包含更多数组或删除您认为合适的数组变得更加容易。

单一责任原则是您的朋友,抽象应该非常有吸引力,因为您注意到您的代码表现出代码异味的迹象。

还想继续吗?然后我强烈推荐观看今年 WWDC 关于高级集合视图布局的演讲,他们准确地讨论了如何降低视图控制器的复杂性并构建可重用的数据源。所有链接见this blog post

【讨论】:

  • 我喜欢这个答案。我一开始就尝试将所有内容都保持抽象,但只需将其放入 viewDidLoad 即可更轻松地解决“问题”。因此,当您说 DataSource 时,您的意思是一个单独的类,对吗?非常感谢!
  • @drpcken 很高兴听到它!我们都犯了为将来我们解决问题而感到内疚;)但重要的是能够识别您的简单解决方案何时会产生更大的问题:)我的意思是单独的类是的:)
【解决方案2】:

每次其中一个段发生变化时,您都需要重建过滤后的数组。我会用这样的东西

@interface ViewController ()
{
    //Create arrays for dictionaries
    @property (strong,nonatomic) NSMutableArray *myArrayOfDictionaries;
    @property (strong,nonatomic) NSMutableArray *filteredArrayOfDictionaries;

    //Array for storage of predicates
    @property (strong,nonatomic) NSMutableArray *predicates;
    @property (strong,nonatomic) NSArray *predicateKeys;
}
@end



-(void) viewDidLoad {
     [super viewDidLoad];

     self.predicateKeys=@[@"key1",@"key2",@"key3",@"key4",@"key4"];
     self.predicates=[NSMutableArray new];
     for (int i=0;i<self.predicateKeys.count;i++) {
         [self.predicates addObject:[NSNull null]];
     }
     [self setupFilteredArray];
}


//  Set the tag for each segmented controller to it's "number" - 0 to 5 and set this method as the action handler for all of the segmented controls


- (IBAction)segmentChanged:(UISegmentedControl *)sender {
      NSInteger segmentNumber=sender.tag;
//Grab value of SegmentControl into instance variable and strip additional characters not needed from the end
    segmentValue = [[sender titleForSegmentAtIndex:sender.selectedSegmentIndex] substringToIndex:[sender titleForSegmentAtIndex:sender.selectedSegmentIndex].length -1 ];

    if ([segmentValue isEqualToString:@""]) {
        [self.predicates replaceObjectAtIndex:segmentNumber withObject:[NSNull null]];
    }
    else {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(%K == %@)",self.predicateKeys[segmentNumber], segmentValue];
        [self.predicates replaceObjectAtIndex:segmentNumber withObject:predicate];
    }

    [self setupFilteredArray];
}

-(void) setupFilteredArray {
    NSArray *tempArray=[NSArray arrayWithArray:self.myArrayOfDictionaries];
    for (int i=0;i<self.predicates.count;i++) {
        if (!([self.predicates[i] isEqual:[NSNull null]])) {
            NSPredicate *predicate=(NSPredicate *)self.predicates[i];
            tempArray=[tempArray filteredArrayUsingPredicate:predicate];
        }
    }

    self.filteredArray=tempArray;  
}

【讨论】:

  • 当你说为每个segmentedcontrol设置标签时,你的意思是按照它在predicateKeys数组中出现的顺序设置它吗?顺便谢谢你!
  • 好的,想通了!但是在更改已选择的分段视图时,它仍在过滤我的过滤结果。我认为这条线:tempArray=[tempArray filteredArrayUsingPredicate:predicate];。当更改已选择的段的值时,它会采用过滤值并再次过滤它们,但不会返回任何内容。
  • 我想我需要检查一下之前是否选择了要更改的段,以便我可以重新加载所有未过滤的数组。
  • setupFilteredArray 首先复制未过滤的数组,然后依次应用每个谓词,因此更改第一个分段控件的值应该重置第一个谓词值,例如。
  • 不幸的是,事实并非如此。这些是我正在使用的两个片段:i.imgur.com/qlo8Ytt.png 如果我在第一个片段中选择 18",在第二个片段中选择 3ph,它效果很好并且可以适当过滤。但是,如果我立即将第一个片段中的选择从 18 更改为 24 ,它没有显示任何结果(应该有),因为我认为它正在尝试过滤过滤后的结果,其中所有 24" 结果都已被过滤掉(这有意义吗?)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-11
  • 2011-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多