Previous Entry Share Next Entry
Cocoa Menu Validation
latest
chanson
Cocoa supports automatic menu validation based on a few simple rules. However, it can be inconvenient to keep adding things to -validateMenuItem: all the time, particularly when there are inheritance hierarchies involved.

There's a pattern that can help you get it right much more easily using introspection. You can implement your -validateMenuItem: method to ask the receiver whether it can respond to a particular selector at the moment, like so:
- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
{
    BOOL enabled = YES;
    
    SEL itemAction = [menuItem action];
    
    if (itemAction != nil) {
        if ([self respondsToSelector:itemAction]) {
            SEL canAction = [self canSelectorForAction:itemAction];
            
            if ((canAction != nil) && [self respondsToSelector:canAction]) {
                BOOL (*performCanAction)(id, SEL) = (BOOL (*)(id, SEL)) objc_msgSend;
                enabled = performCanAction(self, canAction);
            } else {
                enabled = YES;
            }
        }
    }
    
    return enabled;
}

- (SEL)canSelectorForAction:(SEL)action
{
    NSString *actionString = NSStringFromSelector(action);
        // actionString is guaranteed to be 2+ characters
    NSString *actionStringFirst = [actionString substringToIndex:1];
    NSString *actionStringBody = [actionString substringWithRange:
        NSMakeRange(1, [actionString length] - 2)];
    
    NSString *canString
    = [[@"can" stringByAppendingString:[actionStringFirst uppercaseString]]
        stringByAppendingString:actionStringBody];
    
    return NSSelectorFromString(canString);
}
The above checks whether or not the receiver responds to the item's action selector. If it does, it will check if the receiver also implements a -(BOOL)canActionName selector, and use that to tell whether the menu item should be enabled.

The -canSelectorForAction: method just takes an action selector and returns its equivalent "canAction" selector, by upper-casing the first letter and appending it to "can". This lets you write a pair of methods for each action, one to perform the action and one to tell whether the action can be performed at the moment, leading to easy-to-test and uncluttered validation code.

Update (June 19, 2008): Changed how the -(BOOL)canActionName selector is performed to be safe and correct on Intel, and other architectures where a (BOOL) return might not be exactly equivalent to an (id) return. Thanks to an anonymous commenter for the correction!

Compiler Warning

(Anonymous)

2008-06-17 03:28 pm (UTC)

Thank you for the helpful hints for menu validation. After getting your code to work I looked into fixint the cast from pointer to integer of different size warning on the line where you call enabled = (BOOL) [self performSelector:canAction] . I found an article at http://www.red-sweater.com/blog/320/abusing-objective-c-with-class which shows a way to fix this warning. I came to the following solution:

Add the following import: #import <objc/objc-runtime.h>
Replace: enabled = (BOOL) [self performSelector:canAction];
with: BOOL (*performCanAction)(id, SEL) = (BOOL (*)(id, SEL)) objc_msgSend;
enabled = performCanAction(self, canAction);


I know this is an old post, but I figured this suggestion might be helpful to anyone else who finds it.

Re: Compiler Warning

chanson

2008-06-19 06:28 pm (UTC)

Thanks for the update! That's totally the right thing to do, I'll edit the post to fix it up and credit you.

extension

(Anonymous)

2008-06-30 07:27 pm (UTC)

I took your example and extended to do menu initialization as well which was pretty trivial given the nice work that you did on this example. I emailed you on since I wanted your permission to use your example in some public domain code. I would be happy to send it again.

check for responds to

(Anonymous)

2008-12-28 10:28 pm (UTC)

i'm still a cocoa noob, so may be wrong, but i think i read that your object is first checked for respondsToSelector before validateMenuItem is even called so your call:

if ([self respondsToSelector:itemAction]) {

may be redundant??

owhowjor pjcp

(Anonymous)

2011-01-08 10:03 pm (UTC)

ejoslcf fdc hqeaw fat mature (http://www.matureporn234.com/fat mature.html)

epsds!

vstft oynqon qkr orgy pics (http://www.groupsex234.com/orgy pics.html)

bgimelce psjc

(Anonymous)

2011-02-07 02:38 pm (UTC)

ensm qosrf weight loss and calories (http://www.pe6.us/meratol/) jyeonc h la m xyc

tapw hmplk [URL=http://www.pe6.us/meratol/]weight loss and calories[/URL] vtrudz g ky r pdz