Cocoa NSPredicateEditorRowTemplate:如何填充右侧弹出窗口

Cocoa NSPredicateEditorRowTemplate:如何填充右侧弹出窗口,cocoa,nspredicate,nspredicateeditor,nsexpression,Cocoa,Nspredicate,Nspredicateeditor,Nsexpression,我正在尝试生成一个NSPredicateEditorRowTemplate,左侧有一个实体Foo中的许多属性名,其中包括属性栏。当用户选择“bar”时,右侧应成为包含所有“bar”值的弹出窗口 如何最好地填充右侧弹出窗口?bar的所有唯一值都存储在一个NSMutableArray中,因此,当数组发生变化时,或许我可以使用KVO来更改行模板 是否有一种方法可以使用代码轻松更改nspredicateditor行中右侧弹出窗口中的值?我可以在IB中输入一些静态值,但在这种情况下不行 编辑 在阅读了大量

我正在尝试生成一个
NSPredicateEditorRowTemplate
,左侧有一个实体Foo中的许多属性名,其中包括属性栏。当用户选择“bar”时,右侧应成为包含所有“bar”值的弹出窗口

如何最好地填充右侧弹出窗口?bar的所有唯一值都存储在一个
NSMutableArray
中,因此,当数组发生变化时,或许我可以使用KVO来更改行模板

是否有一种方法可以使用代码轻松更改
nspredicateditor
行中右侧弹出窗口中的值?我可以在IB中输入一些静态值,但在这种情况下不行

编辑

在阅读了大量相关问答之后,包括@Dave DeLong的精彩回答,我认为可以像这样做一些工作:

NSArray *leftexp = @[[NSExpression expressionForKeyPath:@"name"],[NSExpression expressionForKeyPath:@"married"]];
NSArray *rightexp = @[[NSExpression expressionWithFormat:@"One"],[NSExpression expressionWithFormat:@"Two"]];
NSPredicateEditorRowTemplate *template = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:leftexp rightExpressions:rightexp modifier:NSDirectPredicateModifier operators:@[@(NSEqualToPredicateOperatorType)] options:0];

NSPredicateEditorRowTemplate *compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:@[@(NSOrPredicateType)]];

[self.predicateEditor setRowTemplates:@[template,compound]];
NSEntityDescription *description = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.managedObjectContext];
self.predicate = [NSPredicate predicateWithFormat:@"name == ''"];

我已经看到了一些使基本NSPredicateEditor出现的方法(至少有复合行),但在我看来,必须有一种优雅的方法来做到这一点,这就是它的本意。但是找不到,有人能帮我吗?

用复合行(NOT、AND、OR)以及一行简单谓词填充
nspredicateditor
的关键,是指
nspredicateditor
应该接收一个
-setRowTemplates:
调用,调用的
NSArray
同时包含复合谓词和常规谓词。然后,应使用
-和predicatewithsubpredicates:
将其谓词设置为具有子谓词的复合谓词

这对我来说是个好办法:

NSArray *leftexp = @[[NSExpression expressionForKeyPath:@"name"]];
NSArray *rightexp = @[[NSExpression expressionForConstantValue:@"A"],[NSExpression expressionForConstantValue:@"B"],[NSExpression expressionForConstantValue:@"C"]];
NSPredicateEditorRowTemplate *template = [[NSPredicateEditorRowTemplate alloc] \
          initWithLeftExpressions:leftexp
          rightExpressions:rightexp
          modifier:NSDirectPredicateModifier
          operators:@[@(NSEqualToPredicateOperatorType)]
          options:0];
NSPredicateEditorRowTemplate *compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:@[@(NSAndPredicateType),@(NSOrPredicateType),@(NSNotPredicateType)]];
NSMutableArray *templates = [NSMutableArray arrayWithObject:compound];
[templates addObject:template];
[self.predicateEditor setRowTemplates:templates];

NSPredicate *p = [NSPredicate predicateWithFormat:@"name == 'A'"];
self.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObject:p]];

有些人可能会争辩说,这样做的目的是在Interface Builder中。如果您熟悉行模板是如何工作的,它们是如何组合的,等等,那么这可能很好。就我个人而言,我更喜欢以编程方式创建行模板,因为我可以明确地说明行模板是如何工作的

让我们通过一个例子来说明这一切是如何协同工作的,然后您可以决定哪种方法最适合您:

  • 您有一个具有两个
    NSString
    属性的对象:
    bar
    baz
  • bar
    只能有一定数量的可能值,我们将其称为
    @“Bar1”
    @“Bar2”
    @“Bar3”
  • baz
    可以是任何
    NSString
