Home > Mobile >  UIControl subclass - show UIMenu when pressing
UIControl subclass - show UIMenu when pressing

Time:11-22

Summary: All I am trying to do is to have a UIControl subclass that shows a UIMenu when pressed and I have read this other question but I am running into a crash when setting the contextMenuinteractionEnabled property to YES.

Similar question: iOS 14 Context Menu from UIView (Not from UIButton or UIBarButtonItem)


I have a UIControl subclass and I am wanting to add a menu to it and for the UIMenu to be shown when single tapping the control. But I keep getting an error when setting the contextMenuInteractionEnabled property to YES.

Here is the control subclass:

@interface MyControl : UIControl
@end

@implementation MyControl
@end

It is just a plain UIControl subclass. Then, I create an instance of that class and set the value of contextMenuInteractionEnabled to YES, as shown here:

MyControl *myControl = [[MyControl alloc] init];
myControl.contextMenuInteractionEnabled = YES; //<-- Thread 1: "Invalid parameter not satisfying: interaction"

But then when running this, I get the following error message:

Error Message

*** Assertion failure in -[MyControl addInteraction:], UIView.m:17965

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: interaction'

Error: NSInternalInconsistencyException

Reason: Invalid parameter not satisfying: interaction

Stack trace: (

0 CoreFoundation 0x00000001b8181e94 5CDC5D9A-E506-3740-B64E-BB30867B4F1B 40596

1 libobjc.A.dylib 0x00000001b14b78d8 objc_exception_throw 60

2 Foundation 0x00000001b2aa5b4c C431ACB6-FE04-3D28-B677-4DE6E1C7D81F 5528396

3 UIKitCore 0x00000001ba47e5bc 179501B6-0FC2-344A-B969-B4E3961EBE10 1349052

4 UIKitCore 0x00000001ba47ea70 179501B6-0FC2-344A-B969-B4E3961EBE10 1350256

)

libc abi: terminating with uncaught exception of type NSException

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: interaction' terminating with uncaught exception of type NSException


Questions

  1. What is meant by the error message "Invalid parameter not satisfying: interaction"? Where is "interaction" coming from and how could this be fixed?

  2. What am I doing wrong? All I want is a custom UIControl that can display a menu when pressed. That's it.

As a side note, this code works fine for a UIButton instance, so the UIButton class is doing something internal that I am not doing, but I don't know what.

Update 1

Following the answer from @DonMag, I tried this:

    MyControl *control = [[MyControl alloc] init];
    control.showsMenuAsPrimaryAction = YES;

    UIContextMenuInteraction *contextMenuInteraction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
    [control addInteraction:contextMenuInteraction];

    [self.view addSubview:control];

And this no longer crashes and the menu even shows up if I long press it. But I was hoping to get it to show up like a regular menu, as the primary action for pressing the control. What I am trying to do is to emulate the menu property on UIButton. That would be the most ideal thing is if I could implement a control that has a menu property and then having that menu be available as the primary action.

CodePudding user response:

There's no need to call .contextMenuInteractionEnabled = YES; ...

Here's a quick, complete example:

#import <UIKit/UIKit.h>

@interface MyControl : UIControl
@end

@implementation MyControl
@end

@interface ControlMenuViewController : UIViewController <UIContextMenuInteractionDelegate>
@end

@interface ControlMenuViewController ()
@end

@implementation ControlMenuViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    MyControl *myControl = [MyControl new];

    UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
    [myControl addInteraction:interaction];
    
    myControl.frame = CGRectMake(100, 200, 200, 50);
    myControl.backgroundColor = UIColor.systemRedColor;
    [self.view addSubview:myControl];
    
}

- (nullable UIContextMenuConfiguration *)contextMenuInteraction:(nonnull UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location {
    
    UIContextMenuConfiguration* config = [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                                                 previewProvider:nil
                                                                                  actionProvider:^UIMenu* _Nullable(NSArray<UIMenuElement*>* _Nonnull suggestedActions) {
        
        NSMutableArray* actions = [[NSMutableArray alloc] init];
        
        [actions addObject:[UIAction actionWithTitle:@"Stand!" image:[UIImage systemImageNamed:@"figure.stand"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Stand!");
            //[self performMenuCommandStand];
        }]];
        [actions addObject:[UIAction actionWithTitle:@"Walk!" image:[UIImage systemImageNamed:@"figure.walk"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Walk!");
            //[self performMenuCommandWalk];
        }]];
        [actions addObject:[UIAction actionWithTitle:@"Run!" image:[UIImage systemImageNamed:@"figure.run"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Run!");
            //[self performMenuCommandRun];
        }]];
        
        UIMenu* menu = [UIMenu menuWithTitle:@"" children:actions];
        return menu;
        
    }];
    
    return config;

}

@end

Edit

Slight modification to allow single-tap (Primary Action):

#import <UIKit/UIKit.h>

@interface MyControl : UIControl
@property (strong, nonatomic) UIContextMenuConfiguration *menuCfg;
@end

@implementation MyControl
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location {
    return self.menuCfg;
}
@end

@interface ControlMenuViewController : UIViewController
@end

@interface ControlMenuViewController ()
@end

@implementation ControlMenuViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    MyControl *myControl = [MyControl new];
    
    myControl.frame = CGRectMake(100, 200, 200, 50);
    myControl.backgroundColor = UIColor.systemRedColor;
    [self.view addSubview:myControl];

    // menu configuration
    UIContextMenuConfiguration* config = [UIContextMenuConfiguration configurationWithIdentifier:nil
                                                                                 previewProvider:nil
                                                                                  actionProvider:^UIMenu* _Nullable(NSArray<UIMenuElement*>* _Nonnull suggestedActions) {
        
        NSMutableArray* actions = [[NSMutableArray alloc] init];
        
        [actions addObject:[UIAction actionWithTitle:@"Stand!" image:[UIImage systemImageNamed:@"figure.stand"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Stand!");
            //[self performMenuCommandStand];
        }]];
        [actions addObject:[UIAction actionWithTitle:@"Walk!" image:[UIImage systemImageNamed:@"figure.walk"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Walk!");
            //[self performMenuCommandWalk];
        }]];
        [actions addObject:[UIAction actionWithTitle:@"Run!" image:[UIImage systemImageNamed:@"figure.run"] identifier:nil handler:^(__kindof UIAction* _Nonnull action) {
            NSLog(@"Run!");
            //[self performMenuCommandRun];
        }]];
        
        UIMenu* menu = [UIMenu menuWithTitle:@"" children:actions];
        return menu;
        
    }];

    // set custom control menu configuration
    myControl.menuCfg = config;
    
    // show menu on single-tap (instead of long-press)
    [myControl setContextMenuInteractionEnabled:YES];
    myControl.showsMenuAsPrimaryAction = YES;
    
}

@end
  • Related