Calling a function through UIButton - ios

I am not sure if my code is wrong, but there are no errors while compiling.
I have a refresh button where it refreshes a TableView and here's the following code:
- (IBAction)refreshButton:(UIButton *)sender {
UIButton *refreshButton = [UIButton alloc];
[refreshButton addTarget:self action:#selector(scanBLEDevices:) forControlEvents:UIControlEventTouchUpInside]; }
- (void)scanBLEDevices:(id)sender {
[manager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:BLEService]] options:nil];
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(stopScan:) userInfo:nil repeats:NO];}
Is there any mistake in this code? I am unsure about this second line, whether this is allowed:
UIButton *refreshButton = [UIButton alloc];

There is no "mistake" in your code, as, like you stated, it complies. The "mistake" here is more than syntax, it's context.
If you are expecting your button to fire and perform your scan,
then you should be calling your method scanBLEDevices: inside your
IBAction method refreshButton:.
In your snippet, by creating a new UIButton in your refresh button method, you are merely assigning an action to a button that has not been initialized yet, and providing no opportunity for the button to be fired (it is created and exists only within this method).
Assuming you have attached your action method correctly in your storyboard, I suggest replacing your simple IBAction method with the following:
- (IBAction)refreshButton:(UIButton *)sender {
[self scanBLEDevices:sender];
}
Because your scanBLEDevices: method requires an id sender, you can pass your button along (since after all you don't use it in the BLE method anway lol).
Hopefully this should help point you in the right direction. Happy coding!
NOTE: If you are unsure about when / where allocation is allowed here, I suggest reading up on some common practices concerning IBActions, targets, and senders when using UIButton elements in your code.
UIButton - UIKit | Apple Developer Documentation

Related

UIButton created from an array of strings needs a target

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.

UIButton won't enable under some circumstances

in my application i'm using a UIButton (myButton) wich i added via Storyboard.
So, when tapping the button an IBAction gets called:
-(IBAction)buttonAction:(UIButton*)sender{
[myCustomClass doSomeCrazyStuff];
[sender setEnabled:NO];
}
As you can see, i'm calling a method from myCustomClass ( myCustomClass is a REST-Client for my web-service).
The viewController the button lays in is delegate of myCustomClass.
There are two delegate methods implemented, one for success and one for error.
-(void)requestSucceeded{
/* If the request succeeded i want the button to be enabled again, and it's selected
state inverted */
NSLog(#"This gets called");
[myButton setEnabled:YES];
[myButton setSelected:!myButton.selected];
}
This works totally fine: i press the button, stuff is done on myCustomClass, request succeeds, button is set to inverted selected state.
But now for the other delegate method:
-(void)requestFailed{
/* If the request failed i want the button to be enabled again, and it's selected
state stays the same */
NSLog(#"That gets called");
[myButton setEnabled:YES];
}
If requestFailed gets called, the console prints That gets called as expected, but the button stays disabled... and i don't know why.
I tried other things in requestFailed like:
[myButton setHidden:YES];
Just to see if the reference to myButton is working...
And it is.
Probably i'm missing something right now, but i can't figure it out.
Thanks for your help.
EDIT:
I don't think requestFailed could be called from a different thread (as #gonji-dev mentioned), since both requestSucceeded and requestFailed are called from the same method.
In my doSomeCrazyStuff method i set up a completion block wich handles connection success and error. If an error occurred it gets handled in another class. If the connection succeeded i'm asking for HTTP status codes to decide wether requestFailed or requestSucceeded will be called.
Similar situation as in https://stackoverflow.com/a/31952060/218152.
Are you absolutely positive that you are invoking:
[myButton setEnabled:YES];
on the main thread?
The industry standard, when manipulating the UI in response to notifications or multithreaded environment is:
dispatch_async(dispatch_get_main_queue(), ^{
// update UI
});

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.

Determine which UIButton fired event in Objective C

I have a handful of UIButtons that when pressed fire the method, (IBAction)buttonPressed:(id)sender. Right now I have a document label set for each (btnPlay, btnStop, btnPause), but I don't believe I can access this in Objective C. Is there something I can set in xcode that acts as a variable so when buttonPressed() is fired I know which button (btnPlay, btnStop, or btnPause) fired the event?
You should change your IBAction to something like the below
-(IBAction)buttonPressed:(UIButton *)button {
if([button.titleLabel.text isEqualToString:#"Start"]){
//Do Stuff
}
}
In this way you can access the sender as a button directly with no issues or type casting required, you can then use the isEqualToString method to check the title of the button and run code inside the if statement.
You might also like to consider using the tag property which pretty much all Interface Objects have.
if(button.tag == 1){
//Do Stuff
}
Switch statements are also a nice clean way of handling different events..
switch (button.tag) {
case 1:
// Do Something
break;
default:
// Do Default Action
break;
}
you can define which method has to be called when the button pressed after #selector in this case playVideo method.
[videoButton setTitle:#"play video" forState:UIControlStateNormal];
[videoButton setBackgroundImage:nil forState:UIControlStateNormal];
[videoButton addTarget:self action:#selector(playVideo:)forControlEvents:UIControlEventTouchUpInside];
That's what the sender argument is there for - you can compare it against each of your buttons in a chain of if statements to see which one sent that message.
Every UIButton has a titleLabel property, which is a UILabel. Check sender.titleLabel.text and compare it against the three strings.
Alternatively, you can also assign each button a tag (generally an integer), either through the Attributes Inspector in Xcode, or using the tag property in code. Then check sender.tag in your action method.

Unable to cancel NSTimer that executes blocks

I know that these NSTimer questions have come up numerous times, however since none seem to involve executing blocks that change the UI, I figured this is still an original question.
I have a subclass of UIButton that, for convenience sake (me, coming from an Android background), has an onClick and onHoldClick function. onClick simply takes a block and executes it in the selector that responds to UIControlEventTouchUpInside. The click function works great. For example:
[myButton setOnClick:^{
NSLog(#"clicked");
}];
The hold click functionality is not working so well.
[myButton setOnHoldClick:^{
NSLog(#"still holding click...");
}];
This listens for the UIControlEventTouchDown event, and performs a task after a delay:
- (void)clickDown:(id)sender
{
isClicked = YES;
[self performSelector:#selector(holdLoop:) withObject:nil afterDelay:delay];//For the sake of the example, delay is set to 0.5
}
The hold loop runs a repeated timer on another function, which handles the block execution (the timer variable is an NSTimer declared in the header file):
-(void)holdLoop:(id)sender
{
[self cancelTimers];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(death:) userInfo:nil repeats:YES];
}
-(void)death:(id)_delay
{
if (isClicked)
{
_holdBlock();
}
else
{
[self cancelTimers];
}
}
The block that executes changes the value of a float, which is used to update the value of a label, which is then redrawn.
The first time the hold click event occurs, this works great. After that, it seems like timers don't get canceled, and new timers are still added. This is what my cancelTimers function looks like (calls here are retrieved from a collection of the other questions on this topic):
-(void)cancelTimers
{
[_timer invalidate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(death:) object:nil];
}
What am I doing wrong, and how do I fix it?
Edit
I do, in fact, already have the function that responds to touch up inside:
- (void)clickUp:(id)sender
{
isClicked = NO;
[self cancelTimers];
_clickBlock();
}
Furthermore, I have realized that the issue comes from an unhandled cancel event. Is there a reason why iOS would auto-cancel my long press?
Solved
Since the block redrew the UI, it was also redrawing the buttons (and resetting their functionality). This event was causing a cancel event to be called on the button - which was not handled. Adding the following:
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchCancel];
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchUpOutside];
-(void)cancelClick:(id)sender
{
isClicked = NO;
[self cancelTimers];
}
As well as reconsidering what changes are made in the block, has gotten me past this issue.
As I understood from the comments and the code, the clickDown: is called for UIControlEventTouchDown so isClicked is set to YES when the first time the button is touched down. You need to add a selector to the event UIControlEventTouchUpInside. It's called when the user lifts his finger while being iside the bound of the button. Inside that method, set isClicked to NO.

Resources