NSPredicateEditor & NSExpression - Can the display be different than the value for the predicate?

懵懂的女人 提交于 2019-12-01 04:58:26

Yes, you can do this.

You want the array of left expressions to be the actual keyPaths in the final predicate. In your case, "Field1" and "Field2".

As for making a different value appear in the popup, here's where a mind-bending concept comes in:

You're going to localize your predicate editor into English.

There are two ways you could do this.

  1. With a .strings file
  2. With an NSDictionary

With a .strings file

In your source, you would include the following in a comment:

// NSLocalizedStringFromTable(@"%[Field1,Field2]@ %[contains]@ %@", @"PredicateEditor", @"")

When you run genstrings on your source code, this will generate a PredicateEditor.strings file with the following entries:

"%[Field1]@ %[contains]@ %@" = "%[Field1]@ %[contains]@ %@";
"%[Field2]@ %[contains]@ %@" = "%[Field2]@ %[contains]@ %@";

You would change the values to be:

"%[Field1]@ %[contains]@ %@" = "%[Abc]@ %[contains]@ %@";
"%[Field2]@ %[contains]@ %@" = "%[Def]@ %[contains]@ %@";

Then, when you create your NSPredicateEditor, you would set the formattingStringsFileName property to "PredicateEditor", and the editor will take care of the rest.

With an NSDictionary

This would follow the same fundamental concepts as the .strings option, except that you would essentially do:

NSDictionary *formatting = @{
  @"%[Field1]@ %[contains]@ %@" : @"%[Abc]@ %[contains]@ %@",
  @"%[Field2]@ %[contains]@ %@" : @"%[Def]@ %[contains]@ %@"
}
[myPredicateEditor setFormattingDictionary:formatting];

That's all you have to do.

I blogged about this a long time ago, and that has more information that you might find useful.

Basically you want to modify the title of the menu items in your popup button. That's all you need to do. It shouldn't effect the underlying predicate that you get returned. If you created it in interface builder it's easy to get at the menu items of a template and set their title. But since you did this in code you'll have to fix it in code.

Here's how you might do that. In my row template class I wanted to change the width of my NSTextFields. So in my row template class I look for them and modify them like this...

- (void)awakeFromNib {
    NSArray* views = [self templateViews];
    for (id view in views) {
        if ([[view class] isEqual:[NSTextField class]]) {
            NSRect tfFrame = [view frame];
            tfFrame.size.width = 600;
            [view setFrame:tfFrame];
        }
    }
}

You can see that I get the templateViews and look for the NSTextFields... and then modify them. You could do something similar looking for NSPopupButtons. Once you found one check their menu item titles and look for the ones titled "abc" and "def" and change their title to "Field1" and "Field2" respectively.

Rather than using NSLocalizedString with the option literals in the specific format and genstrings to generate the localization strings, it seems easier/cleaner to generate the final localization .strings file strings yourself.

From this blog post, we can use the Private API _generateFormattingDictionaryStringsFile to get the formatting strings from the NSPredicateEditor itself:

extension NSPredicateEditor {

    func formattingDictionaryStrings() -> String? {
        var strings: String? = nil

        if let formattingDictionaryData = self.perform(Selector("_generateFormattingDictionaryStringsFile"))?.takeRetainedValue() as? Data {
            strings = String(data: formattingDictionaryData, encoding: .utf16)
        }

        return strings
    }

}

This generates all of the permutations, skipping the need for genstrings. You then replace the tokens to the right of the = with your user-displayed strings.

"%[ABC]@ %[is]@ %[123]@" = "%1$[ABC]@ %2$[is]@ %3$[123]@";
"%[ABC]@ %[is]@ %[456]@" = "%1$[ABC]@ %2$[is]@ %3$[456]@";
"%[ABC]@ %[is]@ %[789]@" = "%1$[ABC]@ %2$[is]@ %3$[789]@";
"%[ABC]@ %[contains]@ %[123]@" = "%1$[ABC]@ %2$[contains]@ %3$[123]@";
"%[ABC]@ %[contains]@ %[456]@" = "%1$[ABC]@ %2$[contains]@ %3$[456]@";
"%[ABC]@ %[contains]@ %[789]@" = "%1$[ABC]@ %2$[contains]@ %3$[789]@";

Even better, you can construct the code that builds each NSPredicateEditorRowTemplate to take as input both the keypath used internally in the predicate and the localized string for that option.

Your method can then generate the strings above, but with the correct localization already inserted.

NOTE: calling this _generateFormattingDictionaryStringsFile private API method seems to cause random crashes within the NSPredicateEditor:

this class is not key value coding-compliant for the key rowType.

So be sure to run it once when needed, but don't leave it active when you're normally running or testing your app.

Yes, it is all a matter of localization, thanks to the fact that the objects are menu items. And they can be treated as such easily.

All you need to do is ...

  • localize your application.
  • Then enter the .strings file and change the value to what you want to be displayed or... use tools for managing/translating localized apps.

Here is an example of changing things directly in a .strings file: Change is to ist and booktitle to Buchtitel

/* Class = "NSMenuItem"; title = "is"; ObjectID = "G1c-st-GEK"; */
"G1c-st-GEK.title" = "ist";

/* Class = "NSMenuItem"; title = "booktitle"; ObjectID = "nQh-54-5Nx"; */
"nQh-54-5Nx.title" = "Buchtitel";

Remark: The best way to find the line to change is by looking for the ObjectID. This can be found for each MenuItem by the UIB identity inspector:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!