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.
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
I just build a class to manage correctly my database and JSON request. The problem is that now, how can I perform the segue ?
Here is my code
In my view :
- (IBAction)loginClick:(id)sender
{
NSString *post = [NSString stringWithFormat:#"username=test&password=test"];
[[DataManagement sharedManager] WebServiceLogin:post];
}
- (void) showTypeView
{
[self performSegueWithIdentifier:#"showTypeView" sender:nil];
}
In my class :
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
...
switch ([[response valueForKey:#"success"] intValue])
{
case 0:
{
NSLog(#"error: %# error Description: %#", [response valueForKey:#"success"], [response valueForKey:#"error_message"]);
break;
}
case 1:
{
LoginViewController *showView = [LoginViewController new];
[showView showTypeView];
break;
}
default:
break;
}
...
}
When I launch, I have an error :
**
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<LoginViewController: 0x165afd30>) has no segue with identifier 'showTypeView''
*** First throw call stack:
(0x2592e2eb 0x250fadff 0x29e2b037 0xe1819 0xdb64f 0x25f64de1 0x25f64d99 0x25f64e8d 0x25e261ef 0x25edf04f 0xa77cab 0xa7f835 0x25e171e3 0x258415f9 0x25e170cb 0x25e16f95 0x25e16e29 0x258f1257 0x258f0e47 0x258ef1af 0x25841bb9 0x258419ad 0x26abbaf9 0x29b2dfb5 0xe3ea9 0x254f4873)
libc++abi.dylib: terminating with uncaught exception of type NSException
**
If you're using segueWithIdentifier then you need to already have the segue built in Storyboard and labeled correctly as "showTypeView". Otherwise you should use a navigation controller to push a view controller or use self presentViewController to show a modal view controller.
EDIT:
Building off of Larme's comment, you can build a delegate like this:
// In your class.h file
#property (weak, nonatomic)id<SegueDelegate> delegate;
// In class.m file
LoginViewController *showView = [LoginViewController new];
self.delegate = showView;
[self.delegate segue];
// In LoginViewController.h
#protocol SegueDelegate
-(void)segue;
#end
#interface LoginViewController: UIViewController <SegueDelegate>
-(void)segue;
#end
// In LoginViewController.m
#implementation LoginViewController
-(void)segue
{
[self performSegueWithIdentifier:#"showTypeView" sender:nil];
}
#end
There are 3 behaviors in my method, I'm pretty sure animationOptions is the one causing bug. AnimationOptions is only used to ban rotation.If i delete this behavior,my code work fine.
Here is my bug.Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil’
After I add a exception breakpoint, the breakpoint stay at this line:
[self addChildBehavior:self.animationOptions];
If i delete this line, my code work fine.
But how can i fix this bug, I can not find where is this line mistake
Here is my DropitBehavior.m
#import "DropitBehavior.h"
#interface DropitBehavior()
#property(strong,nonatomic) UIGravityBehavior *gravity;
#property(strong,nonatomic) UICollisionBehavior *collider;
#property(strong,nonatomic) UIDynamicItemBehavior *animationOptions;
#end
#implementation DropitBehavior
-(UIGravityBehavior *)gravity
{
if (!_gravity) {
_gravity=[[UIGravityBehavior alloc]init];
_gravity.magnitude=0.90;
}
return _gravity;
}
-(UICollisionBehavior *)collider
{
if (!_collider) {
_collider=[[UICollisionBehavior alloc]init];
_collider.translatesReferenceBoundsIntoBoundary=YES;
}
return _collider;
}
-(UIDynamicItemBehavior *)animationOptions
{
if (_animationOptions) {
_animationOptions=[[UIDynamicItemBehavior alloc]init];
_animationOptions.allowsRotation=NO;
}
return _animationOptions;
}
-(void)additem:(id <UIDynamicItem>)item
{
[self.gravity addItem:item];
[self.collider addItem:item];
[self.animationOptions addItem:item];
}
-(void)removeitem:(id <UIDynamicItem>)item
{
[self.gravity removeItem:item];
[self.collider removeItem:item];
[self.animationOptions removeItem:item];
}
-(instancetype)init
{
self=[super init];
[self addChildBehavior:self.gravity];
[self addChildBehavior:self.collider];
[self addChildBehavior:self.animationOptions];
return self;
}
#end
a NSMutable array doesn't accept a "nil" to be added to itself
"self.animationOptions" will call the
-(UIDynamicItemBehavior *)animationOptions
method and the method will return only "nil" all the time.
-(UIDynamicItemBehavior *)animationOptions
{
if (_animationOptions) {
_animationOptions=[[UIDynamicItemBehavior alloc]init];
your logic in this piece of code doesn't allow the system to construct "_animationOptions" object.
if (!_animationOptions)
{
..code..
}
would help
I've a big problem with a scrollView. I have this code and when i press a button that is in the xib file of "registerView" then the app crashes:
MainViewController.m:
ViewDidLoad:
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
scrollView.backgroundColor = [UIColor whiteColor];
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = NO;
scrollView.showsVerticalScrollIndicator = YES;
scrollView.showsHorizontalScrollIndicator = YES;
scrollView.contentSize = CGSizeMake(768, 1900);
[self.view addSubview:scrollView];
RegisterViewController *registerView = [[RegisterViewController alloc] init];
[scrollView addSubview:registerView.view];
"RegisterView" is added to a scrollView, but then when I push a button in "registerView" the app crashes with this log:
2014-05-12 11:18:04.599 RegisterForm[16505:60b] -[UIScrollView modalPresentationStyle]: unrecognized selector sent to instance 0x13f517200
2014-05-12 11:18:04.602 RegisterForm[16505:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIScrollView modalPresentationStyle]: unrecognized selector sent to instance 0x13f517200'
*** First throw call stack:
(0x18a83709c 0x196c8dd78 0x18a83bd14 0x18a839a7c 0x18a7594ac 0x18d800a68 0x1000e76b4 0x18d70c2c0 0x18d70c044 0x18d7137dc 0x18d710ab0 0x18d78488c 0x1000e674c 0x18d7815cc 0x18d780fb8 0x18d77a9a0 0x18d70d530 0x18d70c720 0x18d77a0b0 0x190119128 0x190118c54 0x18a7f6fc8 0x18a7f6f28 0x18a7f514c 0x18a735b38 0x18d7792d4 0x18d7740e8 0x1000e7cd0 0x197277aa0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Button:
RegisterViewController.h:
- (IBAction)btn_reset:(id)sender;
RegisterViewController.m:
- (IBAction)btn_reset:(id)sender {
}
And I've tried to put a button in the scrollView instead but then i cant access to "textfields" info, so I need to put a button in the "RegisterView".
Declare the RegisterViewController *registerView in #interface or create a #property for it.
#interface XXXXX ()
{
RegisterView *registerView;
}
OR
#property (nonatomic, strong) RegisterView *registerView;
In your case MainViewController is not retaining registerView object. For retaining the instance you need to add it as the childViewController of your MainViewController,
RegisterViewController *registerView = [[RegisterViewController alloc] init];
[self addChildViewController:registerView];
[scrollView addSubview:registerView.view];
No need of declaring any additional properties just for that, UIViewController will retain all its childViewControllers.
I'm guessing that its related to not retaining the "RegisterView" class. While you are adding its view to scrollView the class itself is released as soon as the end of the function is reached.
Try declaring your RegisterView class as a property as follows:
#property (nonatomic, strong) RegisterView *registerView;
And to add it in the scroll view:
self.registerView = [[RegisterViewController alloc] init];
[scrollView addSubview:self.registerView.view];
I'm working on an application in which I'm adding UIBarButtonItem to UIToolbar dynamically. When User clicks on a bar button. I'm changing it's tint color to red.
But for some bar buttons it's not working and the application is crashing.
Here is my code:
#interface myClass : UIViewController
#property (nonatomic, retain) NSMutableArray *barButtonItems;
#property (nonatomic, retain) IBOutlet UIToolbar *toolBar;
#end
#implementation myClass
#sythesize barButtonItems, toolBar;
- (void)viewDidLoad
{
[super viewDidLoad];
barButtonItems = [[NSMutableArray alloc] init];
[self initToolBar];
}
//To set the tool bar
- (void)initToolBar
{
[self addBarItem:#"PlantDetails" actionName:#"createPlantDetails:"];
[self addBarItem:#"ElectricalEquipmentInventory" actionName:#"createInventory:button:"];
toolBar.items = barButtonItems;
}
//Create bar button item
- (void)addBarItem:(NSString*)barButtonName actionName:(NSString*)methodName
{
UIBarButtonItem *plantDetails = [[UIBarButtonItem alloc] initWithTitle:barButtonName style:UIBarButtonItemStyleDone target:self action:NSSelectorFromString(methodName)];
[barButtonItems addObject:plantDetails];
[plantDetails release];
plantDetails = nil;
}
//Changes the barbutton tintcolor when user selected
-(void)changeSelection:(UIBarButtonItem *)button
{
NSArray *tempArray = toolBar.items;
for(int loop = 0; loop<[tempArray count]; loop++)
[[tempArray objectAtIndex:loop] setTintColor:[UIColor blackColor]];
[button setTintColor:[UIColor redColor]];
}
//First bar button method
- (void)createPlantDetails:(UIBarButtonItem *)button
{
[self changeSelection:button];
NSLog(#"createPlantDetails");
}
//second bar button method
- (void)createInventory:(int)selectedIndex button:(UIBarButtonItem *)button
{
[self changeSelection:button];
NSLog(#"createInventory");
}
#end
Here my issue is the bar button with only one parameter in it's selector is working perfectly (createPlantDetails) but when I click on the bar button which have two parameter in it's selector (createInventory) the application is crashing on [button setTintColor:[UIColor redColor]]; of changeSelection method.
Crash log is something like: touches event have no method like setTintColor .
I searched a lot but couldn't find a solution. Please help me.
Thanks in advance
The method for the action property has to have one of the following three forms:
- (void)methodName;
- (void)methodName:(id)sender;
- (void)methodName:(id)sender withEvent:(UIEvent *)event;
You can't use any arbitrary format or custom parameters (the button wouldn't know what to pass for them).
The createPlantDetails: method works because it matches the second form.
The createInventory:button: method fails because it doesn't match any of the expected signatures.
Since your method has two parameters, when the button calls the method, the button passes a UIEvent object in the second parameter which in your method is named button.
In changeSelection:, it crashes when it tries to call setTintColor: because button is really a UIEvent and not the UIBarButtonItem (ie. the sender).
The error is not every time.
i had try to clean and rebuild app,but it is also.
error:
*** Assertion failure in -[UIWindowController
transition:fromViewController:toViewController:target:didEndSelector:],
/SourceCache/UIKit_Sim/UIKit-2372/UIWindowController.m:211
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Attempting to begin a modal transition from
<UINavigationController: 0x9190730> to <UINavigationController: 0x83a9dc0>
while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear
to know the current transition has completed'
*** First throw call stack:
(0x213d012 0x1e85e7e 0x213ce78 0x191bf35 0x10b8d05 0xeb74f3 0xeb7777 0x10be033f
0xeb77b7 0x104f0 0x1e996b0 0x18c6035 0x20c0f3f 0x20c096f 0x20e3734 0x20e2f44
0x20e2e1b 0x36437e3 0x3643668 0xdcd65c 0x1133f2 0x25c5)
libc++abi.dylib: terminate called throwing an exception
BaseController.h:
#class LoginViewController;
#interface BaseController:UIViewController
//...
#end
BaseController.m
#implement BaseController
//...
-(void)showLoginForm
{
UIViewController* loginx =(UIViewController*) [[LoginViewController alloc] init];
UINavigationController* navx = [[UINavigationController alloc] initWithRootViewController:loginx];
[navx.navigationBar setBackgroundImage:[UIImage imageNamed:#"title_bar.png"] forBarMetrics:UIBarMetricsDefault];
//added
//i can use #try to catch it
[self presentModalViewController:navx animated:YES];//<====it is error
[loginx release];
[navx release];
}
LoginViewController:
#interface LoginViewController:BaseController
//...
#end
otherViewController:
i will judge the user is login or not in this controller.if it is not login,i will call showLoginForm
//#interface otherViewController:BaseController
//......
-(void)viewDidLoad
{
//make a thread to call isLogin
}
-(void)isLogin
{
BOOL logined = YES;
//......
if(!logined)//i take this on MainThread,it has error also.
[super showLoginForm];
}
MainViewController:(it is the Main ViewController to show otherViewController)
//#interface MainViewController:BaseController
//......
-(void)buttonAction
{
otherViewController* other = [[otherViewController alloc] init];
UINavigationController* navigation = [[UINavigationController alloc] initWithRootViewController:other];
[self presentModalViewController:navigation animated:YES];
//release
}
so ,Who knows how this is going on?
In otherViewController.m you should move the code that calls the isLogin method to
- (void)viewDidAppear:(BOOL)animated
This will make sure that the transition for the presentation of the otherViewController will have finished when [self presentModalViewController:navx animated:YES]; is called.