Programmatically create UIButton - won't call action? - objective-c

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 :)

Related

Can this code works without overridden dealloc method (Objective-C)

Manual memory management is used. The following code runs well and no crash occurs. But there is no -(void)dealloc method. Is this code wrong? Should I add -(void)dealloc?
MyClass.h
#import <UIKit/UIKit.h>
#interface MyClass : NSObject {
#private
BOOL flag;
UIView *view;
UILabel *label;
UIButton *button;
UITabBar *tabBar;
UIWebView *webView;
UIImageView *imageView;
}
#property (nonatomic, retain) UIView *view;
#property (nonatomic, retain) UILabel *label;
#property (nonatomic, retain) UIButton *button;
#property (nonatomic, retain) UITabBar *tabBar;
#property (nonatomic, retain) UIWebView *webView;
#end
MyClass.m
#import "MyClass.h"
#implementation MyClass
#synthesize view;
#synthesize label;
#synthesize button;
#synthesize tabBar;
#synthesize webView;
- (id)init {
self = [super init];
if (self) {
// Initialization code
// Among other code,
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
}
return self;
}
// Other methods here.
// But -(void)dealloc is not overridden here in the MyClass.m
#end
If we must add -(void)dealloc for the above code, should it be like this:
Overridden -(void)dealloc
-(void)dealloc {
[view release];
[label release];
[button release];
[tabBar release];
[webView release];
[super dealloc];
}
Update 1
#synthesize added, see above.
Update 2
Didn't put this into another post because this seems rather related issue:
See the above MyClass.m/.h, there is a private ivar (not sure it should be called ivar or field here) UIImageView *imageView;, it has no property for it, no #synthesize, initialization given there, how can we dealloc it? Also [imageView release]; in -(void)dealloc?
Update 3
Do we have to check availability before releasing ivars? That is, instead of [view release];, use this:
if (nil != view) {
[view release];
}
Yes. You need to implement dealloc.
You dealloc will look like :
-(void)dealloc {
[_view release];
[_label release];
[_button release];
[_tabBar release];
[_webView release];
[super dealloc];
}
Any retained/copy property should be released on dealloc.
Your iVar have no meaning. They do not have the same information as the properties, so you can remove your iVars.
If you want your properties to be backed up by your iVars you should #synthesize them like:
#synthesize view = view;
#synthesize label = label;
#synthesize button = button;
#synthesize tabBar = tabBar;
#synthesize webView = webView;
As you are not using ARC, your code (including the dealloc method) is correct, however you are missing the #synthesize statement in the implementation for the properties to work:
#implementation MyClass
// this will synthesize the getters and setters
#synthesize view, label, button, tabBar, webView;
- (id)init {
self = [super init];
if (self) {
// Initialization code
}
return self;
}
// Other methods here.
-(void)dealloc {
[view release];
[label release];
[button release];
[tabBar release];
[webView release];
[super dealloc];
}
#end
While Daniel's answer is correct in addressing your question, it does not cover what you should do.
This is how your code would be written in a modern world:
turn on ARC. Especially if you are learning or this is your first project. There is no reason to not use ARC. Learning manual-retain-release is valuable, but not critical at this time as the tools do a very good job of providing analysis of when ARC based patterns are leaking memory (either the analyzer or using Instruments, both of which you would need to use under MRR and neither of which work as well under MRR).
Don't use #synthesize and don't declare iVars (and certainly don't declare iVars in your .h file). Let the compiler autho-synthesize the _ prefixed ivars automatically. The _ prefix has the added advantage of disallowing you from accidentally directly accessing an ivar in code. I.e. self.foo vs. foo; the second won't compile.
Or to translate into code:
#interface MyClass : NSObject
#property (nonatomic, retain) UIView *view;
#property (nonatomic, retain) UILabel *label;
#property (nonatomic, retain) UIButton *button;
#property (nonatomic, retain) UITabBar *tabBar;
#property (nonatomic, retain) UIWebView *webView;
#end
And:
#implementation MyClass
{
BOOL _flag;
UIImageView *_imageView;
}
- (id)init {
self = [super init];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
}
return self;
}
// no -dealloc
#end
Note that I declared _imageView as an instance variable that will be compatible with the obvious imageView #property should you need to refactor your class later to make that externally available.
If you realy must use manual retain-release, then add a -dealloc method that calls -release on all the ivars. I.e.[_view release];, [_imageView release];, etc...
Don't get what you mean by "Note that I declared _imageView as an
instance variable that will be compatible with the obvious imageView
#property should you need to refactor your class later to make that
externally available."
If you were to decide that _imageView needs to be accessible to other objects, then you would delete the iVar declaration and add:
#property(nonatomic, retain) UIImageView *imageView;
The compilers automatic synthesis would create an instance variable named _imageView automatically and none of the rest of your code would have to change.
Do we have to make sure an ivar is not nil before releasing it in
dealloc method? (See Update 3 above.)
No. In Objective-C, nil eats messages. That is, [nil release]; is perfectly valid and does absolutely nothing at runtime.
In your code the BOOL flag; has disappeared. Do we have make a
property for BOOL flag;, that is, #property BOOL flag;? Or all we have
to do is just placing a private field in the MyClass.h as #private
BOOL flag; up there in my original question?
I forgot it. You could create a property for it. Or you could declare _flag as an iVar next to _imageView as I've done above.
Most importantly, there is no longer any reason (save for a very rare case that is generally too be avoided) to declare instance variables in your .h.

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

