How does (id)sender work in cocos2d? - objective-c

The following code works good for me:
In the init method of a menu layer:
CCMenuItemFont *item1 = [CCMenuItemFont itemWithString:#"Level 1" target: self selector: #selector(startLevel:)];
item1.userData = (__bridge void*) ([NSNumber numberWithInt:1]);
...//create menu and add in the item1
-(void)startLevel: (CCMenuItem *)sender
{
NSNumber *number = sender.userData;
...
}
My questions are:
I didn't pass item1 when call the method startLevel: how does it know that the sender is item1?
is it written into selector? or is it written in cocoa?

CCMenuItem passes itself as parameter to this selector. Details are in CCMenuItem source code.

Regarding omitting passing itself as a parameter, do you mean like...
- (void) pushedStart : (id) sender
{
//start game
}
but you can't do
[self pushedStart];
because it needs a parameter? If so, what you can do this:
id junkID;
[self pushedStart: junkID];
JunkID will initialize to whatever the hell it is an unassigned ID assigns to, so you pass it as a reference and just don't use it for anything, which is good if you want to have a "start game" button but have the game automatically start inside of a timer or whatever else you're doing with your buttons
As a side note, and getting more into the guts of cocoa, the way it KNOWS (and what YOU must not forget) is that colon. When you call a function you put the variable after a colon [self eat: food];
When you put together the menu item you set it up with target:self, which makes the button use itself (not the layer "self" you use when you call [self eatABanana]) as a target. The button push of
menuButton = target:self selector:#selector(pushButton:)
is represented like
[self pushButton:menuButton]
If you forgot that colon, it's the same as calling a function and not passing the variable, which doesn't give a very helpful error message insofar as it doesn't help you locate where the problem is occurring. I've spent hours upon hours chasing down memory crashes resulting from writing #selector(startGame) instead of #selector(startGame:) in those damn menu buttons. I always feel stupid when I finally figure it out.

Related

Call function pointer from selector

I have a NSMenuItem, and I create it using this:
NSMenuItem* nsMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:menuItem->getText()] action: keyEquivalent:#""];
Now I pass in a function pointer, which I want to call when the selector gets invoked.
How do I do this?
I have tried:
id block = [^{
functionPointer();
} copy];
NSMenuItem* nsMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:menuItem->getText()] action:#selector(invoke) keyEquivalent:#""];
[nsMenuItem setTarget:block];
However, the menu item is still grayed out.
How do I pass in a function pointer as a selector?
I'm building a cross platform app, and so basically I call to create a new menu, and my core C++ code will pass in a function pointer for the Menu Item.
First, you need to understand how menus work. Did you read the documentation? Especially the section about enabling menu items is very insightful.
To give a short overview: the selector is the name of a function that will be called when the menu item is invoked. It's not a function, it's the name of a function. In Smalltalk one would say it is the message to be send.
The target is the object that is receiving the message.
The Menu mechanism checks if the target implements the selector function. Since your target is a block, it does not implement any function at all, hence the menu item is always disabled.
What you need do is the following:
store the block to be executed somewhere
add a generic selector like "performBlock" to your menu item
set the class that stores the block as a target
in that class, implement a fuction performBlock and call the block there
You could subclass NSMenuItem and keep the block in there, see this example here.

Obj-C IBAction (id)sender

I have an IBAction like:
- (IBAction)thisThing:(id)sender {
[self doSomething];
}
I would like do this (manually call the IBAction):
[self thisThing];
However I obviously need to do [self thisThing:...];. <- (what the heck goes after the colon?)
I'm not sure what (id)sender is supposed to be. How do call it manually without needing to click the button that it's tied to? I searched for anything about IBAction (id)sender and the results came up completely empty.
what the heck goes after the colon?
Well it depends on how you have written code inside the IBAction. Say for a calculator app, if all buttons are hooked up with the same IBAction then you would need sender (in this case NSButton) to identify which button got touched/clicked.
-(IBAction) buttonClicked:(id) sender {
// sender's identifier or Tag will let us know the number clicked here
[self doSomeThing];
}
But if you had IBActions for each and every button you would not need to be dependent on sender.
-(IBAction) firstButtonClicked:(id)sender;
-(IBAction) secondButtonClicked:(id)sender;
and so on ...
So in the first case if I want to programatically invoke the action I would pass the sender with appropriate attributes set to make sure the correct button got clicked. In second case just pass nil as it does not depend upon sender's value.
While popeye's comment answers your question, here's another perspective.
You have complete control over the action method. If you aren't using the sender parameter for anything in that method, then you do not have to supply it when calling it manually. By not supply it I mean pass nil as the value of the parameter.
Normally, it will contain a pointer to the control that is wired up to the action. If you did want to use if for something, they you would simply cast sender to the type of the control and do whatever with it.
- (IBAction)thisThing:(id)sender
In here,
- denotes the start of a instance method, whereas + means class(static) method.
( .. ) indicate return type. IBAction is actually void. Using IBAction instead of void tells that this method will be associated with UI(.nib) events.
thisThing is the name of the method, followed by parameter list.
In C view point, actual function names is something like thisThing:. That is, number of parameter modifies function name (external linkage).
If you meant to call thisThing: but write [self thisThing], you are calling different (not existing) method.
So, you must write :. What actual value to pass? One can decide this by looking at the method implementation.
If you have the IBOutlet of the button, you can pass that like [self thisThing:btn];
Or simply pass nil, [self thisThing:nil]; (if you are not using sender inside the IBAction)
- (IBAction)thisThing:(id)sender {
}
Is an event handler. That means that it is called when an event is sent by someone. A typical example of an event is a click on a button, it that case, the button sends the event (that means the button, a NSButton instance, is the sender).
Having the sender as a parameter is useful when you use the same event handler for events coming from different sources, e.g.
- (IBAction)buttonTapped:(id)sender {
if (sender == self.myButton1) {
//button 1 was tapped
}
else if (sender == self.myButton2) {
//button 2 was tapped
}
}
If this case, if you want to call the event handler manually, you just call
[self buttonTapped:self.myButton1];
If you don't use the sender parameter, then you can simply call
[self buttonTapped:nil];
However, the parameter is completely optional, so you can eliminate it:
- (IBAction)buttonTapped {
// ...
}
[self buttonTapped];
On a separate note, it's never good to call event handlers manually. Event handlers serve to handle events. If you need to perform the same action manually, separate it from the event handler, e.g.
- (IBAction)buttonTapped {
[self doSomething];
}
instead of calling [self buttonTapped], call [self doSomething]

