4

I have a method that is being called when a UIButton is clicked. When I create the button I want it to store an NSTimer as an argument.

This is the timer and the creation of the UIButton. How would I add in the timer to be sent down to the method? I've tried withObject:timer but it gives me a warning and crashes at runtime.

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:(0.009) target:self selector:@selector(moveStickFig:) userInfo:stickFig repeats:YES];
[stickFig addTarget:self action:@selector(tapFig:andTime:) forControlEvents:UIControlEventTouchUpInside];

This is the method I'm sending it down to:

-(void) tapFig:(id)sender andTime:(NSTimer *)timer

I've also tried [stickFig performSelector:@selector(tapFig:andTime) withObject:nil withObject:timer] after I defined the UIButton, but that also results in a warning and crashes.

Chris
  • 7,270
  • 19
  • 66
  • 110

6 Answers6

7

You can't - UIControl action selectors are invoked with no parameters, the control that is the source of the action, or the control that is the source of the action and the UIEvent which occurred on that control. In IB you have to connect the UIButton to such a method: you can't add any other custom parameters.

If you want it to have access to other objects, they need to be instance variables.

Review Apple's Introduction to Objective C if you want to understand how to define instance variables.

Phil Willoughby
  • 1,649
  • 12
  • 16
  • How would I make the timer an instance variable that I can use in other methods? – Chris Jan 30 '11 at 17:08
  • 3
    There are actually three possible signatures with zero, one, or two parameters for action methods. –  Apr 23 '11 at 00:31
2

You could take the approach where you extend UIButton.

@interface MyButton : UIButton
@property (nonatomic, retain) NSDictionary *userInfo;
@end

Then your method

- (void)foo:(MyButton *)sender{
    NSLog(@"%@", [sender.userInfo valueForKeyPath:@"extraData"]);
}

And to set userInfo

...
MyButton *myButton = (MyButton *)[UIButton buttonWithType:UIButtonTypeCustom];
//set up a dictionary with info, called userInfo
myButton.userInfo = userInfo;
[myButton addTarget:self selector:@selector(foo:) forControlEvent:UIControlEventTouchUpInside];

Would that work for you?

JP.
  • 544
  • 5
  • 20
0

You can create subclass of UIButton, add property in this subclass, store your object in this property and get it in action method through sender.

Igor
  • 12,165
  • 4
  • 57
  • 73
0

Modify your method to take a single NSArray as an argument. Then, create your array of parameters and pass it to performSelector.

To be more clear:

You would create the IBAction required for the control's event and a second method that takes an NSArray as an argument. When the IBAction method is called, it would call the second method after creating the NSArray of parameters. Think of it as a "method chain."

