Programmatically create NSButton and set custom NSButtonCell - objective-c

Ok, this seems like it should very simple, but I'm coming from iOS and and I must be missing something very obvious. So I have a custom subclass of NSButtonCell. If I create a NSButton in IB and assign the NSButtonCell in IB, it works no problem.
However, I need to programmatically create this button. I've looked at a couple of examples and have the following within awakeFromNib, however, it creates what appears to be a regular button with my custom button cell underneath (I can tell when I click on the button).
NSButton* button = [[NSButton alloc] initWithFrame:NSMakeRect(100, 100, 60, 30)];
CT_CaptureButtonCell* captureCell = [[CT_CaptureButtonCell alloc] init];
[button setCell:captureCell];
[captureCell release];
[self.view addSubview:button];
So what dumb thing am I missing / not understanding? Thanks.

If you look at the documentation for "setCell:", it states:
Discussion
Use this method with great care as it can irrevocably
damage the affected control; specifically, you should only use this
method in initializers for subclasses of NSControl.
The way I read this is that you should be calling your "setCell" from within the init method for a subclass of "NSButton". In other words, write your own NSButton subclass (e.g. "ICButton", for InfalibleCoinage) and then stuff the "setCell" call into the init method there.
And while we're on the subject, what's the big picture of what you are exactly trying to accomplish with "CT_CaptureButtonCell" and can you do it without using a custom button cell?

Related

NSButtons Inside NSPopover View Controller

I've developed for Mac before but this is the first time I've attempted to use the NSPopover control, which seemed like a great idea to start out with but so far is causing me no end of problems. The applciation is a menu bar application. I have two NSButton objects in the NSPopover's view controller, the NSPopover is being created programmatically in another subclass of NSButton, the same button which it is being shown relative to. This NSButton that it is being shown relative to is contained along with some other buttons in an NSMenuItem
The popup, containing the two buttons, is being shown fine (see screenshot below), however, despite the 'Yes' button being highlighted with a focus ring, neither button responds to click events, they do not even graphically click in like I would expect them to.
And this is the code that creates the NSPopover and positions it onscreen:
someViewController *confirmationDialogue = [[someViewController alloc] initWithNibName:#"someViewController" bundle:nil];
popOver = [[NSPopover alloc] init];
[popOver setBehavior:NSPopoverAppearanceMinimal];
[popOver setBehavior:NSPopoverBehaviorTransient];
[popOver setContentViewController:confirmationDialogue];
[popOver showRelativeToRect:NSMakeRect(0, 0, self.frame.size.width, self.frame.size.height))
ofView:self
preferredEdge:NSMaxYEdge];
Has anyone got any kind of solution/workaround to this?
Thanks in advance :)
P.s. This is my first question on SO, so I hope I've provided enough information but I'll give any more details as needed.

Losing Segue Action when adding custom image in navigation bar button item