考虑到这一点,以下是如何以编程方式创建它:

// these defines are for brevity on the web page; please don't actually do this
#define NSPERT NSPredicateEditorRowTemplate
#define KP(_kp) [NSExpression expressionForKeyPath:(_kp)]
#define CV(_cv) [NSExpression expressionForConstantValue:(_cv)]

NSPERT *compound = [[NSPERT alloc] initWithCompoundTypes:@[@(NSAndPredicateType), 
                                                           @(NSOrPredicateType), 
                                                           @(NSNotPredicateType)]];

NSPERT *bar = [[NSPERT alloc] initWithLeftExpressions:@[KP(@"bar")] 
                                     rightExpressions:@[CV(@"Bar1"), CV(@"Bar2"), CV(@"Bar3")] 
                                             modifier:NSDirectPredicateModifier 
                                            operators:@[@(NSEqualToPredicateOperatorType), 
                                                        @(NSNotEqualToPredicateOperatorType)] 
                                              options:0];

NSPERT *baz = [[NSPERT alloc] initWithLeftExpressions:@[KP(@"baz")] 
                         rightExpressionAttributeType:NSStringAttributeType 
                                             modifier:NSDirectPredicateModifier 
                                            operators:@[@(NSEqualToPredicateOperatorType),
                                                        @(NSNotEqualToPredicateOperatorType)]
                                              options:0];

NSArray *templates = @[compound, bar, baz];
[self.myPredicateEditor setRowTemplates:templates];

当然,仅仅为了创建行模板就需要大量的输入。我明白。我只是觉得它也非常明确,易于调试。也就是说,您还可以在Interface Builder中配置完全相同的内容。为此,在xib中放置一个
NSPredicateEditor
,并为其提供3行模板。您将不使用复合行模板,但将其他两个模板更改为如下配置:

NSArray *leftexp = @[[NSExpression expressionForKeyPath:@"name"],[NSExpression expressionForKeyPath:@"married"]];
NSArray *rightexp = @[[NSExpression expressionWithFormat:@"One"],[NSExpression expressionWithFormat:@"Two"]];
NSPredicateEditorRowTemplate *template = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:leftexp rightExpressions:rightexp modifier:NSDirectPredicateModifier operators:@[@(NSEqualToPredicateOperatorType)] options:0];

NSPredicateEditorRowTemplate *compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:@[@(NSOrPredicateType)]];

[self.predicateEditor setRowTemplates:@[template,compound]];
NSEntityDescription *description = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.managedObjectContext];
self.predicate = [NSPredicate predicateWithFormat:@"name == ''"];
对于“
”模板:

对于“
baz
”模板:

如果您将这些截图与上面的代码进行比较,应该(希望)很容易看到一个截图如何映射到另一个截图。使用如下配置的行模板,IB还会在窗口中显示谓词编辑器:

如果您想做更高级的事情,比如创建自定义行模板,或者用数据模型中的内容填充允许的常量值列表,那么您必须在代码中进行配置


编辑以回答评论问题

这些选择器的
修饰符
位对应于所创建的
NSComparisonPredicate
。有三个修饰符:直接修饰符、任意修饰符和全部修饰符

@"bar = 'Bar1'";
  ^ No modifier means this is NSDirectPredicateModifier

@"ANY user.userName = 'markjs'";
  ^~~ use of "ANY" keyword means this is NSAnyPredicateModifier

@"ALL user.age < 42";
  ^~~ use of "ALL" keyword means this is NSAllPredicateModifier
@“bar='Bar1';
^无修饰符表示这是NSDirectPredicateModifier
@“ANY user.userName='markjs';
^~~使用“ANY”关键字意味着这是NSAnyPredicateModifier
@“所有用户年龄<42岁”;
^~~使用“ALL”关键字意味着这是NSAllPredicateModifier
在这三个示例中,它们都是比较谓词(因为它们有左侧、运算符和右侧),但“ANY”或“all”的使用会影响左侧的解释方式。如果其中一个存在,则谓词期望左侧对一组可能性进行求值


使用“直接”选项(或
0
)基本上意味着您将进行一对一的比较。

基于markjs答案,我将其翻译为swift 4,并将其放在NSPredicateEditorRowTemplate的扩展中

extension NSPredicateEditorRowTemplate {

    convenience init( compoundTypes: [NSCompoundPredicate.LogicalType] ) {

        var compoundTypesNSNumber: [NSNumber] = []
        for c in compoundTypes { compoundTypesNSNumber.append( NSNumber(value: c.rawValue) ) }
        self.init( compoundTypes: compoundTypesNSNumber )
    }

