Setting C function as selector for NSButton produces no results - objective-c

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.

Related

ARC and ViewControllers

I have a little misunderstanding regarding ARC. I'm creating a new UIViewController using the following code:
CGRect screenRect = [[UIScreen mainScreen] bounds];
LocationProfileView *locationProfile = [[LocationProfileView alloc] initWithLocation:l];
locationProfile.view.frame = CGRectMake(0, screenRect.size.height, screenRect.size.width, 400);
[appDelegate.window addSubview:locationProfile.view];
[UIView animateWithDuration:.25 animations:^{
locationProfile.view.frame = CGRectMake(0, 0, screenRect.size.width, screenRect.size.height);
}];
In its UIVIew I put a button which removes the view from screen. The problem with this is that locationProfile gets deallocated immediately after its being added to screen, so everytime I'm trying to tap on "Close" button (which calls a method in LocationProfileView class) my application will crash.
So I added a property:
#property(nonatomic, strong) LocationProfileView *locationProfile;
and changed the second line of code in:
locationProfile = [[LocationProfileView alloc] initWithLocation:l];
but now my class won't be deallocated until I initiate it again (because it loses the reference to the first instance of LocationProfileView?). What should I do to make my class being deallocated every time I tap on "Close" button? I guess that setting locationProfile to nil would work, but this means that I'll have to call a method in the main class (the one which contains the code block).
What is the proper way for doing this? Sorry if my questions is too noobish.
Note:
l is an instance of a custom class which contains some infos to be displayed in LocationProfileView's UIVIew.
- (void)closeButtonCallBack {
[self.locationProfile removeFromSuperview];
self.locationProfile = nil;
}
i am assuming the target of your close button is the viewcontroller itself
a strong pointer will the retain the object until the viewController itself is deallocated, unless you assign to it nil
a local variable will be deallocated when it goes out of scope
ALTERNATIVELY
without using the strong pointer, you can do this
LocationProfileView *locationProfile = [[LocationProfileView alloc] initWithLocation:l];
UIButton *close = [UIButton buttonWithType:UIButtonTypeRoundedRect];
close.frame = CGRectMake(0, 100, 100, 30);
[close addTarget:locationProfile action:#selector(removeFromSuperview) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:close];
In your original example,
LocationProfile *locationProfile=...
is a local variable. So it's released as soon as you return from the constructor. That's what you observed.
When you make it a strong property, the view controller retains the locationProfile:
#property(nonatomic, strong) LocationProfileView *locationProfile;

UIButton created in code not working properly

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];

IBAction connected with button but need a void