Local Variables in viewdidload

I'm currently trying to make a program when I create a button, and when I click it its alpha goes to zero. It may seam simple, but its driving me crazy.
You see, in my final project I'm making a lot of buttons so I need to do this all in code and not using story board.
Heres what I have so far
//mediumboard.h
#import <UIKit/UIKit.h>
#interface MediumBoard : UIViewController
#property (weak, nonatomic) IBOutlet UIScrollView *ScrollMedium;
#end
And in the implementation
- (void)viewDidLoad{
UIButton *Button1x1x1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[Button1x1x1 addTarget:self action:#selector(Press1x1x1) forControlEvents:UIControlEventTouchUpInside];
Button1x1x1.frame = CGRectMake(50,50, 80, 130);
[self.view addSubview:Button1x1x1];
}
Up in till now it works fine. It creates the button and displays it on my screen.
However when I try to implement this
-(void)Press1x1x1 {
Button1x1x1.alpha = 0;}
It gives me the error "Use of undeclared identifier 'Button1x1x1'"
I know its a variable localization problem, and that the variable Button1x1x1 is localized in viewdidload, but I have no idea how to solve this problem. Do I make it global variable? Please remember I can't use the Storyboard.
Additional info: I have no other references to Button1x1x1 in my code, anywhere.
You could use a selector that passes the sender as an argument.
[button1x1x1 addTarget:self action:#selector(press1x1x1:) forControlEvents:UIControlEventTouchUpInside];
// ^ emphasized
-(void)press1x1x1:(UIButton *)sender {
sender.alpha = 0;
}
and you should change your variable names so they start with a lowercase letter. Capital letters should only be used for class names.
You don't have to declare a global variable, just declare it as an instance variable like so:
#interface MediumBoard : UIViewController {
#private
UIButton *myButton_; // Or Button1x1x1, whatever you want to call it
}
#property (weak, nonatomic) IBOutlet UIScrollView *ScrollMedium;
#end

Objective-C: Calling class 2 instance from class1 [alloc init] way is not working

I've got the following method on a GameScreen.m file, with its own declaration - (void) drawNumbers on a GameScreen.h file:
//GameScreen.h
#import <UIKit/UIKit.h>
#interface GameScreen : UIView
{
IBOutlet UIButton *cell00;
}
- (void) drawNumbers;
- (IBAction) onCellClick:(id)sender;
#property (nonatomic, retain) IBOutlet UIButton *cell00;
#end
//GameScreen.m
#import "GameScreen.h"
- (void) drawNumbers
{
//testing if this works, so far it doesn't
[cell00 setTitle:#"Whatever" forState:UIControlStateNormal];
[cell00 setTitle:#"Whatever" forState:UIControlStateHighlighted];
}
I'm trying to call this method from my GameScreenViewController.m file, this way:
//GameScreenViewController.m
#import "GameScreenViewController.h"
#import "GameScreen.h"
...
- (void) viewDidLoad
{
GameScreen *aGameScreen = [[GameScreen alloc] init];
[aGameScreen drawNumbers];
[aGameScreen release];
[super viewDidLoad];
}
This is supposed to change the title of a button in a GameScreen.xib file where GameScreenViewController.m is the viewController and GameScreen class is the event handler where I get all the button clicks, timers running, etc. I am trying to call [drawNumbers] from [viewDidLoad] since I want the title to be changed when the screen is brought up front (screen management is done through the AppDelegate files).
The thing is, if I call drawNumbers instance from inside the same class through
//GameScreen.m
#import GameScreen.h
-(void) onButtonClick:(id)sender
{
//some other code
[self drawNumbers];
}
it works (as to say, nothing wrong with the code implementation or the graphic interface).
I've browsed through Apple Guide and tons of pages on the Internet, but I can't seem to find any light to this. Any further help (including answers as to where exactly find the answer in the ADG) would be really appreciated.
(Edited: here goes the AppDelegate code to flip to the specific view, just in case):
//myAppAppDelegate.h
#import <UIKit/UIKit.h>
#class myAppViewController, GameScreenViewController;
#interface myAppDelegate : NSObject <UIApplicationDelegate>
{
UIWindow *window;
myAppViewController *viewController;
GameScreenViewController *gameScreenViewController;
}
- (void) flipToGameScreen;
#property (nonatomic, retain) UIWindow *window;
#property (nonatomic, retain) GameScreenViewController *gameScreenViewController;
#end
//myAppAppDelegate.m
-(void) flipToGameScreen
{
GameScreenViewController *aGameScreenView = [[GameScreenViewController alloc] initWithNibName: #"GameScreen" bundle:nil];
[self setGameScreenViewController:aGameScreenView];
[aGameScreenView release];
[gameScreenViewController.view.frame = [[UIScreen mainScreen] applicationFrame];
[viewController.view removeFromSuperview];
[self.window addSubview:[gameScreenViewController view]];
}
Since your cell00 is to be set by a NIB it will be nil if you simply do [[GameScreen alloc] init]. It will only be set if the corresponding NIB is loaded (and a connection is actually set up).
If the cell can be accessed in your viewDidLoad, create a property on GameScreen and pass it through the property (or a dedicated initWithCell: or something).
If you have something like an IBOutlet GameScreen *aGameScreen; on your GameScreenViewController (and also established a connection to cell00 in the same NIB) you should access that instead.

Objective C: Sending arguments to a method called by a UIButton

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.