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
What is meant by the error message "Invalid parameter not satisfying: interaction"? Where is "interaction" coming from and how could this be fixed?
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