You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何制作NSPredicateEditorRowTemplate以支持NULL值搜索选项?

嘿,这个需求我刚好折腾过,其实不用完全从零写自定义RowTemplate,不过确实需要对默认的模板做一些扩展调整,下面给你一步步说清楚:

核心思路

默认的NSComparisonPredicate操作符集合里没有直接的「检查NULL」选项(比如IS NULL/IS NOT NULL),所以我们需要把这两个操作符加到现有的下拉菜单里,同时处理对应的谓词生成逻辑——而这就需要借助自定义NSPredicateEditorRowTemplate子类来实现,不过工作量很小,不需要完全重写模板。

具体实现步骤

1. 定义自定义NULL操作符

首先我们需要为NULL检查定义专属的操作符常量,注意要避开系统已有的NSComparisonPredicateOperatorType数值,避免冲突:

// Objective-C
typedef NS_ENUM(NSUInteger, CustomPredicateOperator) {
    CustomPredicateOperatorIsNULL = 100, // 选系统未使用的数值
    CustomPredicateOperatorIsNOTNULL = 101
};
// Swift
enum CustomPredicateOperator: UInt {
    case isNULL = 100
    case isNOTNULL = 101
}

2. 创建自定义RowTemplate子类

我们继承默认的NSPredicateEditorRowTemplate,重写几个关键方法来扩展功能:

Objective-C版本

@interface NullablePredicateRowTemplate : NSPredicateEditorRowTemplate
@end

@implementation NullablePredicateRowTemplate

// 扩展操作符下拉菜单,添加NULL选项
- (NSArray *)templateViews {
    NSArray *defaultViews = [super templateViews];
    NSPopUpButton *operatorPopup = defaultViews[1]; // 默认模板的第二个视图是操作符下拉框
    
    // 把NULL检查选项加到菜单末尾
    [operatorPopup addItemWithTitle:@"is NULL"];
    [operatorPopup addItemWithTitle:@"is NOT NULL"];
    
    return defaultViews;
}

// 根据选中的操作符生成对应的谓词
- (NSPredicate *)predicateWithSubpredicates:(NSArray<NSPredicate *> *)subpredicates {
    // 获取当前选中的键路径
    NSString *keyPath = [(NSComparisonPredicate *)subpredicates.firstObject leftExpression].keyPath;
    NSPopUpButton *operatorPopup = self.templateViews[1];
    NSInteger selectedIndex = operatorPopup.selectedIndex;
    
    // 判断是否选中NULL操作符
    NSInteger nullIndex = operatorPopup.numberOfItems - 2;
    NSInteger notNullIndex = operatorPopup.numberOfItems - 1;
    
    if (selectedIndex == nullIndex) {
        return [NSPredicate predicateWithFormat:@"%K IS NULL", keyPath];
    } else if (selectedIndex == notNullIndex) {
        return [NSPredicate predicateWithFormat:@"%K IS NOT NULL", keyPath];
    } else {
        // 其他操作符沿用默认逻辑
        return [super predicateWithSubpredicates:subpredicates];
    }
}

// 控制界面显示:选中NULL操作符时隐藏右侧输入框
- (NSInteger)displayableSubpredicateCount {
    NSPopUpButton *operatorPopup = self.templateViews[1];
    NSInteger selectedIndex = operatorPopup.selectedIndex;
    NSInteger nullIndex = operatorPopup.numberOfItems - 2;
    NSInteger notNullIndex = operatorPopup.numberOfItems - 1;
    
    if (selectedIndex == nullIndex || selectedIndex == notNullIndex) {
        return 1; // 只需要显示键路径选择,不需要输入值
    }
    return [super displayableSubpredicateCount];
}

@end

Swift版本

