cocoa: how to create a custom view by IB? - cocoa

I want to create a custom view, say MyView, which is only containing a button and print out "hello my view" when I click it. When next time I want to use it, I just need to add a custom view to window from IB, add MyView.m into project and set the class of custom view to MyView.
My question is :
I want to use IB to set the appearance of MyView, but I don't know how to get the corresponding view file : MyView.m in order to use the MyView.m file next time.

You need to create the subclassed view using code; for example:
MyView.h:
#import <Cocoa/Cocoa.h>
#implementation MyView : NSView
{
IBOutlet NSTextField *_label;
}
- (IBAction)buttonPressed:(id)sender;
#end
MyView.m:
#import "MyView.h"
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
// Init here
}
return self;
}
- (void)awakeFromNib:
{
// Init here
}
- (IBAction)buttonPressed:(id)sender
{
[_label setStringValue:#"hello my view"];
}
#end
You need to create your custom view layout using IB and change the owning class from NSView to MyView (using the 3rd tab if I recall).
Then connect the _label from the owning object on the left pane (Ctrl-drag) and connect the button action to the buttonPressed: method (Ctrl-drag).

Related

How do I validate an NSButton in an NSToolbar?

I have a document-based app with a tool bar containing several NSButton which I need to validate. Base on other code here, I have subclassed NSToolbar:
#interface CustomToolbar : NSToolbar
#end
#implementation CustomToolbar
-(void)validateVisibleItems
{
for (NSToolbarItem *toolbarItem in self.visibleItems)
{
NSResponder *responder = toolbarItem.view;
while ((responder = [responder nextResponder]))
{
if ([responder respondsToSelector:toolbarItem.action])
{
[responder performSelector:#selector(validateToolbarItem:) withObject:toolbarItem];
}
}
}
}
#end
MyDocument (the File's owner) is set as the delegate of the toolbar. However
-(BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem
is never called. The buttons have an action set on them, so not sure why [responder respondsToSelector:toolbarItem.action] is always false.
I have tried subclassing the NSButton items:
#interface DocumentToolbarActionItem : NSToolbarItem
#implementation DocumentToolbarActionItem
-(void)validate
{
Document* document = [[self toolbar] delegate];
[self setEnabled:[document validateUserInterfaceItem:self]];
}
#end
But this results in an endless loop.
The document's validateUserInterfaceItem: method works for all other items in the app and I need to have my toolbar button call it to determine if they should be enabled or not.
My guess is that you're not calling through [super validateVisibleItems] and, so, losing the superclass behaviour of validation through the responder chain.

switching views with NSMenuItem IBAction does not work

In a document based Cocoa App I want to fill a NSBox in the Document.xib with a view,
by selecting the view with a NSMenuItem. However, the box is not updated with the view.
If I insert a button in the Document.xib, which is connected with the same IBAction as the NSMenuItem, the app works in the expected way.
I created the tree files:
- ViewController.h
- ViewController.m
- prettyView.xib
In ViewController.m the XIB File of the view is initialized.
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (id)init
{
if(![super initWithNibName:#"prettyView" bundle:nil]){
return nil;
}
[self setTitle:#"Pretty View"];
return self;
}
#end
The Document.h contains outlets for the box and two buttons.
One button fills the box with the view, the other one clears the box.
// Document.h
#import <Cocoa/Cocoa.h>
#class ViewController;
#interface Document : NSDocument
#property (weak) IBOutlet NSBox *contentBox;
- (IBAction)fillBox:(id)sender;
- (IBAction)clearBox:(id)sender;
#property ViewController * myViewController;
#end
In Document.m the view controller is instantiated.
// Document.m
#import "Document.h"
#import "ViewController.h"
#interface Document ()
#end
#implementation Document
- (instancetype)init {
self = [super init];
if (self) {
_myViewController = [ViewController new];
}
return self;
}
The methods for the IBActions are implemented in Document.m too.
- (IBAction)fillBox:(id)sender {
NSLog(#"Fill Box selected from %#", [sender className]);
[self.contentBox setContentView:[self.myViewController view]];
}
- (IBAction)clearBox:(id)sender {
NSLog(#"Clear Box selected");
[self.contentBox setContentView:nil];
}
The method fillBox is connected to one of the both buttons as well as to the NSMenuItem.
Pressing the button, a message is written to the console and the view is shown in the box.
Selecting the NSMenuItem, a message is written too, but the view is not displayed in the box.
The IBActions must not be connected with Document but with First Responder of MainMenu.xib.

Is there a way to implement a popup menu from NSToolBarItem?

I am trying to implement a pop-up menu (something that can be seen when in Chrome I press right mouse button when the cursor is over the left arrow).
I have a class derived from NSToolBarItem and I have another class derived from NSToolBar. In the toolbar I call setAllowsUserCustomization. So my right click anywhere on the toolbar brings up the customization menu for the toolbar.
Thank you for any pointer you can give.
You don't need to subclass NSToolbarItem. Just give one toolbar item its own view (in code or in IB). In that view, you can use a standard control like NSPopUpButton, or a custom view with whatever event handling logic you like.
If you want your NSToolbarItem custom views to receive mouseDown events, you can follow this pattern: use a custom NSWindow subclass (or swizzle the -[NSWindow hitTest:] method) and forward events to your views yourself.
// MyWindow.h
#interface MyWindow : NSWindow
#end
#interface NSView (MyWindow)
- (BOOL)interceptsToolbarRightMouseDownEvents;
#end
// MyWindow.m
#implementation NSView (MyWindow)
- (BOOL)interceptsToolbarRightMouseDownEvents { // overload in your custom toolbar item view return YES
return NO;
}
#end
#interface NSToolbarView : NSView /* this class is hidden in AppKit */ #end
#implementation MyWindow
- (void)sendEvent:(NSEvent*)event {
if (event.type == NSRightMouseDown) {
NSView* frameView = [self.contentView superview];
NSView* view = [frameView hitTest:[frameView convertPoint:event.locationInWindow fromView:nil]];
if ([view isKindOfClass:NSToolbarView.class])
for (NSView* subview in view.subviews) {
NSView* view = [subview hitTest:[subview convertPoint:event.locationInWindow fromView:nil]];
if (view.interceptsToolbarRightMouseDownEvents)
return [view rightMouseDown:event];
}
}
[super sendEvent:event];
}
#end

Double Click in NSCollectionView

I'm trying to get my program to recognize a double click with an NSCollectionView. I've tried following this guide: http://www.springenwerk.com/2009/12/double-click-and-nscollectionview.html but when I do it, nothing happens because the delegate in IconViewBox is null:
The h file:
#interface IconViewBox : NSBox
{
IBOutlet id delegate;
}
#end
The m file:
#implementation IconViewBox
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
// check for click count above one, which we assume means it's a double click
if([theEvent clickCount] > 1) {
NSLog(#"double click!");
if(delegate && [delegate respondsToSelector:#selector(doubleClick:)]) {
NSLog(#"Runs through here");
[delegate performSelector:#selector(doubleClick:) withObject:self];
}
}
}
The second NSLog never gets printed because delegate is null. I've connected everything in my nib files and followed the instructions. Does anyone know why or an alternate why to do this?
You can capture multiple-clicks within your collection view item by subclassing the collection item's view.
Subclass NSView and add a mouseDown: method to detect multiple-clicks
Change the NSCollectionItem's view in the nib from NSView to MyCollectionView
Implement collectionItemViewDoubleClick: in the associated NSWindowController
This works by having the NSView subclass detect the double-click and it pass up the responder chain. The first object in the responder chain to implement collectionItemViewDoubleClick: is called.
Typically, you should implement collectionItemViewDoubleClick: in the associated NSWindowController, but it can be in any object within the responder chain.
#interface MyCollectionView : NSView
/** Capture double-clicks and pass up responder chain */
-(void)mouseDown:(NSEvent *)theEvent;
#end
#implementation MyCollectionView
-(void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
if (theEvent.clickCount > 1)
{
[NSApplication.sharedApplication sendAction:#selector(collectionItemViewDoubleClick:) to:nil from:self];
}
}
#end
Another option is to override the NSCollectionViewItem and add an NSClickGestureRecognizer like such:
- (void)viewDidLoad
{
NSClickGestureRecognizer *doubleClickGesture =
[[NSClickGestureRecognizer alloc] initWithTarget:self
action:#selector(onDoubleClick:)];
[doubleClickGesture setNumberOfClicksRequired:2];
// this should be the default, but without setting it, single clicks were delayed until the double click timed-out
[doubleClickGesture setDelaysPrimaryMouseButtonEvents:FALSE];
[self.view addGestureRecognizer:doubleClickGesture];
}
- (void)onDoubleClick:(NSGestureRecognizer *)sender
{
// by sending the action to nil, it is passed through the first responder chain
// to the first object that implements collectionItemViewDoubleClick:
[NSApp sendAction:#selector(collectionItemViewDoubleClick:) to:nil from:self];
}
What you said notwithstanding, you need to be sure you followed step four in the tutorial:
4. Open IconViewPrototype.xib in IB and connect the View's delegate outlet with "File's Owner":
That should do ya, provided you did follow the rest of the steps.

Cocoa - loading a view from a nib and displaying it in a NSView container , as a subview

I've asked about this earlier but the question itself and all the information in it might have been a little confusing, plus the result i want to get is a little more complicated. So i started a new clean test project to handle just the part that im interested to understand for the moment.
So what i want, is basically this: i have a view container (inherits NSView). Inside, i want to place some images, but not just simple NSImage or NSImageView, but some custom view (inherits NSView also), which itself contains a textfield and an NSImageView. This 'image holder' as i called it, is in a separate nib file (im using this approach since i am guiding myself after an Apple SAmple Application, COCOA SLIDES).
The results i got so far, is something but not what i am expecting. Im thinking i must be doing something wrong in the Interface Builder (not connecting the proper thingies), but i hope someone with more expertise will be able to enlighten me.
Below i'll try to put all the code that i have so far:
//ImagesContainer.h
#import <Cocoa/Cocoa.h>
#interface ImagesContainer : NSView {
}
#end
//ImagesContainer.m
#import "ImagesContainer.h"
#import "ImageHolderView.h"
#import "ImageHolderNode.h"
#class ImageHolderView;
#class ImageHolderNode;
#implementation ImagesContainer
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
//create some subviews
for(int i=0;i<3;i++){
ImageHolderNode *node = [[ImageHolderNode alloc] init];
[self addSubview:[node rootView]];
}
}
NSRunAlertPanel(#"subviews", [NSString stringWithFormat:#"%d",[[self subviews] count]], #"OK", NULL, NULL);
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
[[NSColor blackColor] set];
NSRectFill(NSMakeRect(0,0,dirtyRect.size.width,dirtyRect.size.height));
int i=1;
for(NSView *subview in [self subviews]){
[subview setFrameOrigin:NSMakePoint(10*i, 10)];
i++;
}
}
#end
//ImageHolderView.h
#import <Cocoa/Cocoa.h>
#interface ImageHolderView : NSView {
IBOutlet NSImageView *imageView;
}
#end
//ImageHolderVIew.m
#import "ImageHolderView.h"
#implementation ImageHolderView
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
[[NSColor blueColor]set];
NSRectFill(NSMakeRect(10,10, 100, 100));
//[super drawRect:dirtyRect];
}
#end
//ImageHolderNode.h
#import <Cocoa/Cocoa.h>
#class ImageHolderView;
#interface ImageHolderNode : NSObject {
IBOutlet ImageHolderView *rootView;
IBOutlet NSImageView *imageView;
}
-(NSView *)rootView;
-(void)loadUIFromNib;
#end
//ImageHolderNode.m
#import "ImageHolderNode.h"
#implementation ImageHolderNode
-(void)loadUIFromNib {
[NSBundle loadNibNamed:#"ImageHolder" owner: self];
}
-(NSView *)rootView {
if( rootView == nil) {
NSRunAlertPanel(#"Loading nib", #"...", #"OK", NULL, NULL);
[ self loadUIFromNib];
}
return rootView;
}
#end
My nib files are:
MainMenu.xib
ImageHolder.xib
MainMenu is the xib that is generated when i started the new project.
ImageHolder looks something like this:
image link
I'll try to mention the connections so far in the xib ImageHolder :
File's Owner - has class of ImageHolderNode
The main view of the ImageHolder.xib , has the class ImageHolderView
So to resume, the results im getting are 3 blue rectangles in the view container, but i cant seem to make it display the view loaded from the ImageHolder.xib
If anyone wants to have a look at the CocoaSlides sample application , its on apple developer page ( im not allowed unfortunately to post more than 1 links :) )
Not an answer, exactly, as it is unclear what you are asking..
You make a view (class 'ImagesContainer'). Lets call it imagesContainerView.
ImagesContainerView makes 3 Objects (class 'ImageHolderNode'). ImagesContainerView asks each imageHolderNode for it's -rootView (maybe 'ImageHolderView') and adds the return value to it's view-heirarchy.
ImagesContainerView throws away (but leaks) each imageHolderNode.
So the view heirachy looks like:-
+ imagesContainerView
+ imageHolderView1 or maybe nil
+ imageHolderView2 or maybe nil
+ imageHolderView3 or maybe nil
Is this what you are expecting?
So where do you call -(void)loadUIFromNib and wait for the nib to load?
In some code you are not showing?
In general, progress a step at a time, get each step working.
NSAssert is your friend. Try it instead of mis-using alert panels and logging for debugging purposes. ie.
ImageHolderNode *node = [[[ImageHolderNode alloc] init] autorelease];
NSAssert([node rootView], #"Eek! RootView is nil.");
[self addSubview:[node rootView]];
A view of course, should draw something. TextViews draw text and ImageViews draw images. You should subclass NSView if you need to draw something other than text, images, tables, etc. that Cocoa provides.
You should arrange your views as your app requires in the nib or using a viewController or a windowController if you need to assemble views from multiple nibs. Thats what they are for.
EDIT
Interface Builder Connections
If RootView isn't nil then it seems like you have hooked up your connections correctly, but you say you are unclear so..
Make sure the IB window is set to List view so you can see the contents of you nib clearly.
'File's Owner' represents the object that is going to load the nib, right? In your case ImageHolderNode.
Control Click on File's owner and amongst other things you can see it's outlets. Control drag (in the list view) from an outlet to the object you want to be set as the instance var when the nib is loaded by ImageHolderNode. I know you know this already, but there is nothing else to it.
Doh
What exactly are you expecting to see ? An empty imageView? Well, that will look like nothing. An empty textfield? That too, will look like nothing. Hook up an outlet to your textfield and imageView and set some content on them.

Resources