    convenience init( forKeyPath keyPath: String, withValues values: [Any] ) {

        let keyPaths: [NSExpression] = [NSExpression(forKeyPath: keyPath)]

        var constantValues: [NSExpression] = []
        for v in values {

            constantValues.append( NSExpression(forConstantValue: v) )
        }

        let operators: [NSComparisonPredicate.Operator] = [.equalTo, .notEqualTo]
        var operatorsNSNumber: [NSNumber] = []
        for o in operators { operatorsNSNumber.append( NSNumber(value: o.rawValue) ) }

        self.init( leftExpressions: keyPaths,
                   rightExpressions: constantValues,
                   modifier: .direct,
                   operators: operatorsNSNumber,
                   options: (Int(NSComparisonPredicate.Options.caseInsensitive.rawValue | NSComparisonPredicate.Options.diacriticInsensitive.rawValue)) )
    }

    convenience init( stringCompareForKeyPaths keyPaths: [String] ) {

        var leftExpressions: [NSExpression] = []
        for keyPath in keyPaths {

            leftExpressions.append( NSExpression(forKeyPath: keyPath) )
        }

        let operators: [NSComparisonPredicate.Operator] = [.contains, .equalTo, .notEqualTo, .beginsWith, .endsWith]
        var operatorsNSNumber: [NSNumber] = []
        for o in operators { operatorsNSNumber.append( NSNumber(value: o.rawValue) ) }

        self.init( leftExpressions: leftExpressions,
                   rightExpressionAttributeType: .stringAttributeType,
                   modifier: .direct,
                   operators: operatorsNSNumber,
                   options: (Int(NSComparisonPredicate.Options.caseInsensitive.rawValue | NSComparisonPredicate.Options.diacriticInsensitive.rawValue)) )
    }

    convenience init( IntCompareForKeyPaths keyPaths: [String] ) {

        var leftExpressions: [NSExpression] = []
        for keyPath in keyPaths {

            leftExpressions.append( NSExpression(forKeyPath: keyPath) )
        }

        let operators: [NSComparisonPredicate.Operator] = [.equalTo, .notEqualTo, .greaterThan, .greaterThanOrEqualTo, .lessThan, .lessThanOrEqualTo]
        var operatorsNSNumber: [NSNumber] = []
        for o in operators { operatorsNSNumber.append( NSNumber(value: o.rawValue) ) }

        self.init( leftExpressions: leftExpressions,
                   rightExpressionAttributeType: .integer16AttributeType,
                   modifier: .direct,
                   operators: operatorsNSNumber,
                   options: 0 )
    }
}
其用途如下:

   func setMyPetFilter() {

        // We assume that myPredicateEditor is create/set in IB
        // Remove whatever was configured in IB
        myPredicateEditor.rowTemplates.removeAll()

        let templateCompoundTypes = NSPredicateEditorRowTemplate( compoundTypes: [.and, .or, .not] )
        let template1 = NSPredicateEditorRowTemplate( forKeyPath: "Animals", withValues: ["Dog", "Cat", "Hamster", "Canary"] )
        let template2 = NSPredicateEditorRowTemplate( stringCompareForKeyPaths: ["PetName", "OwnerName"] )
        let template3 = NSPredicateEditorRowTemplate( forKeyPath: "Legs", withValues: [2,4] )
        let template4 = NSPredicateEditorRowTemplate( IntCompareForKeyPaths: ["Age", "AgeOwner"] )
        myPredicateEditor.rowTemplates = [templateCompoundTypes, template1, template2, template3, template4]
    }

谢谢戴夫在其他地方给了我一些很好的答案:还有+我很高兴你能弄明白这一点!谓词编辑器花了我很长时间来摸索。再次感谢您提供了有关Cocoa中一个类的好信息,我非常喜欢这些类,但同时发现它们很难理解。我认为,使用IB很难(不可能?)在运行时更改右侧常量。或者我可以使用一些绑定吗?其次,在这些方法中是否有关于
修饰符:
变量信息的链接?在谷歌上找不到任何东西,甚至如此@markjs您也许可以找到一种在运行时动态更改IB生成的行模板的方法(我想不出一种立即执行此操作的方法),但我认为以编程方式创建该行模板会简单得多。另外,我在结尾添加了一点关于修饰语的内容:)再次感谢!我现在有了一个功能很好的谓词编辑器。我发现的最奇怪的事情之一是,通过
-setObjectValue:
,确实存在与编辑器关联的
NSPredicate
。如果没有,就清空窗户!如果用户删除最后一个“实”谓词,则复合行也将消失。此时不能添加新行!我查看了
NSPredicateEditorRowTemplate
是否有方法阻止用户使用“删除”命令