I have i IBAction and it's connected with a button in .xib. But for a action o needed this code;
- (void) reportScore: (int64_t) score forCategory: (NSString*) category
{
GKScore *scoreReporter = [[GKScore alloc] initWithCategory:category];
scoreReporter.value = counter;
[scoreReporter reportScoreWithCompletionHandler: ^(NSError *error)
{
[self callDelegateOnMainThread: #selector(scoreReported:) withArg: NULL error: error];
NSLog(#"Nice: %d", counter);
}];
}
Is there a way to connect my button with the void? Or something like that?
- (IBAction) subScore {
//data
}
IBAction(and IBOutlet) is just used to let XCode's Interface Builder(IB) know that there's a function exist. If you want use void, okay, but you'll not get the function shown in Interface Builder.
IBAction is a special keyword that is used only to tell Xcode(Interface Builder) to treat a method as an action for target-action connections. IBAction is defined to void.
IBOutlet is a special keyword that is used only to tell Xcode(Interface Builder) to treat the object as an outlet. It’s actually defined as nothing so it has no effect at compile time.
You can set the function(which is defined with void, and note IBAction is okay either, but needless here) to your button target like this:
[yourButton addTarget:self action:#selector(yourButtonFunction:) forControlEvents:UIControlEventTouchUpInside];
Generally, if you create your code programmatically(without using XCode's Interface Builder), you do not need IBOutlet or IBAction anymore. ;)

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!

Protocol Memory

I'm sorry for repost. What have really bug me is if property retain should release when:
case 1 : (code below) button is already alloc in init: then add to subview then release it, and by any chance I set the button property in another class (I didn't release in dealloc:) it will leak then?
case 2 : button is already alloc in init: then add to subview then release it, and by any chance I didn't set the any button property in another class (I didn't use the property) (I release it in dealloc) then it will crash.
So what should I do if I want to alloc button in init: and I want to set the property too ?
#interface SomeClass : UIView {
UIButton *_button;
}
#property (nonatomic, retain)UIButton *button;
#implementation SomeClass
#synthesize button = _button;
- (id)init {
_button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
[[_button titleLabel] setFont:BUTTON_FONT];
[_button setBackgroundImage:[[UIImage imageNamed:#"button_blue.png"] stretchableImageWithLeftCapWidth:20.0f topCapHeight:15.0f] forState:UIControlStateNormal];
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button setTitleShadowColor:[UIColor blackColor] forState:UIControlStateNormal];
[[_button titleLabel] setShadowOffset:CGSizeMake(0.0f, 1.0f)];
[_button addTarget:self action:#selector(buttonDidTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubView:_button];
[_button release];
}
- (void)dealloc {
//[self.button release]; // case 1
[self.button release]; // case 2
[super dealloc];
}
#end
So what should I do if I want to alloc button in init: and I want to set the property too ?
Here's how your code should look:
#implementation SomeClass
#synthesize button = _button;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
UIButton *button = [[UIbutton alloc] initWithFrame:CGRectMake(0.0f,0.0f,100.0f,20.0f)];
// Setup the button here...
[self addSubView:button];
self.button = button;
[button release];
}
return self;
}
- (void)dealloc {
[_button release];
[super dealloc];
}
#end
Changes I made:
initWithFrame: is the designated initializer of the UIView, so that's the init method you've got to override
Always check to make sure that your superclass initialized successfully before you setup your class
You've got to return self at the end of your init statements. I don't think your code would have compiled as written.
You created a property, you should use it. Use a temp variable to do all the setup for your button, then when you're finished with setup, use the property accesssor to set the variable and release your temp variable.
Because you only use the property to get/set your button, when it's time to dealloc you can guarantee that the _button iVar will either be valid or nil. Either way calling release on that variable is OK.
I don’t understand what you mean by “using it for read-only”. As declared, the property is read-write both for the class itself and from the outside. But the question can be reasonably answered nevertheless – once your class retains some object, it must release it when it gets deallocated.
P.S. I think you can safely drop the underscore prefix for private variables, it serves no real purpose and makes you write more code. In fact, with modern runtimes you can even drop the instance variable declaration:
#interface Foo : NSObject {}
#property(assign) BOOL bar;
#end
#implementation Foo
#synthesize bar;
#end
OK, third attempt: The problem is that you are in fact setting the property by assigning to the instance variable. Properties and instance variables are closely tied, when you give the following declaration…
#synthesize button = _button;
…you are saying that the _button instance variable should be used to store the value of the button property. Which means that your code over-releases, since you alloc the button in init (+1), release the button in init (-1) and then release again in dealloc (-1 again).
If you have not yet studied the Cocoa Memory Management Guide, do it. Even if you don’t plan to read any other documentation (which would be a pity), make an exception with this one, it will pay you back plenty.
sure, You should release it, because you have used retain for it.
In your -init method, you have a balanced retain/release call. So you don't need to release it again. But by doing it, you are sacrificing the reliability of the value held by _button. If somewhere down the lane the button is removed from the subviews, the button's retainCount could hit zero and it can be deallocated. Then the value held by _button is garbage. So you should not release _button in -init and rather you should do that in -dealloc.
Now, if you access the property elsewhere (outside this UIView object), you don't need to release it again unless you retain it there.
Try to replace [self.button release]; with [self.button removeFromSuperview];