views:

143

answers:

2

Hi, I am trying to use UIMenuController for a dynamical menu (titles and actions come from a server). The problem is that I have to use UIMenuItems initWithTitle:action: where action is a @selector.

I can use @selector(dispatch:) but then I am not able to distinguish which of the items the user pressed. - (void)dispatch:(id)sender { NSLog(@"%@", sender); } says it is a UIMenuController and It don't have a method which would tell which menu item was pressed.

I can't just write 100 methods to dispatch every possible selector, ok there will not be more then 10 but still, this seems not a good idea.

Do I have to create dynamic methods for each such selector? http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html? This seems odd too.

Any better propositions then this two?

// This approach doesn't work.

- (void)showMenu {

    [self becomeFirstResponder];

    NSMutableArray *menuItems = [[NSMutableArray alloc] init];

    UIMenuItem *item;
    for (MLAction *action in self.dataSource.actions) {
        item = [[UIMenuItem alloc] initWithTitle:action.title action:@selector(action:)];
        [menuItems addObject:item];
        [item release];
    }

    UIMenuController *menuController = [UIMenuController sharedMenuController];
    menuController.menuItems = menuItems;
    [menuItems release];
    [menuController update];
    [menuController setMenuVisible:YES animated:YES];

}

- (void)action:(id)sender {
    NSLog(@"%@", sender); // gives UIMenuController instead of UIMenuItem
    // I can not know which menu item was pressed
}

// This approach is really ugly.

- (void)showMenu {

    [self becomeFirstResponder];

    NSMutableArray *menuItems = [[NSMutableArray alloc] initWithCapacity:5];

    UIMenuItem *item;
    NSInteger i = 0;
    for (MLAction *action in self.dataSource.actions) {
        item = [[UIMenuItem alloc] initWithTitle:action.text
                                                                            action:NSSelectorFromString([NSString stringWithFormat:@"action%i:", i++])];
        [menuItems addObject:item];
        [item release];
    }

    UIMenuController *menuController = [UIMenuController sharedMenuController];
    menuController.menuItems = menuItems;
    [menuItems release];
    [menuController update];
    [menuController setMenuVisible:YES animated:YES];

}

- (void)action:(NSInteger)number {
    NSLog(@"%i", number); // gives the index of the action in the menu.
}

// This is a hack, I have to assume that there will never be more then 15 actions
- (void)action0:(id)sender { [self action:0]; }
- (void)action1:(id)sender { [self action:1]; }
- (void)action2:(id)sender { [self action:2]; }
- (void)action3:(id)sender { [self action:3]; }
- (void)action4:(id)sender { [self action:4]; }
- (void)action5:(id)sender { [self action:5]; }
- (void)action6:(id)sender { [self action:6]; }
- (void)action7:(id)sender { [self action:7]; }
- (void)action8:(id)sender { [self action:8]; }
- (void)action9:(id)sender { [self action:8]; }
- (void)action10:(id)sender { [self action:10]; }
- (void)action11:(id)sender { [self action:11]; }
- (void)action12:(id)sender { [self action:12]; }
- (void)action13:(id)sender { [self action:13]; }
- (void)action14:(id)sender { [self action:14]; }
A: 

Unless the menu items do the same thing, why should they share an action? I would go ahead and write actions that specify a behavior you want and link the menu items up to those.

Chuck
The problem is that I can't know which actions there will be at compile time because the actions will come from a server, it is always a title for the menu and a http-url which should be called when the user klicks on it. I'll update the question with some code.
Jeena
+2  A: 

That approach would work, although you need a unique selector-name for every button and a mapping from that name to whatever you want to target.
For the selector name a unique string has to be chosen (UUIDs or maybe a sanitized & prefixed version of the title would work). Then you need one method that resolves the call and "alias" it with the different selector names:

- (void)updateMenu:(NSArray *)menuEntries {
    Class cls = [self class];
    SEL fwd = @selector(forwarder:);
    for (MenuEntry *entry in menuEntries) {
        SEL sel = [self uniqueActionSelector];
        // assuming keys not being retained, otherwise use NSValue:
        [self.actionDict addObject:entry.url forKey:sel]; 
        class_addMethod(cls, sel, [cls instanceMethodForSelector:fwd], "v@:@");
        // now add menu item with sel as the action
    }
}

Now the forwarder can look up what URL is associated with the menu item:

- (void)forwarder:(UIMenuController *)mc {
    NSLog(@"URL for item is: %@", [actionDict objectForKey:_cmd]);
}

To generate the selectors you could use something like:

- (SEL)uniqueActionSelector {
    NSString *unique = ...; // the unique part
    NSString *selString = [NSString stringWithFormat:@"menu_%@:", unique];
    SEL sel = sel_registerName([selString UTF8String]);
    return sel;
}
Georg Fritzsche
Where does the _cmd come from and what is it?
Jeena
@jen: A keyword for the selector with which the implementation function is invoked. Try logging `NSStringFromSelector(_cmd)`.
Georg Fritzsche