I`m developing a app using Storyboards.
In one ViewController I have a button on navigationBar that links to the second ViewController. This transiction is defined in the storyboard (in this case I have defined a push segue to link the two ViewControllers)
I have changed the image of the button following this post in Stackoverflow.
But the problem is: That change in the View of the button breaks the push segue that I have defined in the storyboard. So the question is: How to still change the background of the BarButton without killing the segue action?
I dont want to programmatically reset the segue using performSegueWithIdentifier. This makes no sense since I already have defined it on the storyboard, so I think that must be another solution.
I think this is going to be your best solution:
In viewDidLoad:
self.navButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.navButton.frame = CGRectMake(0, 0, 30, 30);
[self.navButton setImage:[UIImage imageNamed:#"yourImage"] forState:UIControlStateNormal];
[self.navButton addTarget:self action:#selector(yourNavButtonAction) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *logOutBarButton = [[UIBarButtonItem alloc] initWithCustomView:self.navButton];
[self.navigationItem setRightBarButtonItems:[NSArray arrayWithObject:navButton, nil]];
And in the method handling your navButton tap (yourNavButtonAction from above)
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:self];
**Note that this will require you to create a storyboard segue that originates from your ViewController itself, as opposed to a button on that ViewController. Control drag from your ViewController to the target ViewController, give the resulting segue an identifier (yourSegueIdentifier above) and you're set.
You cannot use the quoted code and use the segue at the same time. The code introduced a new object, a UIButton that gets the click, so your storyboard object will not get it any more.
You could try adding a standard custom UIButton in storyboard and change the code as follows:
// instead of
UIButton *someButton = [[UIButton alloc] initWithFrame:frameimg];
// use
_someButton.frame = frameimg;
Assuming that your _someButton is the name for the IBOutlet of the button from storyboard.
If this does not work, you should go with performSegueWithIdentifier. I do not see the problem with this, either. You anyway include a #selector with the custom UIButton. Just use it to initiate the segue. You can still configure the segue, etc., in the storyboard, so nothing is lost, right? In fact, the refactoring above seems like more work.

Objective C / UIBackBarButtonItem Why can't change view

I've ran into an interesting and strange question while messing around with a project.
After spending like 3 hours doing it, I found out you can't change the view of the UIBackBarButtonItem, only the UILeftBarButtonItem, so if I want to implement a custom back button, I hide the UIBackButtonItem and display a UILeftBarButtonItem which does the popping.
Now I find it odd, that you can't change the UIBackBarButtonItem's view, but you can change the UILeftBarButtonItem and the UIRightBarButtonItem's views.
Can someone explain me why would Apple do this?
Actually, you can. Use UIBarButtonItem's instance method setBackButtonBackgroundImage:forState:barMetrics:.
So if you want to change the background image for all your back buttons, your code should like something like:
UIImage *backButtonBgImageNormal = [[UIImage imageNamed:#"back_button_bg.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(5, 15, 5, 5);];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonBgImageNormal forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
Use delegate method of UINavigationController, such like
#pragma mark -
#pragma mark - UINavigationController Delegate Methods
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//// customize you own stuff here;
UINavigationBar *morenavbar = navigationController.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem.tintColor = [UIColor blackColor];
}
I think I have a comprehensive solution for you.
1) From experience, it's just best to not bow to limitations of BarButtonItems. I suggest you create a simple UIButton, customise it to your liking. maybe add an action to its touch events..brand it with custom background and title colors...etc.. keep a reference to this button, maybe as a property.
2) Then, you create an instance of UIBarButtonItem using the UIBarButtonItem initializer -initWithCustomView, you sneak in the UIButton instance as this custom view in the init and have complete control over it.
3) Finally, you just do this.
self.navigationItem.LeftBarButtonItems = #[ourUIBarButtonItem].
The navigation bar has an Array property "leftBarButtonItems" for series of left buttons, and rightBarbuttonItems for the right side. Just replace this with your own array, containing the UIbarButtonItem, that containing your button, and you having a reference to your button.
And now you can completely control you button that is properly in the navigation bar.
NOTE! - Once you provide such a leftBarButtonItem, the stock Apple BackButton is gone.

Creating an instance of an instance of another class

I'm trying to break some of my "super objects" into more manageable classes that have single (or at least limited) responsibility.
One problem I've just run into is making an object of a specific instance of a UIBarButtonItem. In the class it is in now I first define a UIButton, and then all of the images that act as icons for that button as subviews (for instance the button represents access/control to a device, and I use the button image to show the current signal strength of that device). Also that button is listening for NSNotifications from the device object to represent the signal strength changing, or if the device disconnects. And pressing the button sends a message to the device to disconnect. All of this code works perfectly fine now as a property of the RootViewController. However, I want to pull it out into its own class as the button is shared by several classes, and it just clutters up the controller with unnecessary methods.
I tried making an separate class with an init like below. However, this doesn't work as the self used for the button isn't the same self that is ultimately created by [UIBarButtonItem alloc] and when either the NSNotification or the button press try to send a message to the selector of "self", that object has already been dealloced. The problem is, I'm not sure how to create a object (as defined by the class) that is just an instance of another class, as opposed to a property of an object (as it currently is for the RootViewController).
Edit and additional explanation of my problem
MyClass is currently subclass of UIBarButtonItem. However, I'm not trying to use it like this: [[MyClass alloc] initWithCustomView:]. I want [MyClass alloc] init] by itself to completely create the custom view - in other words the whole point of this class is to completely contain all that is necessary for this button to create itself, manage its subviews, and take the appropriate action when it is pressed. (I could easily make MyClass an NSObject with a public method like [MyClass setupButton] and a public property of type UIBarButtonItem. However, I think that looks wrong because then the class is only there to create the button, but it is not the button itself.)
#interface MyClass : UIBarButtonItem
#end
#implementation MyClass
- (id)init {
if (self = [super init]) {
UIImage *defaultButton = [[UIImage imageNamed:#"...
UIImage *defaultButtonPressed = [[UIImage imageNamed:#"....
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
[button setBackgroundImage:defaultButton forState:UIControlStateNormal];
[button setBackgroundImage:defaultButtonPressed forState:UIControlStateHighlighted];
[button addTarget:self action:#selector(deviceButtonPressed) forControlEvents:UIControlEventTouchUpInside];
//Then several UIImageViews that are added as subviews of the button, initially hidden
//Then set up the NSNotification listener
//Finally
self = [[UIBarButtonItem alloc] initWithCustomView:button];
}
return self;
}
//Then several functions to handle hiding and unhiding the subviews depending on the received device notifications, and a function to handle the button press and sending the message back to the device.
This is not how initialization works in Cocoa. Please read "Initialization" in the Cocoa Core Competencies guide.
Your object has already been allocated when this init method is run. You should not be reassigning the self pointer to another allocation.
Your class should first call its superclass's designated initializer self = [super initWithWhatever:obj];, then set up its own properties.
It seems to me that you want to extend UIBarButtonItem, not create an instance of it in your init method. Try changing your class declaration (in your class's .h file) from this:
#interface MyClass : NSObject
to this:
#interface MyClass : UIBarButtonItem
Then just return self in your init method. Setting self to a value is usually a bad idea.
If you're unsure about what's going on here, you're creating a subclass of UIBarButtonItem. This lets your subclass extend the superclass's functionality. If you're confused, you should take a look at subclassing/class inheritance in object-oriented languages to understand what's going on. This guide documents how classes work in Objective-C.

How do you connect events in view controllers created entirely in code, no Interface Builder?

I have created a UINavigationController with a UINavigationBar that has a UIBarButtonItem in the right position ENTIRELY in code, there is no IB/.xib file.
As a newcomer to XCode (v4) from C# I am trying to understand how to trap and handle the click event of the button in the navigation bar.
In C# there would be a click event that you would override which would contain the event handling code.
I have found a number of samples that use the IB to connect UI objects such as a button to a method in the view controller. However, I can not find any examples of how to achieve that solely in code.
Any references of insights appreciated.
In your viewDidLoad method create the UIBarButtonItem using one of the following initialisers...
– initWithImage:style:target:action:
– initWithTitle:style:target:action:
The last two parameters to the initialisers allow you to specify the object to invoke when the action occurs and the method on that object to call i.e.
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithImage:image style:UIBarButtonItemStylePlain target:self action:#selector(methodForItem:)];
Apple's UIBarButtonItem Class Reference
You'll want to use Target/Action on the right bar button item. So after you instantiate your UIBarButtonItem you'll do something like this.
[barButtonItem addTarget:self action:#selector(methodThatShouldBeCalled:) forControlEvents:UIControlEventTouchUpInside];