How to pass parameters using #selector

I need to pass parameters with #selector and here is the method that i need to call using selector:
-(void)clickedInfo:(NSString *)itemIndex{
// some work with itemIndex
}
I know that what i can do is to use an intermediate method as described here.
This approach doesn't work in my case because im adding the target to the uibutton in the cellForItemAtIndexPath method for the collectionView.
The parameter that i need to pass to the clickedInfo method is indexPath.row
and i can not obtain this parameter in an intermediate method.
Thanx in advance
So you want to store some information that can be accessed by the action of a button. Some options are:
Use the tag property of the control. (can only store an integer)
Subclass UIButton and use that class for the button. The class can have a field that stores the information.
Use associated objects (associative references) to attach an object to the button. This is the most general solution.
You can use the performSelector:withObject: selector to pass an object.
Example:
[self performSelector:#selector(clickedInfo:) withObject:myIndex];
- (void) clickedInfo:(NSString *)itemIndex{
// some work with itemIndex
}
Edit: Should be just #selector(clickedInfo:) rather than what I had before.
Edit: Using #newacct 's suggestion, I'd recommend doing something similar to the following:
- (UITableViewCell *)tableView:(UITableView)tableView cellForRowAtIndexPath:(NSIndexPath)indexPath
{
button.tag = indexPath.row;
[button performSelector:#selector(clickedInfo:)];
// or
[button addTarget:self action:#selector(clickedInfo:) forControlEvents:UITouchUpInside];
}
- (void) clickedInfo:(id)sender
{
int row = sender.tag;
// Do stuff with the button and data
}
this is addressed lots of places, but it is easier to answer than to point you there:
[someObject performSelector:#selector(clickedInfo:) withObject:someOtherObject];
where someObject is the receiver and someOtherObject is the parameter passed to clickedInfo

How do I handle a button tap according to Clean Code principles?

I have the following, seemingly simple piece of code handling button taps in an iOS application:
- (IBAction)tapKeypadButton:(UIButton *)sender {
NSString *buttonLabel = sender.titleLabel.text;
if ([buttonLabel isEqualToString:#"<"]) {
[self _tapBackButton];
} else {
[self _tapDigitButton:buttonLabel];
}
}
To completely follow the Clean Code principles by Robert C. Martin, would I need a ButtonTapFactory or something in the same line?
You have two types of buttons, with different behaviors (back button and digit button). To make this code clean, you should have two actions for each type. The type should not be determined by the contents of the text inside the button, but through a semantically meaningful way. (i.e. subclass).
Further, an action method should only contain a call to another method that does the actual logic. Everything else is not testable. In code:
- (IBAction) tapBackButton:(id) sender
{
[self _tapBackButton:sender];
}
- (IBAction) tapDigitButton:(id) sender
{
[self _tapDigitButton:sender];
}
This way you can have unit tests calling your methods without your UI code interfering. Please also note that I removed the label from the call to _tapDigitButton. The digit should not be parsed from the label, but be passed in a more semantically stable way, for example using the tag property.

Why can I not reset a text field within an IBAction associated with it?

I have an IBAction called keyboardResponse associated with a text field called myTextFieldIBOutlet via the "Editting Changed" event handler in the xib:
- (IBAction)keyboardResponse:(id)sender
{
// process this single character - function I wrote else where that works fine.
[self processSingleCharacter:myTextFieldIBOutlet.text];
// clear input text
myTextFieldIBOutlet.text = #"";
}
It's supposed to clear the input after the user types something into it.
I get a run time error with this code in iOS Simulator:
Thread 1: EXC_BAD_ACCESS (code=2, address=0xbf7fff0c)
Why? I had synthesized the IBOutlet myTextFieldIBOutlet already.
if myTextFieldIBOutlet is synthesized, you should change the last line to:
self.myTextFieldIBOutlet.text = #"";
If the textfield you want to clear is the same control that calls this action, you can also use the sender variable you are sending
[sender setText:#""];