Here it is: I am working with some legacy code. So I can't really (feasibly) change the architecture at this point.
I have one file that creates an array of strings, in VC1:
NSMutableArray *arrButtons = [NSMutableArray array];
[arrButtons addObject:data];
[arrButtons addObject:share];
[VC2 showButtons:arrButtons];
Then on my VC2 code , I have:
-(void)showButtons:(NSMutableArray *)arrButtons {
for (int i=0; i<arrButtons.count; i++) {
UIButton *btn = [_popupView viewWithTag:i+5000];
[btn setTitle:[arrButtons objectAtIndex:i] forState:UIControlStateNormal];
//this is the code I am trying out, I just need to addtarget to data, not the rest of the array.
if ([arrButtons containsObject:data]) {
//this is adding to all buttons, not just data. Figure out a way to add this action to only data.
btn.[index: data]
[btn addTarget:self action:#selector(arrayButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
}
It would make sense to just add the target in VC1 when we first add it to the array. BUT I can't because when it is created, it is just a string.
I'd like to point out that the button DOES show up on screen. But I don't know how to access that specific button in the array to add a target to it.
The best solution I can come up with is that I need to addTarget but if anyone else has any pointers or ideas on how something like this can be resolved, I would really appreciate it.
p.s. I know how to connect IBActions from IB, the problem is this is a button created 100% programmatically, and when created, it is really just a string, not a button. So addTarget is not available.
I think that the problem in VC2 is in:
if ([arrButtons containsObject:data]) {
If I'm understanding this correctly, arrButons will always contain the data string. Assuming that data is the variable representing title of the button you want to add a target to, I'd put this instead:
if ([arrButtons[i] isEqualToString: data]) {
Please let us know how it goes.
In my IOS app I have a button that the user can assign a custom action to, but I don't know what the best way is to dynamically set actions to the button. My first idea was to change the [sender tag] of the button, and then after being pressed it will read a series of "if" statements that say "if (sender tag=1){then do this} if(sender tag=2){then do this}, and so on." But the problem with this is that i will have thousands of possible actions, so if someone chooses an action who's sender tag is 2000, then the method will have to read through every single question before it reads the "if (sender tag=2000)", which is a waste of time. How can I dynamically set an action to a Button?
You have lots of options. Buttons support target/action, so you can change the button's action using
removeTarget:action:forControlEvents: and addTarget:action:forControlEvents:.
Or you could use tags as you say. Tags are numeric, so you could use a switch statement rather than a chain of if/then/else. Better yet, though, would be to use an NSArray of blocks and invoke the appropriate block for the user-selected action.
Example:
In your header:
typedef void (^myButtonBlock)(id sender);
In your implementation:
myButtonBlock aBlock = ^(UIButton *sender)
{
NSLog(#"Button block triggered for button with tag %d", sender.tag);
}
NSMutableArray *blocksArray = [NSMutableArray new];
[blocksArray addObject: aMethod];
I am having a IBAction for 4 buttons which represent 4 available answers and each time I press a button I read the sender ID, then it figures out if answer is correct and so on.
Now I have a special case, when another button (nothing in common with those 4 buttons) is pressed, it waits for two of these answers, checks if one of them is correct( i know how to do that) and then the program continues running.
So for now, I have :
- (IBAction)answerButtonPressed:(id)sender {
NSString* answer= [[sender titleLabel] text];
//checks if answer key is equal to a1 ( which is always the correct answer )
if([[currentQuestionUsefulVariable valueForKey:#"a1"] isEqualToString:answer] ){
correctQuestionsCount1 ++;
}
//answer is wrong
else{
wrongQuestionsCount1 ++;
}
}
As you see I store the answer string in a variable called answer
And again - All I need is to store two answers and check the two of them when this special button is pressed. I will of course put a boolean variable to indicate when it is pressed and it will do the work.
EDIT:
The two answer thing is when I press a specific joker button and it gives the advantage to the user to chose two of four available answers. This is why I need to do that. For any other cases I need only one answer at a time.
Any ideas ?
Well you're going to need an instance variable, the value of which persists throughout the lifetime of the object, and perhaps using a mutable array of the answers is the way forward:
#interface MyViewController ()
{
NSMutableArray *_correctAnswers;
}
It must be initialised in viewDidLoad (other options are available):
-(void)viewDidLoad {
[super viewDidLoad];
_correctAnswers = [NSMutableArray new];
}
and then start collecting correct answers into this array:
- (IBAction)answerButtonPressed:(id)sender {
NSString* answer= [[sender titleLabel] text];
//checks if answer key is equal to a1 ( which is always the correct answer )
if([[currentQuestionUsefulVariable valueForKey:#"a1"] isEqualToString:answer] ){
[_correctAnswers addObject:answer];
}
//answer is wrong
else{
wrongQuestionsCount1 ++;
}
if ([_correctAnswers count] == 2) {
// Do something? It's not exactly clear what you want to do
// when 2 correct answers have been given.
}
}
Note: you can dump correctQuestionsCount1 as that will be [_correctAnswers count] now. Also you will need to reset the array at some point.
Note 2: You could also start collecting incorrect answers as well, for some analysis or perhaps to disable that button so the user cannot repeatedly answer the question wrong.
I have UIButtons programatically created. Now, I created an method to trigger for the button like so:
-(void)createButton {
//code to create button
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender {
if([tag sender] == 0) {
posX = 380;
} else if(....... //set posX to different values
}
[self.myScroll setContentOffset:CGPointMake(poxX, 0) animated:YES];
That's pretty much what the buttons do aside from loading data. Basically, I am using the buttons as tabs. If I tap on a button, it slides to the center. In one of these buttons, there's an "update buttons" button where I can add and remove more buttons. If I tap on one of the buttons, it would automatically be removed and if I tap add, one would automatically add. There's no problem with that. The thing is, I want to retain the "update buttons" button centered as it is technically still the selected button. Here's how the method inside the view for update buttons:
-(void)updateButtons {
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
id indexId = [NSNumber numberWithInteger: index];
//this following line causes the app to crash because it does not recognize the indexId I'm trying to set
[self myAction:indexId];
}
Everytime I execute the updateButtons function and myAction is triggered, the app crashes with an uncaught exception. So my question is, how can I properly pass an id to an action method?
Precise answer to your question is: pass nil for the sender parameter:
[self myAction:nil];
-(void)createButton
{
//code to create button
myButton.tag = 1;
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender {
//do something here
}
you are trying to pass the NSNumber object but you need to pass the UIButton object to myAction: method, I think you should create the UIButton object in the .h file and add tag to the button and pass the reference of that button object to the method
-(void)updateButtons
{
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
if(myButton.tag == index){
[self myAction:myButton];
}
}
-(void)updateButtons {
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
mybutton.tag=index
[self myAction:nil];
}
then in
-(void)myAction:(id)sender {
//do something here
int index=[sender tag]; //this is your index
}
id in Objective-C just means any object—it's used to avoid specifying a certain type of object. When using target-action, the first argument is the sender, or the object that sent the action. For the buttons, the sender would be an instance of UIButton.
I would recommend adding NSLog(#"%#",sender); to your action method to see what type it is each time the method is called.
So what should be passed as the argument? Well, it depends on what myAction does, and you'll have to share that code to get more details on this. If myAction doesn't use the sender argument, you can safely pass nil as other answerers suggest.
Note that when you use target-action, if you're not using the sender argument, you can leave it off altogether. Just declare your method like this:
-(void)myAction
{
// code here
}
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
Instead of above, replace this below line
[mybutton addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventTouchUpInside];
Try this:
-(void)createButton
{
//code to create button
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender
{
//do something here
}
-(void)updateButtons
{
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
id indexId = [NSNumber numberWithInteger: index];
[self myAction:mybutton];//if u r using my button else u can use
[self myAction:nil]
}
First off, seems weird that you are using -addTarget:action:forState. UIButton is a concrete subclass of UIControl and as such, instead uses the method -addTarget:action:forControlEvents:
which can take a variable number of or-able UIControlEvent enums. Specifically, the ones you want for a UIButton would be of the subtype UIControlEventTouch....
You should be crashing right away just cause that method doesn't exist.
That being said, without looking at your code we can't really tell you exactly which line inside your -myAction: method causes the crash. But the important point here is, the method whose signature / selector you are registering via the target-action pattern doesn't necessarily even need to have a parameter of type id, UIButton or anything.
Basically, when you do -addTarget:action:forControlEvents:you are telling a subclass of UIControl that when it undergoes the desired event/s, it should invoke a method in the object you pass to the first parameter of -addTarget: (the target), whose signature is the selector you pass to action:. The selector you pass to this parameter can have one or zero parameters in turn. If you pass in one with none (say, your action method is -doSomething), when the UIControl responds to the UIControlEvent it'll simply call your method and that's that. If instead, you pass in a selector that takes one parameter, the UIControl that triggered the action is automatically passed in to that parameter, cast to whatever type your action method's parameter type is.
So for instance:
if you register like so:
[self.readingListButton addTarget:self action:#selector(doSomething) forControlEvents:UIControlEventTouchUpInside];
your action method would look like this:
- (void)doSomething
{
// Notice we don't have a parameter and so we are limited to doing stuff
// that does not require the sender to be passed in.
NSLog(#"do Something!");
}
If instead you register like this (notice the : in the selector):
[self.readingListButton addTarget:self action:#selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
You could go:
- (void)doSomething:(id)sender
{
// If the button triggers the method, sender will be an id pointer holding the memory
// address of a UIButton and we could cast it to UIButton like so: UIButton *b = (UIButton)sender
// Then again, sender might not be a button. As long as we stick to stuff that any object
// will respond to we are fine though.
NSLog(#"do Something! %#", sender);
}
or:
- (void)doSomething:(UIButton*)sender
{
// We straight out assume it is a button:
NSLog(#"do Something! %#", sender);
}
So as you can see, the choice of parameter vs no parameter and the parameter type is sort of up to you.
Now my guess is the reason why your code crashes is because when you manually call the action method you are passing it an NSNumber instead of a UIButtonand inside the method you do something with the parameter that treats it as a UIBUtton.
Think about this for instance:
- (void)doSomething:(UIButton*)sender
{
// Our parameter is a button, so we can totally change its state:
sender.selected = !sender.selected;
}
If you pass in a button to the above method, it'll work just fine. However in your second case, you pass a number. and what really happens is this:
NSNumber *n;
UIButton *b = (UIButton*)n;
[target doSomething:b];
And inside -doSomething:
- (void)doSomething:(UIButton*)sender
{
// Our parameter is a button, so we can totally change its state:
sender.selected = !sender.selected;
}
But sender now is not really a button. It's a number cast to button. The minute the code above tries to change the state, it attempts to call the method -setState: on a NSNumber which does not have that method, and so you'd get a classic exception along the lines of:
unrecognized selector sent to instance.
So bottom line, if you don't require to pass in the button or any info into the target method, just define it without parameters or, if you do require a parameter, either make the parameter polymorphic (type id) and inside your method check to see what it is and act accordingly, or stick to a parameter of type UIButton but them make sure you only call it passing in buttons.
I currently have an array of buttons being generated which works great. Outside of this on touch up inside each button calls on a -(void)onTouch function where some math is done to determine an action. This all works great except I would like to store a history of the pressed buttons. I've tried many different ways to create an NSMutableArray and store the values of the pressed buttons, but because I can only declare the array within the -onTouch action, every time a button is pressed the array is reset so it never remembers more than one move. If I try to declare the array in my header and synthesize it outside I either get the error that nsmutable array is not a compile time thinger or it doesn't store anything (log output is "(null)". Can someone paste in some code on how to declare an array that can store and append the uibutton tags outside of where the uibutton press event happens? I'll post code later tonight if this isn't clear.
Cheers
You need to not only declare the array, but also initialise it. If you don't initialise, you won't necessarily get a warning, but you will get lots of nil data.
You only want to initialise the array once (as you have noticed) so viewDidLoad is a good place to do it. Another good place is in a custom accessor...
- (NSMutableArray*)historyArray
{
if (!_historyArray) {
_historyArray = [[NSMutableArray alloc] init];
}
return _historyArray;
}
Now the first time you try to [self.historyArray addObject:sender], the accessor will note the absence of a historyArray, create one and return it. Next time round it won't be recreated as it already exits.
#property(nonatomic,retain)NSMutableArray *tapCollection;
-(void)viewDidLoad{
self.tapCollection = [[NSMutableArray alloc] init];
}
-(void)onTouch:(id)sender{
UIButton *btnTapped = (UIButton *)sender;
[self.tapCollection addObject:[NSNumber numberWithInt:btn.tag]];
}