Consise simple example of working with NSCollectionView drag and drop - cocoa

I would like to figure out how to perform drag and drop from strings in a table view onto a collection view. I know there are delegate methods for collectionView drag and drop but can't find any examples of how to implement them. I have my collection view set up, it seems to be working correctly but don't know how to finish.
Any help is appreciated.
Update: The collection view setup I am working with has 3 NSTextFields and 2 check boxes for each collection item. There is also a tableView in the same view. The table view is passed an a MutableArray of strings. I want to be able to drag string values from the table view rows into the appropriate textFields in the collection view item.
This is different from the typical way drag and drop is used for collection views.

I am going to answer my own question because I spent quite a bit of time today trying to figure this out, Many people struggle with this procedure and mostly because I feel bad I keep asking the Stack community all these collection view questions all week:
I discovered the default behavior of the NSTextField actually allows a drop if it is in focus. The problem is that I need the appropriate NSTextField to auto focus on mouse enetered event. So as things turned out, I did not even need the NSCollectionView drag and drop delegates. I needed the NSTableView drag delegates and I needed to subclass the NSTextField and implement mouse event (drop) delegates in it.
so my class for the original collectionViewItem look like this:
//someClass.h
#interface SomeClass : NSObject{
IBOutlet NSTextField *field1_;
IBOutlet NSTextField *field2_;
IBOutlet NSTextField *field3_;
IBOutlet NSButton *chkBox1_;
IBOutlet NSButton *chkBox2_;
}
#porperty(readwrite, copy) NSTextField *filed1_;
properties are made for all 5 outlets for binding purposes. If you follow the tutorial on the Mac OSX Dev Library ; Collection View Programming Guide, it walks you through the process of setting up a collection View but it uses bindings.
So now the key is to set up a textField sub class
//MyNSTextField.h
#import
#interface MyNSTextField : NSTextField{
//mouse positioning
unsigned long last_;
}
//MyNSTextField.m
#import "MtTextField.h"
#implementation
-(void)dealloc{
[super dealloc];
}
-(void)awakeFromNib{
//register for dragged types
//any other nib stuff
}
//three required mouse events
-(unsigned long)draggingEntered:(id<NSDraggingInfo>)sender{
//this forces the textfield to focus before drop
[self.window makeFirstResponder: self];
NSPasebord *pBoard;
self->last_ = DragOperationNone;
pBoard = [sender draggingPastboard];
return self->last_;
}
-(unsigned long)draggingUpdated:(id<NSDraggingInfo>)sender{
return self->last_;
}
-(void)draggingExited:sender{
if([sender draggingSource] != self){
self->last = NSDragOperationNone;
}
}
}// end class
now just go back to the original class and change the name of your textField outlets from NSTextField to MyNSTextField and in the collection view, select each textfield and assign it the new class name in the inspector and as long as you had your tableview drag delegates set up, or if your are dragging from some other source, make sure you have the appropriate dragging source delegates set and it should work.

Related

How to use NSToolBar in Xcode 6 and Storyboard?