Evan Mulawski
  • 54,662
  • 15
  • 117
  • 144
  • 1
    Doesn't work - you don't get to pick the parameter send by a UIControl action; it's always the control. – Phil Willoughby Jan 30 '11 at 17:14
  • and where on earth would the first method get these array elements from? if the first method had the arguments somehow to begin with (that's the whole point of this question) then it wouldn't need the second method – user102008 Apr 22 '11 at 22:16
0

You need to make the timer a property of your view controller and then referenced it from your tapFig: method. Here is what your code might look like:

MainViewController.h

//
//  MainViewController.h
//  TapFigSample
//
//  Created by Moshe Berman on 1/30/11.
//  Copyright 2011 MosheBerman.com. All rights reserved.
//

@interface MainViewController : UIViewController  {
    NSTimer *timer; 
    UIButton *stickFig;
}

@property (nonatomic, retain) NSTimer *timer;
@property (nonatomic, retain) UIButton *stickFig;

- (void)tapFig:(id)sender;

- (void) moveStickFig;

- (void) moveStickFig:(id)yourArgument


@end

MainViewController.m

//
//  MainViewController.m
//  TapFigSample
//
//  Created by Moshe Berman on 1/30/11.
//  Copyright 2011 MosheBerman.com. All rights reserved.
//

#import "MainViewController.h"


@implementation MainViewController

@synthesize timer, stickFig;

- (void) viewDidLoad{

    [self  setTimer:[NSTimer scheduledTimerWithTimeInterval:(0.009) target:self selector:@selector(moveStickFig:) userInfo:stickFig repeats:YES]];
    [stickFig addTarget:self action:@selector(tapFig:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)tapFig:(id)sender{
    //do something with self.timer

}

- (void) moveStickFig:(id)yourArgument{
    //Move the stick figure
    //A tophat and a cane might 
    //look nice on your stick figure
    // :-)
}

- (void)dealloc {
    [stickFig release];
    [timer release];
    [super dealloc];
}


@end

Notice the @property declaration in the header file. I'd consider taking another look at the timer initialization too, but that could just be me. I hope this helps!

Moshe
  • 57,511
  • 78
  • 272
  • 425
  • Your "timer" in "viewDidLoad" is a local variable. setting it accomplishes nothing. it does not set the instance variable – user102008 Apr 22 '11 at 22:13
  • @user102008 - Yikes! You're right. It's supposed to be sample code, but I've modified it. – Moshe Apr 24 '11 at 09:07
0

I suggest to create a small support class that works like a delegate that simply takes care of the click action for you, then change the target of your addTarget method.

First create your support class:


@interface ClickDelegate : NSObject {
    NSTimer timer;
}

@property (nonatomic, assign) NSTimer *timer;

- (void)clickAction:(id)sender;

@end


@implementation ClickDelegate

@synthesize timer; 

- (void)clickAction:(id)sender {
    // do what you need (like destroy the NSTimer)
}

@end

Then change the target:


// In your view controller
                      
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:(0.009) target:self selector:@selector(moveStickFig:) userInfo:stickFig repeats:YES];                                                                               
   
// Instantiate a new delegate for your delegate action
// and set inside of it all the objects/params you need
ClickDelegate *aDelegate = [[ClickDelegate alloc] init];
aDelegate.timer = timer;

[stickFig addTarget:aDelegate action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside];

self.myDelegate = aDelegate; // as suggested in the comments, you need to retain it
[aDelegate release]; // and then release it

In this way you're delegating the click callback to another object. This case is really simple (you just need to get the NSTimer instance) but in a complex scenario it also helps you to design the application logic by delegating different stuff to different small classes.

Hope this helps! Ciao

Community
  • 1
  • 1
lomanf
  • 2,039
  • 1
  • 16
  • 14
  • Oh, I suggest the "support class" because I meant you do not have just one instance of the NSTimer.. I supposed you have multiple buttons and multiple timers. Otherwise, a simple instance property of your ViewController is enought. Bye. – lomanf Jan 30 '11 at 17:37
  • "it should be retained by the UIButton when you add the target" This is not true. Your solution won't work. – user102008 Apr 22 '11 at 22:11
  • Thank you. That's true, the target is NOT retained and it should be retained for example in a property. BTW, this doesn't mean that the purpose of the solution won't work. The scope of the solution was to delegate the logic and multiple parameters of a complex button action to someone else. In this case, he just need to reach the NSTimer then the cost/benefit of a whole delegation is not really useful but I think that the logic is still valid. – lomanf Apr 23 '11 at 00:03
  • if you are going to retain the delegate as a property in `self`, you might as well just retain the timer itself as a property of `self`, and not bother with creating this extra class and all. also, you still have the problem of not knowing when to release this property unless you know how long the button will be around – user102008 Apr 23 '11 at 02:34
  • As I told, this is an approach and was intended to add more "stuff" into the delegate (like the NSTimer and more stuff). The button is part of the controller, you can keep both the button and the delegate alive until the functionality is needed. Set the NSTimer as property was quite obvious and already answered. I wanted to try to put another point of view. Anyway, thanks for your clever support. – lomanf Apr 26 '11 at 23:48