UIButton created from an array of strings needs a target - ios

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.

Related

Updating UILabel.Text inside of a for loop after a button is pressed

I have a UIViewController, and in that I have an array of questions that I pull from a sqlite3 query. I am then using a for loop to iterate through each question in the array to change the UILabel.text to display the question on the screen. This is actually working for the first question in the array!
I then have four buttons for answers. I want to make it so if one of the buttons is pressed, the answer is saved and the next question in the loop updates the UILabel.text.
The four answers never change as it is more of a survey than answers, so one of the answers is "I agree" or "disagree", so the button text never changes.
Is this possible?
I have been on here and Google to find a way to link the button pressed with completing each iteration of the loop without any luck.
Why are you iterating through questions and changing UILabel's text? Shouldn't be it changed only on tapping one of the survey buttons?
If I got you correctly, you should do following:
1) Declare three properties in your controller: NSArray *questions, NSMutabelArray *answers, NSInteger currentIndex;
2) Init/alloc them in viewDidLoad (except currentIndex, of course, set it to 0).
3) Fill up questions array with your question strings.
4) Set text to UILabel, label.text = questions[currentIndex];
5) create IBAction method and link it to all survey buttons.
6) in IBAction method, insert button's title to answers array and show next question.
- (void)viewDidLoad {
[super viewDidLoad];
self.questions = {your questions array};
self.answers = [[NSMutableArray alloc] init];
self.currentIndex = 0;
}
- (IBAction)btnClicked:(id)sender {
UIButton *btn = (UIButton *)sender;
NSString *title = btn.titleLabel.text;
[self.answers addObject:title];
currentIndex++;
label.text = questions[currentIndex];
}
I hope you will understand the code.
In short, yes this is possible.
You'll first want to keep track of the question that your user is currently on. You can do this by storing an index in an instance variable or, if you plan on allowing the user to open the app and start from where they left off, you can use NSUserDefaults, which writes to disk and will persist.
// In the interface of your .m file
int questionIndex;
// In viewDidLoad of your controller, however this will start for index 0, the beginning of your questions array
questionIndex = 0
By storing the index in NSUserDefaults, you can grab it in ViewDidLoad, and start from where the user last left off:
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:questionIndex] forKey:#"questionIndex"];
To store your answer, you could add a method for your buttons called answerTapped:,
- (void)answerTapped:(UIButton *)answerButton
{
// Grab the answer from the text within the label of the button
// NOTE: This assume your button text is the answer that you want saved
NSString *answer = answerButton.titleLabel.text;
// You can use your questionIndex, to store which question this answer was for and you can then take the answer and store it in sqlite or where you prefer...
}
You can add this method to your buttons like so
[answerButton addTarget:self action:#selector(answerTapped:) forControlEvents:UIControlEventTouchUpInside];
You could then write a method to increment questionIndex now that an answer button has been pressed.
- (void)incrementQuestionIndex
{
// Increment index
questionIndex += 1;
// Update and save value in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:questionIndex] forKey:#"questionIndex"];
}
You could then call a separate, final method to update the question label.
- (void)updateQuestionLabel
{
// Grab the question using the index (omit this line and go straight to the next if storing the index in an iVar)
questionIndex = [[[NSUserDefaults standardUserDefaults] objectForKey:#"questionIndex"] integerValue];
// Grab the question using the index. Assumes you have a questions array storing your questions from sqlite.
NSString *question = [questions objectAtIndex:questionIndex];
// Update the question UILabel
[questionLabel setText:question];
}

Making a button read a string from an array

I'm a beginner developer for iOS. I'm using objective c to develop an app. I want a button to be able to read a string from an array. What I mean by this is NOT to set the string as what the button displays. I want it to be able to get the string so I can use AVSpeechSynthesizser to read the string aloud. Thanks in advance
You don't provide any code or details about your problem.
I have to make assumption that you just want to read something from array while button is tapped.
Either using storyboard to create the button object and its handler or manually add the handler.
Let's say you have the button object named 'exampleButton', if you choose manually add the handler,
[exampleButton addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
Let's say your array name is exampleArray, and you want to access the first element.
EDIT:
use firstObject instead objectAtIndex:0 since the latter one will crash the app if the array is empty.
- (IBAction)buttonTapped:(id)sender {
// becareful, if the array is empty, firstObject will return nil.
// If you use [exampleArray objectAtIndex:0], it will crash
id obj = [exampleArray firstObject];
if ([obj isKindOfClass:[NSString class]]) {
NSLog(#"%#", obj); // now you have the string object.
}
}
You have to learn more if you still cannot get yourself started with above code.

How to dynamically assign method to button

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];

Passing id to button action method in Objective-C

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.

NSMutableArray UIButton Press History

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]];
}

Resources