class NullablePredicateRowTemplate: NSPredicateEditorRowTemplate {
    override var templateViews: [NSView] {
        let defaultViews = super.templateViews
        guard let operatorPopup = defaultViews[1] as? NSPopUpButton else {
            return defaultViews
        }
        operatorPopup.addItem(withTitle: "is NULL")
        operatorPopup.addItem(withTitle: "is NOT NULL")
        return defaultViews
    }
    
    override func predicate(withSubpredicates subpredicates: [NSPredicate]) -> NSPredicate {
        guard let compPredicate = subpredicates.first as? NSComparisonPredicate,
              let keyPath = compPredicate.leftExpression.keyPath,
              let operatorPopup = templateViews[1] as? NSPopUpButton else {
            return super.predicate(withSubpredicates: subpredicates)
        }
        
        let selectedIndex = operatorPopup.selectedIndex
        let nullIndex = operatorPopup.numberOfItems - 2
        let notNullIndex = operatorPopup.numberOfItems - 1
        
        if selectedIndex == nullIndex {
            return NSPredicate(format: "%K IS NULL", keyPath)
        } else if selectedIndex == notNullIndex {
            return NSPredicate(format: "%K IS NOT NULL", keyPath)
        } else {
            return super.predicate(withSubpredicates: subpredicates)
        }
    }
    
    override var displayableSubpredicateCount: Int {
        guard let operatorPopup = templateViews[1] as? NSPopUpButton else {
            return super.displayableSubpredicateCount
        }
        let selectedIndex = operatorPopup.selectedIndex
        let nullIndex = operatorPopup.numberOfItems - 2
        let notNullIndex = operatorPopup.numberOfItems - 1
        
        if selectedIndex == nullIndex || selectedIndex == notNullIndex {
            return 1
        }
        return super.displayableSubpredicateCount
    }
}

3. 配置NSPredicateEditor使用自定义模板

最后把这个自定义模板设置给你的NSPredicateEditor,替换原来的默认比较模板:

// Objective-C
NSArray *keyPaths = @[@"name", @"email", @"address"]; // 你的字段列表
NullablePredicateRowTemplate *nullableTemplate = [[NullablePredicateRowTemplate alloc] initWithLeftExpressions:[keyPaths valueForKeyPath:@"SELF"]
                                                                                      rightExpressionAttributeType:NSStringAttributeType
                                                                                                    modifier:NSDirectPredicateModifier
                                                                                                    operators:@[@(NSLessThanPredicateOperatorType), @(NSGreaterThanPredicateOperatorType), @(NSEqualToPredicateOperatorType)]
                                                                                                        options:0];
self.predicateEditor.rowTemplates = @[nullableTemplate];
// Swift
let keyPaths = ["name", "email", "address"].map { NSExpression(forKeyPath: $0) }
let nullableTemplate = NullablePredicateRowTemplate(leftExpressions: keyPaths,
                                                    rightExpressionAttributeType: .stringAttributeType,
                                                    modifier: .direct,
                                                    operators: [NSNumber(value: NSComparisonPredicate.Operator.lessThan.rawValue),
                                                               NSNumber(value: NSComparisonPredicate.Operator.greaterThan.rawValue),
                                                               NSNumber(value: NSComparisonPredicate.Operator.equalTo.rawValue)],
                                                    options: 0)
predicateEditor.rowTemplates = [nullableTemplate]
关键注意点
  • 自定义操作符的数值一定要避开系统已有的NSComparisonPredicateOperatorType值,防止逻辑冲突;
  • displayableSubpredicateCount方法是控制输入框显示的关键,返回1时系统会自动隐藏右侧的输入视图;
  • 如果你的字段有不同类型(比如数字、日期),可以创建多个对应类型的自定义模板,或者在模板里做类型适配。

简单来说,确实需要自定义NSPredicateEditorRowTemplate类,但不需要完全从零构建,只需要在默认模板基础上扩展操作符列表,并重写几个核心方法就能实现你要的效果——把NULL检查选项和其他比较操作符放在同一个下拉菜单里。

内容的提问来源于stack exchange,提问作者Ely

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:36:06