Custom NSToolbarItem Button Not Showing - objective-c

I have two custom NSToolbarItems in the toolbar of the application. Each class has a NSButton within, where I setup the button and then set the toolbar item's view to the button (the stop button item for example):
#implementation RBSStopButtonToolbarItem
#synthesize button = _button;
-(id)initWithItemIdentifier:(NSString *)itemIdentifier
{
self = [super initWithItemIdentifier:itemIdentifier];
if(self)
{
// create button
_button = [[NSButton alloc] init];
// set the frame and bounds to be the same size
//[_button setFrameSize:NSMakeSize(64.0, 64.0)];
//[_button setBoundsSize:NSMakeSize(64.0, 64.0)];
// button will not have a visible border
[_button setBordered:NO];
// set the original and alternate images...names are "opposite"
[_button setImage:[NSImage imageNamed:#"StopButtonAlternateIcon"]];
[_button setAlternateImage:[NSImage imageNamed:#"StopButtonIcon"]];
// image position
[_button setImagePosition:NSImageOnly];
// set button type
[_button setButtonType:NSMomentaryChangeButton];
// button is transparent
[_button setTransparent:YES];
// set the toolbar item view to the button
[self setView:_button];
}
return self;
}
I have an IBOutlet for each custom NSToolbarItem:
// toolbar item for start button
IBOutlet RBSStartButtonToolbarItem *_startButtonToolbarItem;
// toolbar item for stop button
IBOutlet RBSStopButtonToolbarItem *_stopButtonToolbarItem;
Yet I do not see the images in the custom view toolbar items:
The images are .icns type. The example I attempted to following is here:
NSButton in NSToolbar item: click issue
Is there anyone with experience who can offer advice?

I don't know why, but:
[NSToolbarItem initWithCoder:] is calling [NSToolbarItem setImage:] which is then calling [NSButton setImage:] on the button you have set as the toolbar item's view. This wipes out what you have done.
The example that you are referring to DOES NOT subclass NSToolbarItem.
I recommend that you also DO NOT subclass NSToolbarItem, and instead add a regular NSToolbarItem to the toolbar via interface builder and then in awakeFromNib find that toolbar item via its item identifier and set the button as its view.
I have verified that doing it this way works as expected.

I do not follow why your example doesn't work.
But I have worked out the custom NSToolbarItem with my own way without even using NSToolbarDelegate.
My way is assuming you build your toolbar within a nib and not with code(mostly).
What I am doing is creating my own NSView in my nib with whatever I want in it.
Then I drag this NSView into into my NSToolbar in my nib.
xCode will automatically place your NSView inside an NSToolbarItem.
You can then drag this custom NSToolbarItem into the default items and place it with whatever order you want(so you don't even need to place it by code).
The tricky part is to subclass NSToolbarItem and then within the awakeFromNib of this specific NSToolbarItem subclss you set it's view to the NSView underneath it.
You would also need to refer the NSView into an IBOutlet * NSView within that subclass.
Here is the code of the subclass.
The header file:
#import <Cocoa/Cocoa.h>
#interface CustomToolbarItem : NSToolbarItem
{
IBOutlet NSView * customView;
}
#end
The Obj-c file:
#import "CustomToolbarItem.h"
#implementation CustomToolbarItem
-(instancetype)initWithItemIdentifier:(NSString *)itemIdentifier
{
self = [super initWithItemIdentifier:itemIdentifier];
if (self)
{
}
return self;
}
-(void)awakeFromNib
{
[self setView:customView];
}
#end
I have also wrote a blog post about how I did this:
http://pompidev.net/2016/02/24/make-a-custom-nstoolbar-item-in-xcodes-interface-builder/

Related

Auto layout on subview inside subview (NSView)

I'm having hard time trying to properly set the auto layout for a subview inside another subview.
I'm using an example where two toolbar items show two different subviews (which works as expected), and those two share a third subview that's the one that does not fit well.
The code to add the subview is very simple:
[subView removeFromSuperview];
[itemXSubView addSubview:subView];
[self.window setContentView:itemXView];
First I remove the third and shared subview (subView) in case it was already added, then add it to the item[1-2]SubView and set the content of the window with the subview item[1-2]View, [1-2] depending on the toolbar button selected. Everything else is done with auto layout conditions.
The result is that the third and shared subview is always misplaced and/or cut, as in the example below. Resizing the window and changing from the first or the second view usually aggravates the issue.
Example of third subview items cut
Test updates
Tried to delegate the main window and override two resize functions (as per #the4kman suggestions), but they did never get called. The init is the only being called:
#interface viewController: NSView <NSWindowDelegate>
#end
#implementation viewController
-(id)init
{
if((self=[super init])) { }
return self;
}
- (void)resizeSubviewsWithOldSize:(NSSize)oldSize;
{
[super resizeSubviewsWithOldSize:oldSize];
}
- (void)resizeWithOldSuperviewSize:(NSSize)oldSize;
{
[super resizeWithOldSuperviewSize:oldSize];
}
- (void)layout
{
[super layout];
}
Another suggestion that got called, but sadly with no actual improvement. Delegated the window to viewController and set the main view (self.view) to the nested subView. Tried also combining with [itemXSubView setNeedsLayout:true];:
#interface viewController: NSViewController <NSWindowDelegate>
#end
#implementation viewController
-(void)viewWillLayout
{
[super viewDidLayout];
[self.view setNeedsLayout:true];
}
#end
Thanks in advance!
itemXSubView resizes its subviews but it doesn't fit them. You have to fit subView inside item1SubView before addSubview.
- (IBAction)item1Action:(id)sender {
NSLog(#"Item 1 action triggered");
[subView removeFromSuperview];
subView.frame = item1SubView.bounds;
[item1SubView addSubview:subView];
[self.window setContentView:item1View];
}

Objective-C: How to switch the NSView by button click

I have different xib files with NSViewController attached to them. (Screenshot below)
One of xib file called StartMenuViewController which has a button. I want to click that button and change the view to DetectingUSBViewController.(Screenshot below)
The IBAction of that button is in StartMenuViewController.m file.
And I use AppController.m to control my main xib view.(NSWindow + NSView) (Screenshot below)
When the application runs, I try to initialize the StartMenuViewController fist by doing the following thing in my AppController.m file.
-(void)awakeFromNib{
[self initialize];
}
-(void) initialize
{
#autoreleasepool {
//mainViewController is a NSViewController and _mainView is a NSView which connect with Custom View in main xib
self.mainViewController = [[[StartMenuViewController alloc]initWithNibName:StartMenuView bundle:nil]autorelease];
[_mainView addSubview:[_mainViewController view]];
}
}
It works fine and it will show the StartMenuViewController.xib on the window at first, but I do not know how to change the view after clicking the button(FIND USB DRIVE). I want the current view changes to DetectingUSBViewController.xib.
Simplest way possible, assuming you have tied your USB button properly in, do the following :
- (IBAction)usbButton:(UIButton *)sender {
DetectingUSBViewController *second = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
[self presentViewController:second animated:YES completion:nil];
}
load the DetectingUSBViewController in startMenuViewController as DetectingUSBViewController* v1 = [[ViewCont1 alloc] initWithNibName:#"ViewCont1" bundle:nil]; now add or replace the view as [v1 view] in view where you want to add/replace.
You need to hook up your button to send an IBAction
You need a 'View for DetectingUSBViewController.xib'
=> one way (iOS like) is to use a ViewController. Subclass NSViewController and then alloc init a DetectingUSBViewController
Add the view. Don't present the VC (as there is no such thing in OSX)
//button click action
- (IBAction)usbButton:(UIButton *)sender {
//! Retain the VC
Self.detectingUSBViewController = [[DetectingUSBViewController alloc] initWithNibName:#"DetectingUSBView" bundle:nil];
//add the view
[_mainView addSubview:[_detectingUSBViewController view]];
}

Automatically click segmentControl with index 0 on ViewDidAppear

I am hoping to "automate" a click on the segmentController with the index of 0.
My tabBar-based app has multiple segmentControllers in a tab in the ViewDidAppear method, I would like to automatically have it "click" the first segmented controller.
if (segmentController.selectedSegmentIndex == 0) {
//stuff here
}
if (segmentController.selectedSegmentIndex == 1) {
//stuff here
}
Does anyone know how I might accomplish this? Thank you!
If you're creating it programmatically, you could lazy load it like this:
#interface ExampleViewController : UIViewController
#property (nonatomic, strong) UISegmentedControl *segmentedControl;
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl;
#end
#implementation ExampleViewController
- (UISegmentedControl *)segmentedControl
{
if (!_segmentedControl)
{
NSArray *items = #[#"First", #"Second", #"Third"];
_segmentedControl = [[UISegmentedControl alloc] initWithItems:items];
[_segmentedControl addTarget:self
action:#selector(segmentedControlClicked:)
forControlEvents:UIControlEventValueChanged];
[_segmentedControl setSelectedSegmentIndex:0]; // Set Default selection
CGRect frame = _segmentedControl.frame;
frame.origin = CGPointMake(0.0f, 0.0f); // Move to wherever you need it
[self.view addSubview:_segmentedControl];
}
return _segmentedControl;
}
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl
{
// Whatever your code is goes here...
}
#end
If you're wanting a method to be called also initially, you can call it within your viewDidLoad: method as such:
- (void)viewDidLoad
{
[self.segmentedControl setSelectedSegmentIndex:0]; // Set desired default index (optional if set in lazy load as shown above)
[self segmentedControlClicked:self.segmentedControl];
}
This would hence simulate a click on desired default index.
Be careful putting the above into viewDidAppear: (you could if you really wanted to) because anytime the view comes to the front, this method will be called (in example, if this view controller presents a modal view controller, once the modal is dismissed, this view controller's viewDidAppear: method will be called).
Cheers!
Set the selectedSegmentIndex property on your UISegmentedControl in your viewDidAppear (or viewDidLoad) method.
self.segmentedController.selectedSegemntIndex = 1;
UISegmentedControl Reference

firstResponder in NSViewController

I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works

How do I bind programmatically in the view subclass of my NSCollectionView?

I've successfully created an NSCollectionView and added a label to the view prototype in IB, bound to a property of my represented object. I now want to programmatically create an NSButton and NSTextField with the NSTextField bound to a property of my represented object. When the button is clicked I want to show and hide the NSTextField.
The problem I've come across is if I put my initialization code for my controls in the view's initWithCoder method, and the binding in the view's awakeFromNib, the binding doesn't get hooked up. If I put the initialization for my controls in the awakeFromNib, when the button is clicked, I don't have access to the controls in my view (they are null when printed out using NSLog).
From what I can tell it looks like the issue may be that the way NSCollectionView works is, it creates an instance of the view, then copies it for how every many objects are in the collection view. How do I get the the buttons to initialize and the binding to work with the copy of the prototype?
Below is my initialization code and my binding in the awakeFromNib for my subclassed view:
SubView.h
#interface SubView : NSView {
NSButton *button;
NSTextField *textField;
IBOutlet NSCollectionViewItem *item; // Connected in IB to my NSCollectionViewItem
}
- (IBAction)buttonClicked:(id)sender;
#end
SubView.m
#implementation SubView
- (id)initWithCoder:(NSCoder *)decoder
{
id view = [super initWithCoder:decoder];
button = [[NSButton alloc] initWithFrame:NSMakeRect(50, 95, 100, 20)];
[button setTitle:#"Begin Editing"];
[button setTarget:self];
[button setAction:#selector(buttonClicked:)];
[self addSubview:button];
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 100, 75)];
[self addSubview:textField];
return(view);
}
- (void)awakeFromNib
{
// Bind the textField to the representedObject's name property
[textField bind:#"value"
toObject:item
withKeyPath:#"representedObject.name"
options:nil];
}
- (IBAction)buttonClicked:(id)sender
{
[button setTitle:#"End Editing"];
[textField setHidden:YES];
}
#end
This sounds similar to something I just did, so maybe it's what you need.
Subclass NSCollectionView and override:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
In newItemForRepresentedObject:, retreive the view item, then add your controls and any programmatic bindings:
#implementation NSCollectionViewSubclass
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object {
// Allow the superclass to create or copy the collection view item
NSSCollectionViewItem *newItem = [super newItemForRepresentedObject:object];
// Get the new item's view so you can mess with it
NSView *itemView = [newItem view];
//
// add your controls to the view here, bind, etc
//
return newItem;
}
#end
Hopefully this is somewhere close to where you need to be...
-awakeFromNib is not called on the view for a NSCollectionViewItem if that view is in the same nib as the NSCollectionView, but it is called if you put the view in a separate nib.
Create an empty nib file (BlahBlahCollectionViewItem.nib).
Cut the collection item view out of whatever nib you have it in
Paste it into the new nib file
Change the class of its owner to NSCollectionViewItem.
Connect the view outlet on the owner to the newly pasted view
Open the nib file containing the NSViewController
Select the associated NSViewControllerItem
Change its Nib Name property to the name of the new nib
Keep your code in -awakeFromNib
-awakeFromNib is not called for views copied from the prototype NSCollectionViewItem. Put your binding code in initWithCoder: and you should be fine.