Objective C: Sending arguments to a method called by a UIButton - objective-c

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.

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.

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?

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."

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

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!

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.

Related

Method of one class not executing when called form a different class

I am working on a front end for a project in objective-c and I am having some trouble getting methods of my class Window which is a subclass of NSViewController to fully execute when called from a different class.
I have a method of the class Window that is called setColor which changes the color of my NSTableView variable which is linked to a bordered scroll view in my interface. I am able to successfully change the color by calling the setColor method like this from the init method in Window: [self setColor :self];
However when I do this [window1 setColor: window1] with window1 being an object of the class Window that I have declared in class Door, nothing seems to happen since the color of the boarded scroll view remains the same.
My Window.h file looks like this:
#interface Window : NSViewController {
#public
IBOutlet NSTableView *dataTableView;
}
#property (retain) IBOutlet NSTableView *tableView;
- (IBAction)SetColor:(id)sender;
#end
My Window.m looks like this:
#synthesize tableView;
- (void) awakeFromNib {
// [self SetColor :self];
}
- (IBAction)SetColor:(id)sender;
{
NSLog(#"changing the color");
[self->tableView setBackgroundColor: NSColor.blueColor];
}
Door.h looks like this
#interface Door : NSViewController {
Window* window1;
}
-(IBAction)buttonPress:(id)sender;
#property (retain) Window* window1;
#end
Door.m looks like this:
-(void) dealloc{
[window1 release];
}
-(id)init{
self = [super init];
if(self){
window1 = [Window alloc];
}
-(IBAction)buttonPress :(id)Sender;
{
[window1 setColor: window1];
}
I am using Xcode 3.2 so I cannot use ARC.
window1 = [Window alloc] will not load a Nib or storyboard and connect the outlet IBOutlet NSTableView *dataTableView to the table view inside it.
If "Window" is a view controller, you need to initialize it and the outlets in it a more standard way. View controllers need the proper initialization or the outlets are nil, and in Objective-C, if you send a method to nil, it just does nothing.

Objective-c proper delegation

I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.

(Target: Object) not working for setting UIButton from outside viewController

I can create the UIButton.
But the callback: target: object won't work within the current object. ??
I can only do "self.viewController" object, and can't do "self" for current object.
thx
#import <Foundation/Foundation.h>
#import "ViewController.h"
#class ViewController;
#interface CGuiSetup : NSObject
{
#public
ViewController *viewController;
}
- (void) Setup;
- (void) ButtonRespond:(UIButton*) btn;
#end
#import "CGuiSetup.h"
#implementation CGuiSetup
- (void) ButtonRespond:(UIButton*) btn
{
NSLog(#"ButtonRespond");
}
- (void) Setup
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
// But I want to call the following in the CGuiSetup object (self) instead... but it crashes out if I leave it as just "self"
[btn addTarget:self.viewController action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //works: for "self.viewController" if I put ButtonRespond in the ViewController
[btn addTarget:self action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //fails: for "self"
[self.viewController.view addSubview:btn];
}
#end
#import <UIKit/UIKit.h>
#import "CGuiSetup.h"
#class CGuiSetup;
#interface ViewController : UIViewController
{
CGuiSetup *guiSetup; //<---- had to take this out of the "viewDidLoad" method
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
guiSetup = [CGuiSetup alloc];
guiSetup->viewController = self;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
[btn addTarget:guiSetup action:#selector(ButtonRespond:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
If you're using ARC, does any object have a retaining reference to CGuiSetup? I sounds like CGuiSetup is instantiated, creates (or maybe receives from another object) the viewController, adds the button to it and then gives the view controller to another object (perhaps by pushing it on a navController or setting it to be the root controller of the app)? Whatever the case is, CGuiSetup it being dealloc'd and the button is trying to send a message to an object that's already been destroyed. How/where is CGuiSetup created? What object retains a reference to it? That's probably where your problem is.
If you don't understand what retain/release is and/or you don't know what ARC is, you need to read Apple's memory management guide: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
That's probably because your object is destroyed, while _viewController still has retain count greater than 0 (so it's not destroyed). Balance you're retain/release count.

Programmatically create UIButton - won't call action?

I have a custom class, and that class has a UIButton instance variable. I have added this code in the class designated initializer:
theFishDeathView = [UIButton buttonWithType:UIButtonTypeCustom];
[theFishDeathView setFrame:CGRectMake(15, 15, 50, 50)];
[theFishDeathView setImage:[UIImage imageNamed:#"Small fish - death.png"] forState:UIControlStateNormal];
So this should properly allocate / initialize the button. And truly enough, the button get's displayed on the screen when this is called (and of course added as a subview).
Now, I call this method on my object:
[theFishDeathView addTarget:self action:#selector(sellFish) forControlEvents:UIControlEventTouchDown];
And here is the sellFish method:
-(void) sellFish {
thePlayer.dollars += worthInDollars * 3;
[theFishDeathView removeFromSuperview];
}
But when I try and press the button, it doesn't call that method. Am I missing something here?
For the sake of completeness, here is the Fish.h file. It is clear that theFishDeathView is an instance member of the Fish object.
#import <Foundation/Foundation.h>
#interface Fish : NSObject
{
float cookingTime;
float weight;
int worthInDollars;
NSString *name;
NSArray *animaionImages;
int fishMovementSpeed;
}
// Will be used to display
#property (nonatomic, retain) UIImageView *theFishImageView;
#property (nonatomic, retain) UIButton *theFishDeathView;
// Create setter / getter methods
#property (nonatomic, retain) NSString *name;
#property (readonly) int worthInDollars;
#property (readonly) int fishMovementSpeed;
-(id) initWith: (NSString *)theName andWeight: (float)theWeight andCookingTime: (float)theCookingTime andValue: (int)theValue andMovementSpeed: (int)speed;
-(CGRect) newFrameWithWidth:(int)width andHeight:(int)height;
-(void) killFish;
// Cooking methods
-(void) startCooking;
-(void) isDoneCooking;
-(void) isOverCooked;
-(void) sellFish;
#end
try
-(void) sellFish:(id)sender
and (with the : after sellFish)
[theFishDeathView addTarget:self
action:#selector(sellFish:)
forControlEvents:UIControlEventTouchUpInside];
[theFishDeathView addTarget:self
action:#selector(sellFish)
forControlEvents:UIControlEventTouchUpInside];
you wrote UIControlEventTouchDown not UIControlEventTouchDown
I wanted people to know i found the error (with some help from the Apple developers forum) - it was a memory leak. I was trying to send a message to a zombie object (i.e a deallocated object). I thought it was retained by adding it as a subview, but i totally forgot it was the BUTTON i added as a subview, and NOT the class that contained the button. So the class itself got deallocated, and the button was retained, he's the reason why i could still press it.
For others dealing with similar issues, turn on the Zombie Objects Enabled thing in the plist.info file. That way, you will get an error message like this: "Trying to send action to deallocated object".
Thanks for trying to help me out :)

UIButton set touch handler in code

I want to accomplish touching a UIButton and having code run in a different class than the owner.
I realize I can do a touchUpInside to the button's owner (ClassA) and then call the method inside ClassB that I want called, but is there any way to expedite this?
ideas:
have ClassB be the delegate for the ClassA->UIButton
set the touchUpInside call in programming to used the function inside ClassB
I'm not sure how to accomplish either of these ideas :( Input is mas appreciated!
One option is to set the button up using
[myButton addTarget:yourOtherClass action:#selector(mySelector:) forControlEvents:UIControlEventTouchUpInside];
but this is a bit dangerous because target is not retained so you could send the message to a deallocated object.
You could instead set up a protocol
MyController.h
#protocol MyControllerDelegate
- (void)myController:(MyController *)controller buttonTapped:(UIButton *)button;
#end
#interface MyController : UIViewController
#property (nonatomic, assign) id <MyControllerDelegate> delegate;
- (IBAction)buttonTapped:(UIButton *)button;
#end
Then in your implementation
MyController.m
- (IBAction)buttonTapped:(UIButton *)button
{
if ([self.delegate respondsToSelector:#selector(myController:buttonTapped:)]) {
[self.delegate myController:self buttonTapped:button];
}
}
As the method defined in the protocol was not optional I could have instead done a check for (self.delegate) to make sure it is set instead of respondsToSelector.