I've been trying to build on a Cocoa app that uses Swift and Storyboard in Xcode 6, but how can I use NSToolbar there?
In Xcode 5 and xib, you can add NSToolbar from within Object Library to any .xib files and then click on the added toolbar to expand it and drag and drop a connection from any items there to a AppDelegate.h file. In this way you can create a IBAction or IBOutlet connection. I confirmed that this can also be done if you use Swift and non-storyboard in Xcode 6. However, it looks like this is not the case in Xcode 6 and Storyboard environment.
I first created a project that uses Storyboard in Xcode 6, but then, I wasn't able to add a NSToolbar from within Object Library to a View Controller in Storyboard. So I added it to Window Controller's Window object in Storyboard. However, in this way I cannot create those connections from any items in the expanded toolbar to either AppDelegate.swift or ViewController.swift.
So my question is:
Is it feasible to create a storyboard app that uses NSToolbar?
If it is feasible, is the addition of NSToolbar to the Window Controller the proper way to use NSToolBar in Storyboard?
Finally, how can I create #IBOutlet and #IBAction connections there?
UPDATE
I found that the accepted answer by #GeorgeVillasboas only works for #IBAction. I am still looking for how to create an #IBOutlet connection...
I had this very same problem.
The solution works for both Objective-C and Swift projects.
Working with Storyboards on OSX, it creates an instance of NSWindow and segues to another NSViewController as its Window Content Segue, as you described.
On your ViewController, create a standard IBAction to receive the action when the toolbar is clicked. To wire it up with the NSToolbar, just control-drag (or leftClick-drag) from your NSToolbarItem to the FirstResponder object, as shown on the picture below.
This will open a HUGE list of available connections. Your IBAction will be on that list.
Just selected it and you're good to go.
Hope this helps!
Here's an answer that doesn't rely on run-time hook-ups - #cdalvaro's answer gets most of the way there for some applications, but isn't full, and it requires the ViewController to know about the artificial NSWindowController, which doesn't feel right.
Like #cdalvaro, the first step is to build your own subclass of NSWindowController, and to set the Storyboard WC to that class. You can then create all of your connections to and from the NSToolbar (both #IBOutlets & #IBActions) in the new WindowController. So far so good.
The last step, which I haven't seen anywhere else, is how to refer to the ViewController in the WindowController - you can't create an #IBOutlet to it - for the same reasons that we got here in the first place - you can't create references across scenes in the Storyboard. However, the WindowController must have a reference to the ViewController, and it does... self.window!.contentViewController! as! ViewController
Here's a complete WindowController with a checkbox that sets values in the ViewController, without the ViewController having to know anything...
class MyWindowController: NSWindowController {
var viewController: ViewController {
get {
return self.window!.contentViewController! as! ViewController
}
}
#IBOutlet weak var aSwitch: NSButton!
#IBAction func toolbarActionA(sender: AnyObject) {
println("toolbarActionA")
self.viewController.a = !self.viewController.a
self.aSwitch.state = self.viewController.a ? NSOnState : NSOffState
}
}
This helped me for the IBOutlet solution you are looking for:
https://stackoverflow.com/a/27555237/3398062
Update (explanation)
I discovered this thread because I was trying to create a Circular Progress Indicator inside the toolbar and I didn't know how to animate it from the ViewController since the IBOutlet was declared inside a custom WindowController.
Finally, I found the post that I have added above which describes how to access to IBOutlets from other classes.
In essence what I have done is the following:
Create a custom NSWindowController subclass (CustomWindowController) for the Window Controller so I can add the IBOutlet for the ProgressIndicator:
Code Example:
#interface CustomWindowController : NSWindowController
#property (weak) IBOutlet NSProgressIndicator *progressIndicator;
#end
Then in the ViewController class, in the method I want to use to update the state of the Progress Indicator, I create and object of the custom Window Controller.
Code Example:
CustomWindowController *customWindowController = self.view.window.windowController;`
Finally, to change the state of the Progress Indicator there is only to call the method from the created custom object.
Code Example:
[customWindowController.progressIndicator startAnimation:sender];
or
[customWindowController.progressIndicator stopAnimation:sender];
This video helped me how to create a toolbar without writing a single line of code: https://www.youtube.com/watch?v=XSQocHG3IjA
You can add the 'Window Controller' item from the Object Library (if you don't have one), connect to a View Controller (where you want your toolbar to display) and follow the video! For custom Toolbar buttons add 'Image Button' item to the Toolbar just by dragging from the Object Library. Then you can pick an image for a button, set the size and so on...
Here is a general solution for the outlets and actions. it allows you to preform all the the same functions as an iboutlet would for a tool bar item and also allows you to set the button to a function instead of creating an ibaction. hope it helps :P
override func viewDidLayout() {
var x = self.view.window?.toolbar?.items[1].label
println(x)
if(self.view.window?.toolbar?.items[0].label! != "Check")
{
toobarediting()
}
println("didlay")
}
func toobarediting() {
self.view.window?.toolbar?.insertItemWithItemIdentifier("Check", atIndex: 0)
}
func toolbarcheck(functiontoset: Selector) {
var y = self.view.window?.toolbar?.items[0] as NSToolbarItem
y.action = functiontoset
if(functiontoset != nil)
{
y.enabled = true
}
}
Here is a link to my question attempting to get a cleaner answer
http://www.stackoverflow.com/questions/27371439/use-nstoolbar-outlet-xcode-6-and-storyboard/27374449
but it looks like from the answers i have gotten so far this is the best option.
The same problem of IBOutlets also applies to KVO. One of the features of the NSSplitViewController is the canCollapse property for each split view item which supports KVO, but this is unusable in IB just like IBOutlets.
The only workaround I can see is to add a NSObjectController to each scene and when the scene loads, set the object controller's object to the object in the other scene (already loaded) that you want to reference.

How to connect outlet in a table cell view?

I am using a view-based table, and I want to create an outlet for an element in a cell view. I cannot get the outlet to connect though... it's always nil.
Specifically, I have a NSProgressIndicator in a table cell and want to manipulate it in code.
Here's what I have so far:
I have created a subclass of NSTableView, with the corresponding outlet property:
#interface MyTableCellView : NSTableCellView
#property IBOutlet NSProgressIndicator *myProgressIndicator;
#end
#implementation MyTableCellView
-(void)awakeFromNib
{
// _myProgressIndicator is nil!
}
#end
And I have set the custom class in the nib. The existing NSTableCellView is replaced with MyTableCellView via the dropdown.
At this point, some observations:
If I Ctrl+Click and drag the progress indicator to connect this outlet, it is not shown.
Likewise, if I try to Ctrl+Click and drag the progress indicator using the assistant editor, I can only connect to the property via binding. It doesn't recognize this as a valid outlet.
However this outlet IS shown on the sidebar, with a warning that it doesn't exist:
I know MyTableCellView is being used. Breakpoint on awakeFromNib confirms this, and confirms that _myProgressIndicator is nil.
This is a sandbox project, with barely more than what I've described.
SO, how do I access this progress indicator from code?
I don't believe you should do it that way; instead:
Modify the model object used by the table view's data source to populate the table view.
Call the table view reloadData (or better reloadDataForRowIndexes:columnIndexes:).
Therefore you should only need an outlet to the table view to do this and any modification of the table view's cell objects should be done within the table view delegate and/or data source methods.
As described in the question, the usual method to connect outlets in Interface Builder does not work (maybe it is fixed in a recent version of Xcode, I am still using a version earlier than 6). Anyhow, it does work via the Connections Inspector.
Create an IBOutlet in your subclass of NSTableCellView.
In your XIB, from Identity Inspector, change the NSTableCellView to your subclass.
From Connections Inspector, drag from your outlet to the View object.

Click in view of NSCollectionViewItem

I'm new to Cocoa dev, so many concepts of it are not clear to me...
I'm trying to build a simple app which will use Flickr API to retrieve user photosets and show them in a NSCollectionView, by clicking them, will start to download the photos of the photo set.
I'm using Xcode 5.0.1 with latest SDK which is 10.9
After reading some articles about how to use binding to deal with NSCollectionView, I'm now facing another problem regarding handling events in NSCollectionViewItem.
Per I understanding, mouse events can be easily handled by implement
-(void) mouseDown:(NSEvent *)theEvent
In a NSView subclass, say
#interface MyViewController : NSView {
}
And assign the view custom class to the subclass I made (MyViewController) in InterfaceBuilder.
Now, I have no problem to do as above, and the mousedown did handled as expect in most of widgets.
The problem is, I have a NSCollectionViewItem subclass as below:
#interface MyItemController : NSCollectionViewItem {
}
I'm trying to implement mousedown method there, this class was set to as File's Owner in a separated nib file. And the view will be automatically load when the NSCollectionView loaded.
Now, MyItemController cannot be as customer class in the view object in IB which is obviously because of it is not a NSView subclass but a NSCollectionViewItem subclass.
If I write a subclass of NSView and make the custom class of view object, I can get the mousedown.
However, I cannot get the representedObject and index of NSMutableArray in this approach and they are the essential information I need.
So my question is, what is the right way to deal with mouse events view of NSCollectionViewItem?
My code in GitHub here:
https://github.com/jasonlu/flickerBackupTool
Thanks!
UPDATE
I found a approach to solve this problem is by subclassing NSView and implement mousedown and use super, subviews to get and index and the array itself
- (void)mouseDown:(NSEvent *)theEvent {
NSCollectionView *myCollectionView = (NSCollectionView *)[self superview];
NSInteger index = [[myCollectionView subviews] indexOfObject:self];
NSLog(#"collection view super view: %#",myCollectionView);
NSLog(#"collection index: %ld",index);
NSLog(#"array: %#", [[myCollectionView content] objectAtIndex:index]);
}
It seems work, but I'm not sue if this is the best practice, it looks like depends on view too much and took a long way to reach the array.
I wouldn't bet that NSCollectionView always creates all subviews (subviews which are far away from the viewing area might be delayed and/or reused). Therefore, I wouldn't rely upon subview searching.
Overload NSViewController to create an NSView so that the representedObject assigned to the NSViewController is accessible from the NSView. From there you could search the actual content for index determination.
Overloading NSCollectionView and recording the actual index during view creation would probably not work well because a deleted item probably doesNot re-create any views.

Can't CTRL+Drag NSButton to custom NSView header

I'd like to create a custom NSTableCellView instantiated by Interface Builder. I've set my Table Cell View class to MyTableCellView, and properly created MyTableCellView : NSTableCellView .m/.h files.
However, I just can't CTRL+Drag a simple button from inside this view to MyTableCellView.h in order to create an IBOutlet.
Here is a video to show what happens: http://youtu.be/sNNbuVT-SZs.
How the view is subclassed:
How I try to CTRL+Drag a button
Also, sometimes, Interface Builder just don't allow the cell view's class to be modified. What's happening ?
I finally found a solution, that is a little weird but works as expected. Instead of connecting the NSButton to MyTableCellView header directly, I used the reversed path:
Manually create an outlet:
#property(retain, nonatomic) IBOutlet NSButton* button;
Then click the empty circle on the left, and drag it to your XIB file's button:
I have no idea why it works this way, please let me know if you know the anwser.

cocoa + context sensitive menu on NSTableView with multiple rows selected

i am having a problem displaying context sensitive menu on control click on a tableview when multiple rows are selected.
Its working fine when a single row is selected and then control clicked on it.
The way i am implementing this is shown below:
-(void)doSingleClick
{
NSLog(#"single clicked");
if([[NSApp currentEvent] modifierFlags] & NSControlKeyMask)
{
NSLog(#"control clicked.......");
[NSMenu popUpContextMenu:[self showContextMenu] withEvent:[NSApp currentEvent] forView:tableView];
return;
}
}
and showContextMenu function returns a NSMenu object.
I am dong it this way as my table view for some strange reason does not recognize mouseDown or mouseUp or menuForEvent events.
the problem with the above code segment is, when multiple rows are selected and control clicked, it does not recognize the control click and does not go into that loop and hence not displaying the context menu.
Please suggest me a mechanism to achieve this.
Thanks
I don't recommend the approach that is given in the answers above. Instead, look at the "DragNDropOutlineView" example in Leopard and higher. That, and the release notes, give a proper way to implement contextual menus for a single row, or multiple rows. This includes having AppKit automatically do the proper highlighting.
corbin dunn
(NSTableView Software Engineer)
i hve tableviewcontroller class which is a subclass of NSTableView.
That's very bad naming and suggests that you are not architecting your application properly. Views aren't controllers. Keep them separate.
but this class in which i implemented menuForEvent method but its not getting called for some reason.
Did you make your table view an instance of this class in Interface Builder? If not, your instance is still an NSTableView, and the subclass you wrote is what Ian Hickson might call “a work of fiction”.
Corbin's answer is the best one here.
link text
I don't believe the action method is called when multiple rows are selected.
What would probably be a lot easier would be to override the menuForEvent: method in NSTableView. You'd have to create a subclass of NSTableView to do this, but it would be a cleaner solution.
You could also create an informal protocol (a category on NSObject) and have the NSTableView delegate return the appropriate menu.
#interface NSObject (NSCustomTableViewDelegate)
- (NSMenu *)tableView:(NSTableView *)tableView menuForEvent:(NSEvent *)event;
#end
#implementation NSObject (NSCustomTableViewDelegate)
- (NSMenu *)tableView:(NSTableView *)tableView menuForEvent:(NSEvent *)event {
return nil;
}
#end
And in your NSTableView subclass:
- (NSMenu *)menuForEvent:(NSEvent *)event {
return [[self delegate] tableView:self menuForEvent:event];
}

Resources