I'm implementing a button class in cocos2d, and I want to be able to pass the selector when the button is created. Here is Button.m:
#import "CCButton.h"
#implementation CCButton
+(CCButton*) buttonFromImage:(NSString*)image selectedImage:(NSString*)selectedImage atPosition:(CGPoint)position selector:(SEL)selector_method
{
CCMenuItem *menuitem = [CCMenuItemImage itemFromNormalImage:image selectedImage:selectedImage target:self selector:selector_method];
menuitem.position = position;
CCButton *menu = [CCMenu menuWithItems:menuitem, nil];
menu.position = CGPointZero;
return menu;
}
#end
It inherits from CCMenu. What I want to do is define the selector method wherever create my button. For example, if I have a menu, I want the selector to be in the menu, and assign the selector to the button (in menu.m):
backButton = [CCButton buttonFromImage:#"image1.png" selectedImage:#"image2.png" atPosition:ccp(120,70) selector:#selector(backTouched:)];
[self addChild:backButton z:1];
...
- (void)backTouched:(id)sender {
//do what i want the button to do here
}
This crashes when I touch the button. How do I implement what I want?
Thanks for the help,
Dave
Edit: the error I get is bad pointer, SIGABRT
The target cannot be self. The target has to be the class that implements the button you have created.
When passing in the selector as you create the button, also pass in the target of the button creating class.
In other words, target is the class that contains the method that you pass as the selector.
Hope that made things clear :)
PS: Here's what you should try. Note that your buttonFromImage now takes in a target attribute which is set when you create the backbutton. Also the target that you set in your buttonFromImage is not self, but the target that comes in from the buttonFromImage method.
#implementation CCButton
+(CCButton*) buttonFromImage:(NSString*)image selectedImage:(NSString*)selectedImage atPosition:(CGPoint)position selector:(SEL)selector_method target: (id)target
{
CCMenuItem *menuitem = [CCMenuItemImage itemFromNormalImage:image selectedImage:selectedImage target:target selector:selector_method];
menuitem.position = position;
CCButton *menu = [CCMenu menuWithItems:menuitem, nil];
menu.position = CGPointZero;
return menu;
}
#end
//*************************************************
backButton = [CCButton buttonFromImage:#"image1.png" selectedImage:#"image2.png" atPosition:ccp(120,70) selector:#selector(backTouched:) target:self];
[self addChild:backButton z:1];
//...
- (void)backTouched:(id)sender
{
//do what i want the button to do here
}
There are a couple of things that could be the problem, but none of them have anything to do with passing a selector:
You're passing self (which in a class method is the CCButton class) as the target of the button, but CCButton does not have a corresponding class method, and it's almost certainly not the object that you intend to respond to the action.
Your method says it returns a CCButton, but you're actually returning a CCMenu. Unless CCButton and CCMenu are structurally identical (i.e. CCButton has no instance variables), this is almost guaranteed to cause a crash, and is wrong in any case.
Related
EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")
I have two class files hudlayer.m and actionlayer.m
I have a method named jump in hudlayer.m
And i have a method named jumpone in actionlayer.m
-(void) jumpone {
_heroBody->ApplyLinearImpulse(b2Vec2(_playerVelX/[_lhelper pixelsToMeterRatio], 1.25), _heroBody->GetWorldCenter());
}
and another method called jump in hudlayer.m
-(void)jump {
ActionLayer *aob = [[ActionLayer alloc] init];
[aob jumpone];
}
The problem is when i call the Jumpone method from actionlayer.m my sprite jumps (i.e method called)
My init method of action layer
- (id)initWithHUD:(HUDLayer *)hud
{
if ((self = [super init])) {
[self setupWorld];
}
return self;
}
But when i call jumpone via jump method in from hudlayer.m it fails and my app crashed.
Any help will be appreciated .thanks
the best solution for your problem is to add a tag to hudlayer & action layer
ex: hudlayer.tag=1;
actionlayer.tag=2;
and then just use getChildByTag like this:
[[[[CCDirector sharedDirector]runningScene] getChildByTag:1]jumpone];
Everytime you call jump it creates a new instance of you ActionLayer. And following that, you setup a new world and everything get tangled up. Furthermore its a memory leak.
Make you ActionLayer to an iVar of HUDLayer and call
aob = [[ActionLayer alloc] init];
in the HUDs init method.
Dont forget to release aob in dealloc of the HUDLayer
To clarify my question, my program has three lightbulb on the screen (Customized UIButton)
when any lightbulb is pressed, I programatically generate a UIView with a switch on it
when I turn on the switch, corresponding lightbulb will light up (change its background image)
However, I have trouble accessing this UISwitch since I can't declare it publicly
My code goes something like this:
#property buttonA;
#synthesize buttonA;//all three buttons have their background image set to 'off.png'
- (IBAction)lightBulbPressed:(UIButton *)sender
{
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(1,1, 64, 64)];
UISwitch *mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(0,0,64,64)];
[mySwitch addTarget:self action:#selector(onOrOff) forControlEvents:UIControlEventValueChanged];
[myView addSubview:mySwitch]
[self.view addSubview:myView];
}
So what troubles me is how to program the selector onOrOff, so that it knows which switch is being touched and change the background image of corresponding button accordingly.
Think about your method:
- (IBAction)lightBulbPressed:(UIButton *)sender {
// your method
}
You already know who called it. This piece of information is stored in sender.
So you can save it and use later in onOrOff
By the way, if you are using UISwitch you have to check
UIControlEventValueChanged
and not UIControlEventTouchUpInside.
EDIT: To pass your sender you can store its value to a NSString *buttonTapped declared in your .h file
- (IBAction)lightBulbPressed:(UIButton *)sender {
if (sender == bttOne) {
buttonTapped = #"ButtonOneTapped";
} else if (sender == bttTwo) {
buttonTapped = #"ButtonTwoTapped";
} else if (sender == bttThree) {
buttonTapped = #"ButtonThreeTapped";
}
// your method
}
- (void)onOrOff {
if ([buttonTapped isEqualToString:#"ButtonOneTapped"]) {
// Button One
} else if ([buttonTapped isEqualToString:#"ButtonTwoTapped"]) {
// Button Two
} else if ([buttonTapped isEqualToString:#"ButtonThreeTapped"]) {
// Button Three
}
}
One way to do so, is taht you give them distinct tag numbers in IB, and in - (IBAction)lightBulbPressed:(UIButton *)sender method, get their tag. e.g. NSInteger pressedButtonTag = [sender tag];, and go from there.
Also, instead of alloc/init myView every time user presses a button, you can add that view in IB, add the switch to it, put in the hierarchy of the owner but not the view, and set an outlet to it in .h. Call it whenever you need it, and again, access the switch by tag e.g. ( UISwitch *mySwitch = (UISwitch *)[myView viewWithTag:kSwitchTag]; ) and do whatever you want to do (on or off), add it to the subview and remove it later. This is more efficient.
I have a method that takes a UIButton, modifies its properties and returns a UIButton. However, it doesn't ever seem to be initialized. I'm sure I'm doing something wrong with the memory management here, but don't exactly know how to fix it. No runtime errors occur.
It is called like so...
newGameBtn = [self customButtonFromButton:newGameBtn
withText:[NSString stringWithFormat:#"NEW GAME"]
withFontSize:22
withBorderColor:[UIColor orangeColor]
isSilent:YES];
[dashboardContainer addSubview:newGameBtn];
The method is defined as follows...
- (UIButton*) customButtonFromButton:(UIButton*)button withText:(NSString*)text withFontSize:(int)fontSize withBorderColor:(UIColor*)borderColor isSilent:(BOOL)isSilent {
button = [[[UIButton alloc] init] autorelease];
// Set properties from parameters
// Other conditional custom stuff here
return button;
}
NOTE: newGameBtn is of type UIButton* and is initialized with:
newGameBtn = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
Another option might be to subclass UIButton, but I figured I'd try to fix this since I've already walked down this path.
You should use +[UIButton buttonWithType:] when creating buttons to get a properly initialized button.
Most classes are not properly initialized by the default -[NSObject init] method. So please look at the class reference, or superclass reference, for a usable initialization method.
In this case you should also set a frame.
You don't modify this button with your method, you're creating a completely new one with alloc-init!
If you want to change the button in your first argument just remove the first line of your method
I have a toolbar with various image buttons, created in Interface Builder.
I'd like to be able to programmatically replace one of the buttons with an activity indicator when pressed, and then put back the original button but change its color from white to yellow when the activity completes.
Is this possible with an IB built toolbar or must I look at building the whole toolbar programmatically and custom views?
Here is an example of what I did in a similar situation. I wanted to build the toolbar using Interface Builder but toggle one of the BarButtonItems based on whether or not it was "checked". In this example, there are a few key things to note. The following 2 member variables are defined for the class in the header file:
NSMutableArray *toolbarItems;
IBOutlet UIToolbar *toolbar;
NSUInteger checkUncheckIndex;
When I want to update the checked status, I call this function... Please note that there is a selector defined called checkUncheckClicked that is called when the particular button in the UIToolbar is clicked. And the UIToolbar is set up as an IBOutlet to toolbar. Alternately, you could hook up the UIBarButtonItem as an outlet itself and use that as your logic to identify the index of the button, or for that matter, you could hard-code the index of the button if things won't change over time. Finally, there is a checked.png and unchecked.png to alternate between that is included in the project.
- (void)updateBarButtonItemChecked:(BOOL)checked {
if (toolbarItems == nil) {
toolbarItems = [[NSMutableArray arrayWithArray:toolbar.items] retain];
checkUncheckIndex = -1;
for (NSUInteger i = 0; i < [toolbarItems count]; i++) {
UIBarButtonItem *barButtonItem = [toolbarItems objectAtIndex:i];
if (barButtonItem.action == #selector(checkUncheckClicked)) {
favoriteIndex = i;
break;
}
}
}
if (checkUncheckIndex != -1) {
UIBarButtonItem *barButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:checked ? #"checked.png" : #"unchecked.png"]
style:UIBarButtonItemStylePlain target:self action:#selector(checkUncheckClicked)] autorelease];
[toolbarItems replaceObjectAtIndex:checkUncheckIndex withObject:barButtonItem];
toolbar.items = toolbarItems;
}
}
And, of course toolbarItems and toolbar should be released in your dealloc for the class.
Hope this helps!
Here is the approach I used
It seemed to be much simpler to manage the toolbar entirely programatically so ....
In your view controller declare 1 or more sets of UIBarButtonItem items as property items also declare and hookup the toolbar as a UIToolbar property. Also declare 1 or more arrays to hold the items.
In the implementation
In viewDidLoad alloc and set your UIBarButtonItems for example
playButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemPlay
target:self
action:#selector(handlePlayClick)];
Flexible buttons (for alignment etc) are declared like this
flexButton1 =[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil action:nil];
There are several initMethods to handle the different types of buttons toolbars support. All follow a syntax similar to the above. Worth noting is the target and action settings. Target: would normally be self, action is the name of the function that button should trigger.
After alloc'ng your UIBarButtons add them to an array using initWithObjects.
Then to assign the buttons to the toolbar you would call
[toolbar setItems:<array name>];
Dont forget to dealloc your UIBarButtons and arrays at the end of your code.
Hope this helps. If you need more code let me know.
Rich D.
Swift
Much has changed since these answers have been posted. Here is a Swift 2.0 solution.
It matters not how the original UIBarButtonItem you are replacing has been created programmatically. All you need is the UIToolbar outlet. To replace, say, the 2nd item from the left, do this:
var toolbarItems = self.toolbar.items
let newItem = UIBarButtonItem(barButtonSystemItem: .Play, target: self, action: "play")
toolbarItems![1] = newItem
self.toolbar.setItems(toolbarItems, animated: true)
Swift has a much easier way. In my case, I am switching the play button with the pause button every touch.
#IBAction func playandPause(sender: UIBarButtonItem) { // Here we are creating an outlet. Hook this up to the bar button item.
for (index, button) in toolbarItems!.enumerate() { // Enumerating through all the buttons
if button === sender { // === operator is used to check if the two objects are the exact same instance in memory.
var barButtonItem: UIBarButtonItem!
if mediaplayer.playing {
mediaplayer.pause()
barButtonItem = UIBarButtonItem.init(barButtonSystemItem: .Play, target: self, action: #selector(playandPause(_:)))
}else {
mediaplayer.play()
barButtonItem = UIBarButtonItem.init(barButtonSystemItem: .Pause, target: self, action: #selector(playandPause(_:)))
}
toolbarItems![index] = barButtonItem // Replace the old item with the new item.
break // Break once we have found the button as it is unnecessary to complete the rest of the loop
}
}
}
I think it should be possible. You might either try creating an IBOutlet for that specific button in your ViewController class, and connect the button from IB to that outlet, or you can use the items property of that UIToolbar instance (you do have a reference to that, don't you?) and find the appropriate item in there, create a new NSArray with modified items, and set it back on that toolbar.items property.
HTH
A note on this - the toolbar will strip out any color in your icons, so you still won't be able to make it yellow. You'll need to change the image shape to indicate "on" instead.
Alternatively you'll need to load your BarButtonItems with UIButtons (use the initWithCustomView) and set the image of the button appropriately.
HTH
Try:
- (void)setTitle:(NSString *)title forItem:(int)item ofToolbar:(UIToolbar *)tb {
NSMutableArray *newItems = [tb.items mutableCopy];
UIBarButtonItem *old = [newItems objectAtIndex:1],
*titled = [[UIBarButtonItem alloc] initWithTitle:title style:old.style target:old.target action:old.action];
[newItems replaceObjectAtIndex:1 withObject:titled];
tb.items = newItems;
[titled release];
}
For the entire toolbar that you created in IB (that is to say, not the individual toolbar items), create an IBOutlet:
#IBOutlet weak var toolbarThatYouMade: UIToolbar!
This is an array of individual toolbar items, so you would access the leftmost member (for example) with a [0] index:
toolbarThatYouMade.items![0].image = UIImage(named: "New Image")
This code assumes that you have an image named "New Image" in your assets.
You can then trigger an image change for a toolbar item without having to access that specific item itself; for example, if you were using this for something like a media player, you could toggle pause/play images from:
a) the Pause/Play button itself,
b) the Stop button,
c) when you are notified of a change of player state,
or
d) something else entirely. (Be brave! Be bold! Be something else that begins with 'b'!)