Double Click in NSCollectionView - cocoa

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.

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.

Why won't the data display in my NSTableView(view based)?

I followed the advice here on how to setup a MainWindowController: NSWindowController for my project's single window. I used a Cocoa class to create the .h/.m files, and I checked the option Also create .xib for User Interface. As a result, Xcode automatically hooked up a window, which I renamed MainWindow.xib, to my MainWidowController.
Next, I deleted the window in the default MainMenu.xib file (in Interface Builder I selected the window icon, then I hit the delete key). After that, I was able to Build my project successfully, and my controller's window in MainWindow.xib displayed correctly with a few buttons on it.
Then I tried adding an NSTableView to my MainWindowController's window. In Xcode, I dragged the requisite delegate and datasource outlets for the NSTableView onto File's Owner, which is my MainWindowController, and I implemented the methods in MainWindowController.m that I thought would make the NSTableView display my data:
- tableView:viewForTableColumn:row:
- numberOfRowsInTableView:
Now, when I Build my project, I don't get any errors, but the data doesn't appear in the NSTableView.
My code is below. Any tips are welcome!
//
// AppDelegate.h
// TableViews1
//
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
...
//
// AppDelegate.m
// TableViews1
//
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) MainWindowController* mainWindowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setMainWindowCtrl:[[MainWindowController alloc] init] ];
[[self mainWindowCtrl] showWindow:nil];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MainWindowController.h
// TableViews1
//
#import <Cocoa/Cocoa.h>
#interface MainWindowController : NSWindowController
#end
...
//
// MainWindowController.m
// TableViews1
//
#import "MainWindowController.h"
#import "Employee.h"
#interface MainWindowController () <NSTableViewDataSource, NSTableViewDelegate>
#property (strong) NSMutableArray* employees;
#property (weak) IBOutlet NSTableView* tableView;
#end
#implementation MainWindowController
- (NSView*)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
Employee* empl = [[self employees] objectAtIndex:row];
NSString* columnIdentifier = [tableColumn identifier];
//The column identifiers are "firstName" and "lastName", which match my property names.
//You set a column's identifier by repeatedly clicking on the TableView until only
//one of the columns is highlighted, then select the Identity Inspector and change the column's 'Identifier' field.
NSString* emplInfo = [empl valueForKey:columnIdentifier]; //Taking advantage of Key-Value coding
NSTableCellView *cellView =
[tableView makeViewWithIdentifier:columnIdentifier
owner:self];
NSLog(#"The Table view is asking for employee: %#", [empl firstName]);
[[cellView textField] setStringValue:emplInfo];
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [[self employees] count];
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
Employee* e1 = [[Employee alloc] initWithFirstName:#"Joe" lastName:#"Blow"];
Employee* e2 = [[Employee alloc] initWithFirstName:#"Jane" lastName:#"Doe"];
[self setEmployees:[NSMutableArray arrayWithObjects:e1, e2, nil]];
//Test to see if the employees array was populated correctly:
Employee* e = [[self employees] objectAtIndex:0];
NSLog(#"Here is the first employee: %#", [e firstName]);
//I see the output: "Here is the first employee: Joe"
}
- (id)init {
return [super initWithWindowNibName:#"MainWindow"];
}
- (id)initWithWindowNibName:(NSString *)windowNibName {
NSLog(#"Clients cannot call -[%# initWithWindowNibName] directly!",
[self class]
);
[self doesNotRecognizeSelector:_cmd];
return nil;
}
#end
...
//
// Employees.h
// TableViews1
#import <Foundation/Foundation.h>
#interface Employee : NSObject
#property NSString* firstName;
#property NSString* lastName;
- initWithFirstName:(NSString*)first lastName:(NSString*)last;
#end
...
//
// Employees.m
// TableViews1
//
#import "Employee.h"
#implementation Employee
- (id)initWithFirstName:(NSString *)first lastName:(NSString *)last {
if (self = [super init]) {
_firstName = first; //I read that you shouldn't use the accessors in init methods.
_lastName = last;
}
return self;
}
#end
File's Owner(=MainWindowController) connections:
NSTableView connections:
Response to comments:
Here is why calling [self tableView] reloadData] at the end of -windowDidLoad, as suggested in the comments, didn't work:
My _tableView instance variable--created by my #property declaration in MainWindowController.m--doesn't point to anything; therefore calling:
[[self tableView] reloadData]
I think is equivalent to calling:
[nil reloadData]
which doesn't do anything.
I never assigned anything to the _tableView instance variable in the -init method, nor did I assign it a value by dragging an outlet somewhere in Interface Builder. To fix that problem, I selected MainWindow.xib (the controller's window) in the Project Navigator(left pane), and then in the middle pane(Interface Builder), I selected the cube representing the File's Owner(selecting the Identity Inspector in the right pane reveals that the File's Owner is the MainWindowController). Then in the right pane, I selected the Connections Inspector, and it revealed an outlet called tableView, which is the IBOutlet variable I declared in MainWindowController.m.
Next, I dragged from the tableView outlet onto the TableView in the middle pane:
Doing that assigns the NSTableView object to the _tableView instance variable that was created by my #property declaration in MyWindowControler.m:
#property (weak) IBOutlet NSTableView* tableView;
As an experiment, I disconnected the outlet, then commented out the #property declaration for tableview, and the tableView outlet no longer appeared in the Connections Inspector. Also, if I change the declaration from:
#property (weak) IBOutlet NSTableView* tableView;
to:
#property (weak) NSTableView* tableView;
...then the tableView outlet doesn't appear in the Connections Inspector. That experiment answered a couple of questions I had about whether I should declare a property as an IBOutlet or not: if you need to assign one of the objects in Interface Builder to one of your variables, then declare the variable as an IBOutlet.
Thereafter, calling [self tableView] reloadData] at the end of -windowDidLoad succeeds in populating the TableView. However, I have not seen any tutorials that call reloadData, and even Apple's guide does not do that.
So, I am still puzzled about whether calling -reloadData is a hack or it's the correct way to do things.
Without it, your table view sits there blissfully clueless about your
expectation that it should even bother asking its datasource for data.
I assumed that an NSTableView automatically queries its datasource when it is ready to display itself, and that my code needed to be able to provide the data at that time.
I don't see you sending -reloadData to your table view anywhere. Tacking it onto the end of -windowDidLoad would be a good place. Without it, your table view sits there blissfully clueless about your expectation that it should even bother asking its datasource for data.
For all it knows, the data is simply not ready / available, so why would it try? More importantly, when should it try? It'd be rather rude of it to try whenever it pleases, considering the UI may not have finished loading / connecting to outlets, or its datasource may be in a vulnerable state (like teardown during/after dealloc) and sending datasource requests may result in a crash, etc.
Two things:
1st, set some breakpoints on when you set your employees array in windowDidLoad vs. when the table first attempts to populate itself and your numberOfRowsInTableView implementation gets called. If the latter happens before the former, then you'll need to add a reloadData after you create your array.
2nd, I personally always use NSCell instead of NSViews for my tables, so I always implement objectValueForTableColumn in my table's datasource. So I'm not sure if there's something different you need to do when you use NSView objects and implement viewForTableColumn. Is there a reason you're not using NSCell?

Drag and drop in an NSView

I'm testing drag and drop in an NSView, but draggingEntered: is never called.
Code:
#import <Cocoa/Cocoa.h>
#interface testViewDrag : NSView <NSDraggingDestination>
#end
#implementation testViewDrag
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self registerForDraggedTypes:[NSImage imagePasteboardTypes]];
NSLog(#"initWithFrame");
}
return self;
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
NSLog(#"draggingEntered");
return NSDragOperationEvery;
}
-(NSDragOperation) draggingUpdated:(id<NSDraggingInfo>)sender
{
NSLog(#"draggingUpdated");
return NSDragOperationEvery;
}
#end
In interface builder I add a subView (class is set to testViewDrag) to the main window. In log I can see the initWithFrame log but when I drag nothing is shown in the log.
What am I missing ?
"To receive drag operations, you must register the pasteboard types that your window or view will accept by sending the object a registerForDraggedTypes: message, defined in both NSWindow and NSView, and implement several methods from the NSDraggingDestination protocol. During a dragging session, a candidate destination receives NSDraggingDestination messages only if the destination is registered for a pasteboard type that matches the type of the pasteboard data being dragged. The destination receives these messages as an image enters, moves around inside, and then exits or is released within the destination’s boundaries." You can read more about the drag and drop programming topic here. As I see it, your problem lies in the argument you define in your registerForDraggedTypes: method.
Try replacing it with this:
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSColorPboardType, NSFilenamesPboardType, nil]];
Hope this helps!

How to prevent a nib from loading if already in instance is loaded?

i am developing a small application. On the first window, i have an option to create a new account. I use a button "Continue" for this. When this button is clicked, another window for creating a new account opens. I want that once this window is opened, no other instance of this nib file should load again. Even if the user clicks on "Continue" again, already opened instance of the nib file (the one for creating a new account) should come to front.
Is there any API which will help to check if one instance of nib is already loaded?
Or may be something that gives a list of all the nibs loaded in the memory?
Thanks in Advance...
UPDATE:
#interface WelcomePageController : NSObject {
IBOutlet NSTextField * userNameField;
IBOutlet NSPopUpButton * actionList;
IBOutlet NSWindow * welcomePage;
CreateNewAccountWindowController * createNewAccountWindowController;
}
-(IBAction) changePasswordButton:(id)sender;
-(IBAction) logOutButton:(id)sender;
-(IBAction) continueButton:(id)sender;
#end
#implementation WelcomePageController
-(void)windowDidUpdate:(id)sender{
UserInfo * user=[UserInfo uInfoObject];
[userNameField setStringValue:[user.firstName stringByAppendingFormat:#" %#!", user.lastName]];
if ([user.userType isEqual:#"Standard"]) {
[actionList setAutoenablesItems:NO];
[[actionList itemAtIndex:2]setEnabled:NO];
[[actionList itemAtIndex:3]setEnabled:NO];
}
else {
[actionList setAutoenablesItems:YES];
}
}
-(IBAction) changePasswordButton:(id)sender{
[NSBundle loadNibNamed:#"ChangePassword" owner:self];
}
-(IBAction) continueButton:(id)sender{
if ([actionList indexOfSelectedItem]==0) {
[NSBundle loadNibNamed:#"ViewAvailableItemsWindow" owner:self];
}
else if([actionList indexOfSelectedItem]==1){
[NSBundle loadNibNamed:#"NewOrderPage" owner:self];
}
else if([actionList indexOfSelectedItem]==2){
[NSBundle loadNibNamed:#"ManageItemList" owner:self];
}
else {
if(!createNewAccountWindowController){
createNewAccountWindowController=[[CreateNewAccountWindowController alloc]init];
}
[createNewAccountWindowController showWindow:self];
//[NSBundle loadNibNamed:#"NewAccount" owner:self];
}
}
-(IBAction) logOutButton:(id)sender{
[NSBundle loadNibNamed:#"LoginPage" owner:self];
[[sender window]close];
}
#end
This is the complete code that i am using....The code in question is the method continueButton..The else condition(last one)..
I have tried this. I open the NewAccountWindow once i click on the Continue button. I close the window and click on the continue button again. However this time the "NewAccountWindow" does not open again(even the already existing instance does not show up).
The standard approach for this is to have a subclass of NSWindowController (potentially holding outlets to the window widgets) responsible for loading the nib file. For instance,
#interface CreateAccountWindowController : NSWindowController {
// …
}
// …
#end
#implementation CreateAccountWindowController
- (id)init {
self = [super initWithWindowNibName:#"CreateAccount"];
return self;
}
// …
#end
When the user clicks the Continue button, you have an action method that handles that click. In the class that contains the action method, declare an instance variable for the corresponding window controller:
CreateAccountWindowController *createAccountWindowController;
and, in the action method that handles clicks of the Continue button, create an instance of CreateAccountWindowController if and only if none exists yet. This will make sure at most one instance of that window controller exists at any given time, hence the corresponding nib file is loaded at most once:
- (IBAction)showCreateAccountWindow:(id)sender {
if (! createAccountWindowController) {
createAccountWindowController = [[CreateAccountWindowController alloc] init];
}
[createAccountWindowController showWindow:self];
}

NSWindowController subClass - Init is Called twice

im very new in cocoa development and I'm trying to load a Window.
I will explain my problem.
When the user click the menuItem I use the following code to load my window
if ( !cadastroContasController )
{
cadastroContasController = [[cadastroContas alloc]init];
[cadastroContasController SetMenuItem:sender];
}
if ( ![[cadastroContasController window] isVisible] )
{
NSLog(#"!isVisible");
[cadastroContasController showWindow:nil];
}
I my cadastroContas class looks like this:
#interface cadastroContas : NSWindowController
{
NSMenuItem *mnuCommand;
IBOutlet NSComboBox *cmbSelecao;
IBOutlet NSTextField *txtNome;
IBOutlet NSTextField *txtSaldoInicial;
IBOutlet NSTextField *txtAnotacoes;
}
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (BOOL)windowShouldClose:(id)sender;
- (void)windowWillClose:(NSNotification *)notification;
- (void)SetMenuItem:(NSMenuItem*) menu;
- (NSMenuItem*) MenuItem;
#end
and the implementation is
#implementation cadastroContas
-(void)windowDidLoad
{
NSLog(#"windowDidLoad");
[mnuCommand setState:NSOnState];
}
-(id)init
{
self = [super initWithWindowNibName:#"cadastroContas"];
NSLog(#"Init self=%p", self);
return self;
}
-(void)dealloc
{
NSLog(#"Dealoc=%p", self);
[super dealloc];
}
- (void)windowDidBecomeKey:(NSNotification *)notification
{
NSLog(#"windowDidBecomeKey window=%p", [self window]);
}
- (BOOL)windowShouldClose:(id)sender
{
NSLog(#"windowShouldClose Window=%p", [self window]);
NSLog(#"mnuComando=%p GetMenuItem=%p", mnuCommand, [self MenuItem] );
if ( mnuCommand )
{
[mnuCommand setState:NSOffState];
}
return YES;
}
- (void)windowWillClose:(NSNotification *)notification
{
NSLog(#"windowWillClose Window=%p", [self window]);
NSLog(#"mnuCommand=%p GetMenuItem=%p", mnuCommand, [self MenuItem] );
[self dealloc];
}
- (void)SetMenuItem:(NSMenuItem*) menu
{
mnuCommand = menu;
}
- (NSMenuItem*) MenuItem
{
return mnuCommand;
}
#end
When the menu was clicked, I received two messages "Init" and I don't know why.
Exemple:
[2223:a0f] Init self=0x10014fe40
[2223:a0f] Init self=0x10011f5a0
The second message let the "[cadastroContasController SetMenuItem:sender];" useless.
So, I need help to understand whats going on..
Another thing, [[cadastroContasController window] is always returning NULL(0x0)!!, but inside my controller i can handle it (it isn't null).
This means you inited two instances, as shown by your logging of the self pointer: Notice that the value is different between the two messages.
You can use the Allocations instrument in Instruments to see what caused each window controller to be instantiated.
Usually, this problem happens when you create one of these in the nib and the other one in code. In the case of a window controller, the one you create in code should be the owner of its nib; you should not create another window controller as an object in the nib.
Another thing, [[cadastroContasController window] is always returning NULL(0x0)!!, but inside my controller i can handle it (it isn't null).
The window controller whose window outlet you set to the window is the one that is returning non-nil. The window controller whose window outlet you didn't set is the one that is returning nil.
Following from what I said above, after deleting the window controller you created in the nib, you should connect your File's Owner's window outlet to the window.

Resources