UIButton created in code not working properly - objective-c

So I've been holding off asking this question for a while because I know the solution will most likely be something very very simple. But I have come to the end of my tether so here goes:
I have created a UIButton programatically and linked it to a method, but it is not working!!
.h definition
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface CreaterPage : UIViewController
{
IBOutlet UIView *postcardView;
IBOutlet UIButton *returnButton;
}
-(void)goBack:(UIButton *)button;
#end
.m definition
#import "CreaterPage.h"
#implementation CreaterPage
-(void)viewDidLoad
{
NSLog(#"Creater Page View Loaded Successfully");
UIButton *goHomeButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[goHomeButton addTarget:self action:#selector(goBack:) forControlEvents:UIControlEventTouchDown];
[goHomeButton setTitle:#"Go Back" forState:UIControlStateNormal];
goHomeButton.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:goHomeButton];
}
-(void)goBack:(UIButton *)button
{
NSLog(#"Home");
}
#end
And basically, when I run the code, the button appears as defined but my program crashes whenever I press it.
In the main.m file, it gives me the error
Thread 1: Program received signal: "EXC_BAD_ACCESS".
On the line
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
I've tried all sorts and only turned to creating it programatically because I couldn't get it working through the interface builder.
So I'm hoping somebody on here can change my juvenile ways and show me where I'm going wrong :D
Thanks,
Matt
EDIT 1: Changed #selector(goBack) to #selector(goBack:)

My first guess would be that your action is defined as such:
[goHomeButton addTarget:self action:#selector(goBack) forControlEvents:UIControlEventTouchDown];
Note the #selector(goBack) without a colon following the method name. Your method in your .m file is defined as:
-(void)goBack:(UIButton *)button
So I imagine changing your action to #selector(goBack:) would clear things up.
Sidenote: It's very uncommon to define the type of the sender for an IBAction, as you have done. While you might not encounter any issues as long as your UIButton is the only UI object that causes the method to be called, it's very poor practice. You should change your method signature to:
-(IBAction)goBack:(id)sender
Note also the use of IBAction in place of void. While they're syntatically the same thing, the IBAction makes it clear to readers, and to Interface Builder, which methods are available for linking.

Change
[goHomeButton addTarget:self action:#selector(goBack) forControlEvents:UIControlEventTouchDown];
to
[goHomeButton addTarget:self action:#selector(goBack:) forControlEvents:UIControlEventTouchDown];

Related

Setting C function as selector for NSButton produces no results

I have a function which creates an instance of button, and I'd like the callback to go to a separate function. This doesn't work (no error or anything, just nothing happens when I click on the button). I need it to be a C function because I'm interfacing with Golang (using cgo).
void Start(void){
...
NSRect buttonFrame = NSMakeRect(59, 33, 82, 32);
NSButton *button = [[NSButton alloc] initWithFrame:buttonFrame];
[button setAction:#selector(ButtonClick:)];
...
}
void ButtonClick(void){
NSLog(#"Button clicked!");
}
You cannot use a C function as the action for an NSButton. The button requires a target which is an object and the selector for a method on that target. If there's no target, there must still be an object in your window's responder chain that will respond to the selector.
You must create an object (doesn't have to be an instance; you can use a class object if you so choose) in order for the button to operate. The method also needs to have a particular signature: it must take one argument, which will be the button when it's called.
If you must use the function you've already written, you will have to write an ObjC class that calls through to it from the action method:
#import <Cocoa/Cocoa.h>
#interface ButtonPasser : NSObject
+ (IBAction)buttonPassthrough:(id)sender;
#end
#implementation ButtonPasser
+ (IBAction)buttonPassthrough:(id)sender
{
buttonClick();
}
#end
void start(void){
...
NSRect buttonFrame = NSMakeRect(59, 33, 82, 32);
NSButton *button = [[NSButton alloc] initWithFrame:buttonFrame];
[button setTarget:[ButtonPasser class]];
[button setAction:#selector(buttonPassthrough:)];
...
}
This uses the class object and a class method, since I'm not sure what you would do with an instance after you created it. Using an instance would be much more usual, however.
Your problem is that neither your Start or ButtonClick methods (should be start and buttonClick) are instance methods. They both must be tied to an object in order for buttonClick to be set as an action.
Once these are both instance methods you should be able to add [button setTarget:self]; above your setAction call.
If you cannot make start an instance method, you should add another method in between your calls.

Passing data to a UIView - failure

I have a strange problem that I've never encountered before,
I have data in my viewController that I want to display in a UIView.
It's an iPad App which involve a SplitView Controller, when I click on an element within the table view (masterView) it execute a function in my detailViewController (via a protocol).
A function is executed which launch a UIView and send data to it:
myController:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}
DocumentView.h
#import <UIKit/UIKit.h>
#import "Document.h"
#import "DocumentInfo.h"
#class Document;
#class DocumentInfo;
#interface DocumentView : UIView
#property(strong,nonatomic) Document *doc;
#property(strong,nonatomic) DocumentInfo *doc_info;
#end
DocumentView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILabel *titreDoc=[[UILabel alloc] initWithFrame:CGRectMake(20, 32, 339, 21)];
titreDoc.textColor = [self makeColorRGB_RED:66 GREEN:101 BLUE:149];
titreDoc.font = [UIFont fontWithName:#"System" size:(24.0)];
[self addSubview:titreDoc];
NSLog(#"%# - %#",doc,doc_info);
titreDoc.text=#"Nouveau Document";
}
return self;
}
My view is well displayed (I mean the label appear) but impossible to get the data which would have been passed to it... (the NSLog print (null) (null) )
Anybody know the reason why?
The problem seems pretty straight forward. You initialize your view (which means that you run - (id)initWithFrame:(CGRect)frame) and after that you're setting the data, so it's normal that you see null values into your init method because the ivars have not being set yet. What you could do is to modify your init method in order to construct your view taking into account these ivars. Something like this perhaps:
- (id)initWithFrame:(CGRect)frame doc:(Document *)doc docInfo:(DocumentInfo *)docInfo;
ps. If you choose to make your custom init method do not forget to call the designated initializer (-initWithFrame:) before any customization.
The reason the NSLog prints null is because the doc and doc_info are nil when the initWithFrame method is called. The doc and doc_info properties are set after the initWithFrame method is called in selectionChanged: method. Add the NSLog function after line 3 in selectionChanged method like this:
- (void)SelectionChanged:(DocumentInfo*)document_info withDocu:(Document *)document{
DocumentView *viewDoc=[[DocumentView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
viewDoc.doc=document;
viewDoc.doc_info=document_info;
NSLog(#"%# - %#",doc,doc_info);
[viewDoc setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:viewDoc];
}

Displaying an UIImageView property from another class programmatically

Well, here's the situation:
I've got...
a custom class with an UIImageView-property, let's call it the Enemy-class
a ViewController
a NSMutableArray to help create multiple instances of Enemy (called enemies)
and what I want:
be able to create an unlimited amount of Enemy-Instances through a method in my ViewController (like [self spawnEnemy];, where self is the ViewController)
and, subsequently, display the UIImageView property (let's call it "enemyImage") on the view that is controlled by my ViewController
I've tried something like this:
-(Enemy *) spawnEnemy
{
Enemy *tempEnemy = [Enemy new];
[enemies addObject:(Enemy*)tempEnemy];
[self.view addSubview:(UIImageView*)[[enemies objectAtIndex:[enemies count]] enemyImage]];
//randomLocation is declared in the Enemy-Class and just assigns a random
//CGPoint to self.enemyImage.center
[[enemies objectAtIndex:[enemies count]] randomLocation];
return [[enemies objectAtIndex:[enemies count]]createEnemy];
}
This runs without errors, randomLocation gets called (tried with NSLog), AND if I do something like this in another Method of ViewController:
[[self spawnEnemy] enemyTestMethod];
enemyTestMethod is being executed as well.
But still, no enemieViews are displayed on the screen...
What am I doing wrong?
Thank you so much for your help and time.
==== Edit ====
Here's the relevant code from Enemy.h/Enemy.m:
#interface Enemy : NSObject
{
UIImageView *enemyImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *enemyImage;
-(Enemy*) createEnemy;
//Enemy.m
#implementation Enemy
#synthesize enemyImage, speed;
-(Enemy *) createEnemy
{
self.enemyImage = [[UIImageView alloc] init];
[self.enemyImage setImage:[UIImage imageNamed:#"enemy.png"]];
return self;
}
I also corrected the last line in the spawnEnemy-Method to properly send createEnemy.
You don't include the code in Enemy where you alloc/init the UIImageView property. Unless this code explicitly specifies a CGRect with the size and origin that you want, the view will be initialized with CGRectZero, which means even if you're correctly adding the subview (and it looks like you are) you still won't see it anywhere.
Post the Enemy code, and the problem will probably be immediately apparent.
Have you called -createEnemy before you added them to your view?
OK, you've got this checked.
Then maybe you should check as #MusiGenesis suggested.
To do this, you need to inspect the properties of your enemyImage.
You can do this in either of the following ways:
print its frame.size by:
NSLog(#"enemyImage frame size: %#", NSStringFromCGRect(enemy.enemyImage));
set a breakpoint where you feel great, and check with your debugger:
p (CGRect)[[enemy enemyImage] frame]

Retrieving Custom Button Property in Objective-C

I've created a custom button called TaskUIButton that inherits from UIButton. The only difference I have right now is a "va" property.
Here's the interface
// TaskUIButton.h
#interface TaskUIButton : UIButton
{
NSString *va;
}
#property(nonatomic, retain) NSString *va;
#end
And the implementation file
//TaskUIButton.m
#implementation TaskUIButton
#synthesize va;
#end
Now, I've got an action that I'm using which I want to use to set and retrieve the va property of a button (just for testing/experimentation of course).
Here is where the button action is
- (IBAction)setAndRetrieveVa:(id)sender{
TaskUIButton *imaButton = [TaskUIButton buttonWithType:UIButtonTypeRoundedRect];
imaButton.va = #"please work";
NSLog(#"%#", imaButton.va);
}
Upon activating the setAndRetrieveVa: action, my app crashes with:
-[UIRoundedRectButton setVa:]: unrecognized selector sent to instance 0x4b3a5a0
I'm sure that its a stupid mistake on my part, but I've been going at it for a while and would love some insight!
Thanks!
You are getting this because buttonWithType: is returning a new object which is a UIRoundedRectButton object which is a subclass of UIButton. You can't alter this behavior of the method unless you override but you are unlikely to get what you want. You should take the alloc-init approach.
Using Associative References
You will need to #import <Foundation/NSObjCRuntime.h> for this to work.
To set,
objc_setAssociatedObject(button, "va", #"This is the string", OBJC_ASSOCIATION_RETAIN);
And to retrieve,
NSString * va = (NSString *)objc_getAssociatedObject(button, "va");
This way you wouldn't need to subclass UIButton.
I ended up just extending UIControl...turned out to be alot easier :)
- (IBAction)setAndRetrieveVa:(id)sender{
TaskUIButton *newTaskButton = [[TaskUIButton alloc] initWithFrame:CGRectMake(29.0, (76.0+ (88*taskCounter)), 692, 80.0)];
[newTaskButton addTarget:self action:#selector(function1:)forControlEvents:UIControlEventTouchUpInside];
[newTaskButton addTarget:self action:#selector(function2:) forControlEvents:UIControlEventTouchDragExit];
[newTaskButton setBackgroundColor:[UIColor grayColor]];
[newTaskButton setTitle:#"0" forState:UIControlStateNormal];
[newTaskButton setVa:#"please work!"];
NSLog(#"%#", newTaskButton.va);
}
And for click highlighting, I can always add a function that changes the background color when touch-down occurs, and switches the color back when touch-up occurs. Hurrah!

Adding button control at running time in objective-c Cocoa

If I put these lines in the xxxAppDelegate.m file, it works fine. But I need to use it under another module such as in main.m etc. The compiler generated an error stated that window is undefined. "window" is defined in the xxxAppDelegate modues, how do you reference it in another modules beside xxxAppDelegate.m
NSView *superview = [window contentView];
NSButton *button = [ [ NSButton alloc ] initWithFrame: NSMakeRect( 10.0, 10.0, 200.0, 100.0 ) ];
[ button setBezelStyle:NSRoundedBezelStyle];
[ button setTitle: #"Click" ];
[superview addSubview:button];
[button setTarget:self];
[ button setAction:#selector(doSomething:)];
Cocoa likes to keep things modular. window doesn't exist in the context of the delegate, because it is another class.
make a property for window in your app delegate if it doesn't exist:
.h
#property(readonly)NSWindow * window;
.m
#synthesize window;
then:
((YourDelegateClass *)[NSApp delegate]).window should work.
You could just paste that code wherever you need it (IE, put it in main). Unless, is there some reason you need it declared in the delegate?