IOS UIMenuController UIMenuItem, как определить элемент, выбранный с помощью универсального метода выбора

Со следующей настройкой

....
MyUIMenuItem *someAction  = [[MyUIMenuItem alloc]initWithTitle : @"Something"  action : @selector(menuItemSelected:)];
MyUIMenuItem *someAction2 = [[MyUIMenuItem alloc]initWithTitle : @"Something2" action : @selector(menuItemSelected:)];
....

- (IBAction) menuItemSelected : (id) sender
{
    UIMenuController *mmi = (UIMenuController*) sender;
}

Как узнать, какой пункт меню был выбран.

И не говорите, что нужно иметь два метода... Заранее спасибо.


person ort11    schedule 05.02.2012    source источник
comment
Так что я думаю, что нет возможности сделать программные меню в IOS?   -  person ort11    schedule 06.02.2012


Ответы (4)


Хорошо, я решил это. Решение не очень красивое, и лучший вариант — «Apple устраняет проблему», но это, по крайней мере, работает.

Прежде всего, добавьте к селекторам действий UIMenuItem префикс "magic_". И не делайте соответствующих методов. (Если вы можете это сделать, то вам все равно не нужно это решение).

Я создаю свои UIMenuItems таким образом:

NSArray *buttons = [NSArray arrayWithObjects:@"some", @"random", @"stuff", nil];
NSMutableArray *menuItems = [NSMutableArray array];
for (NSString *buttonText in buttons) {
    NSString *sel = [NSString stringWithFormat:@"magic_%@", buttonText];
    [menuItems addObject:[[UIMenuItem alloc] 
                         initWithTitle:buttonText
                         action:NSSelectorFromString(sel)]];
}
[UIMenuController sharedMenuController].menuItems = menuItems;

Теперь ваш класс, который перехватывает сообщения о нажатии кнопки, нуждается в нескольких дополнениях. (В моем случае класс является подклассом UITextField. У вас может быть что-то другое.)

Во-первых, метод, который мы все хотели иметь, но которого не существовало:

- (void)tappedMenuItem:(NSString *)buttonText {
    NSLog(@"They tapped '%@'", buttonText);
}

Затем методы, которые делают это возможным:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    NSString *sel = NSStringFromSelector(action);
    NSRange match = [sel rangeOfString:@"magic_"];
    if (match.location == 0) {
        return YES;
    }
    return NO;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    if ([super methodSignatureForSelector:sel]) {
        return [super methodSignatureForSelector:sel];
    }
    return [super methodSignatureForSelector:@selector(tappedMenuItem:)];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    NSString *sel = NSStringFromSelector([invocation selector]);
    NSRange match = [sel rangeOfString:@"magic_"];
    if (match.location == 0) {
        [self tappedMenuItem:[sel substringFromIndex:6]];
    } else {
        [super forwardInvocation:invocation];
    }
}
person sobri    schedule 26.03.2012
comment
Вот полная реализация с поддержкой блоков. github.com/steipete/PSMenuItem - person steipete; 18.07.2012
comment
@steipete, твоя реализация блока гениальна - person Pier-Luc Gendreau; 18.11.2013

Можно было бы ожидать, что действие, связанное с данным пунктом меню, будет включать параметр sender, который должен указывать на выбранный пункт меню. Затем вы можете просто изучить заголовок элемента или сделать, как предлагает kforkarim, и создать подкласс UIMenuItem, чтобы включить свойство, которое вы можете использовать для идентификации элемента. К сожалению, согласно этому вопросу SO, параметр отправителя всегда равен нулю. Этому вопросу больше года, поэтому все могло измениться - посмотрите, что вы получаете в этом параметре.

С другой стороны, похоже, что вам нужно будет выполнить другое действие для каждого пункта меню. Конечно, вы можете настроить его так, чтобы все ваши действия вызывали общий метод, и если все они делают что-то очень похожее, это может иметь смысл.

person Caleb    schedule 05.02.2012
comment
Пытаюсь сделать мягкое меню. Выполнение различных действий для этого нецелесообразно. Параметр не всегда равен нулю, это меню. Я не вижу способа сделать то, что вы говорите, отсюда и вопрос, спасибо. - person ort11; 05.02.2012

Оказывается, можно получить объект UIButton (который на самом деле является UICalloutBarButton), который представляет UIMenuItem, если вы создадите подкласс UIApplication и повторно реализуете -sendAction:to:from:forEvent:. Хотя через UIApplication проходит только селектор -flash, этого достаточно.

@interface MyApplication : UIApplication
@end

@implementation MyApplication
- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
{
    // target == sender condition is just an additional one
    if (action == @selector(flash) && target == sender && [target isKindOfClass:NSClassFromString(@"UICalloutBarButton")]) {
        NSLog(@"pressed menu item title: %@", [(UIButton *)target titleLabel].text);
    }
    return [super sendAction:action to:target from:sender forEvent:event];
}
@end

Вы можете сохранить target (или любые данные, которые вам нужны) в, например. свойство и доступ к нему позже из вашего действия UIMenuItem.

И чтобы ваш подкласс UIApplication работал, вы должны передать его имя в качестве третьего параметра в UIApplicationMain():

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, NSStringFromClass([MyApplication class]), NSStringFromClass([YOUR_APP_DELEGATE class]));
    }
}

Это решение работает на iOS 5.x-7.0 на дату публикации (не тестировалось на более старых версиях).

person kambala    schedule 17.09.2013

ort11, вы можете создать свойство myuimenuitem и установить какой-то тег. Таким образом, объект отправителя может быть распознан по его тегу it. В Ibaction вы можете установить оператор switch, который может соответствовать каждому sender.tag и работать с этой логикой. Думаю, это самый простой путь.

person topgun    schedule 05.02.2012
comment
Отправителем является Меню, а не элемент. Вот почему вопрос. Зачем IOS это делать? Просто не делает т.к. - person ort11; 05.02.2012
comment
ord11, вы все равно инициализируете меню с разными заголовками, так почему бы вам не проверить ссылку на объект с его заголовком, как предложил Калеб. Поскольку отправитель — это просто тип идентификатора, вы можете проверить конкретный тип класса, а затем проверить его заголовок против него. Таким образом, вы можете ссылаться на тип класса MyUIMenuItem. что-то вроде этого. if ([sender isKindofClass:[MyUIMenuItem class]]), а затем еще один оператор if, как будто ([sender.title isEqualToString:@Something2]), then UIMenuController mmi = (UIMenuController) sender; где ваш отправитель *someAction2 в этом случае. Надеюсь это поможет - person topgun; 06.02.2012
comment
Отправитель - это меню, а не пункт меню - person ort11; 14.02.2012
comment
Сначала ваш отправитель является пунктом меню, а затем вы используете его как меню. просто возьмите nslog описания вашего отправителя перед кастингом, чтобы получить обзор объекта отправителя - person topgun; 15.02.2012
comment
Отправитель - это меню, а не пункт меню - person ort11; 03.04.2012
comment
ort: Изначально ваш отправитель является пунктом меню, если вы присмотритесь: - (IBAction) menuItemSelected : (id) sender { UIMenuController mmi = (UIMenuController) sender; } Отправитель (id), который вы получаете, является элементом меню, однако вы передаете его с помощью контроллера меню. Я буду иметь больше смысла, если вы просто сделаете NSLog, прежде чем вы запустите его, чтобы заметить, какие пункты меню вы выбрали. Сохраните или сохраните где-нибудь, а затем добавьте это в свое меню. - person topgun; 01.05.2012
comment
kforkarim: объект, переданный в параметре sender, является UIMenuController, а не UIMenuItem. То, как ort11 впоследствии кастует это, является отвлекающим маневром. - person hatfinch; 30.05.2016