I need to drag a row from NSTableView containing an image path and drop it over NSImageView, and the image of the dragged row should appear in the imageview. Help appreciated
Thanks a lot. It really worked. I registered NSImageView and NSTableView for NSStringPboardType and NSFilenamesPboardType. Then in TableView delegate I used
the following code.
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
NSString *string = [filePath objectAtIndex:[rowIndexes firstIndex]];
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
[pboard setString:string forType:NSStringPboardType];
return YES;
}
And in NSImageView NSDragging Destination informal protocol, used following code.
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSString *str = [[sender draggingPasteboard] stringForType:NSStringPboardType];
myImage = [[NSImage alloc] initWithContentsOfFile:str];
[self setImage:myImage];
[self setNeedsDisplay: YES];
return NSDragOperationCopy;
}
cheers :)
First, in your table data source, implement the necessary methods for table row dragging. You'll put data representing the row onto the drag pasteboard in one or more data types. One type you'll use for this is NSFilenamesPboardType, which takes an array of pathnames.
Then, make a subclass of NSImageView that can handle NSFilenamesPboardType in drops. (You'll need to implement methods from the NSDraggingDestination informal protocol.) Then make your image view an instance of this subclass, instead of NSImageView, and register that view for NSFilenamesPboardType.
Related
I am trying to solve this issue regarding a UITableView cell being off screen, or outside the visible area.
Within my tableview cells I have a UITextField which I am able to parse easily using the code below. However I find that that for the cells that are not visible it returns a NULL value.
I am guessing this is a feature to improve memory usage but is there anyway to turn it off? Or if not is there a work around?
InputCell *inputCell = (InputCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
UITextField *cellContent = (UITextField *)[inputCell.textInput viewWithTag:0];
NSLog(#"Cell Content: %#" , cellContent.text);
Thanks and thanks again!
Views need models, especially table views. A model is some object (often a group of objects in collection classes) that represents the state of your app. A table view requires an array. The datasource protocol asks you to describe that array. Since tableview cells are part of the view, they shouldn't be relied upon to keep the state of your app. That's up to you as follows:
In your vc's private interface:
#property(strong, nonatomic) NSMutableArray *myDatasource;
Early, like in view did load:
myDatasource = [NSMutableArray array];
// fill it with strings
In numberOfRowsInSection...:
return self.myDatasource.count;
In cellForRowAtIndexPath:
cellContent.text = self.myDatasource[indexPath.row];
Make the vc your textField's delegate and implement:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *candidateString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSIndexPath *indexPath = [self indexPathWithSubview:textField];
self.myDatasource replaceObjectAtIndex:[indexPath.row] withObject: candidateString];
return YES;
}
This helper finds the indexPath of any textField (any subview) of any cell:
- (NSIndexPath *)indexPathWithSubview:(UIView *)subview {
while (![subview isKindOfClass:[UITableViewCell self]] && subview) {
subview = subview.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)subview];
}
It looks like a lot, but not so bad when you get used to it. The pattern is always - think of the objects that describe the state of your app (model). Think of views that best describe and manipulate that state (view). Make your view controllers (controllers) (a) notice model changes and change the views accordingly, and (b) hear about user actions from the views and update the model.
I'm trying to understand how best to impliment drag and drop of files from the Finder to a NSTableView which will subsequently list those files.
I've built a little test application as a proving ground.
Currently I have a single NSTableView with FileListController as it's datasourse. It's basically a NSMutableArray of File objects.
I'm trying to work out the best / right way to impliment the drag and drop code for the NSTableView.
My first approach was to subclass the NSTableView and impliment the required methods :
TableViewDropper.h
#import <Cocoa/Cocoa.h>
#interface TableViewDropper : NSTableView
#end
TableViewDropper.m
#import "TableViewDropper.h"
#implementation TableViewDropper {
BOOL highlight;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code here.
NSLog(#"init in initWithCoder in TableViewDropper.h");
[self registerForDraggedTypes:#[NSFilenamesPboardType]];
}
return self;
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
NSLog(#"performDragOperation in TableViewDropper.h");
return YES;
}
- (BOOL)prepareForDragOperation:(id)sender {
NSLog(#"prepareForDragOperation called in TableViewDropper.h");
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"%#",filenames);
return YES;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
highlight=YES;
[self setNeedsDisplay: YES];
NSLog(#"drag entered in TableViewDropper.h");
return NSDragOperationCopy;
}
- (void)draggingExited:(id)sender
{
highlight=NO;
[self setNeedsDisplay: YES];
NSLog(#"drag exit in TableViewDropper.h");
}
-(void)drawRect:(NSRect)rect
{
[super drawRect:rect];
if ( highlight ) {
//highlight by overlaying a gray border
[[NSColor greenColor] set];
[NSBezierPath setDefaultLineWidth: 18];
[NSBezierPath strokeRect: rect];
}
}
#end
The draggingEntered and draggingExited methods both get called but prepareForDragOperation and performDragOperation don't. I don't understand why not?
Next I thought I'll subclass the ClipView of the NSTableView instead. So using the same code as above and just chaging the class type in the header file to NSClipView I find that prepareForDragOperation and performDragOperation now work as expected, however the ClipView doesn't highlight.
If I subclass the NSScrollView then all the methods get called and the highlighting works but not as required. It's very thin and as expected round the entire NSTableView and not just the bit below the table header as I'd like.
So my question is what is the right thing to sublclass and what methods do I need so that when I peform a drag and drop from the Finder, the ClipView highlights properly and prepareForDragOperation and performDragOperation get called.
And also when performDragOperation is successful how can this method call a method within my FileListController telling it to create a new File object and adding it to the NSMutableArray?
Answering my own question.
It seems that subclassing the NSTableView (not the NSScrollView or the NSClipView) is the right way to go.
Including this method in the subclass :
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
return [self draggingEntered:sender];
}
Solves the problem of prepareForDragOperation and performDragOperation not being called.
To allow you to call a method within a controller class, you make the delagate of your NSTextView to be the controller. In this case FileListController.
Then within performDragOperation in the NSTableView subclass you use something like :
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
id delegate = [self delegate];
if ([delegate respondsToSelector:#selector(doSomething:)]) {
[delegate performSelector:#selector(doSomething:)
withObject:filenames];
}
This will call the doSomething method in the controller object.
Updated example project code here.
In master view application xcode generates ready app with table view and the plus button. I want to change that button to to add a new cell but not with the date as it is by default. I want to add two text fields like label->textfield, label->textfield.
In code I have this:
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (GCDetailViewController *) [[self.splitViewController.viewControllers lastObject] topViewController];
}
and the function:
- (void)insertNewObject:(id)sender{
if (!_objects) {
_objects = [[NSMutableArray alloc] init];
}
[_objects insertObject:[UITextField alloc] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Thank You
The way to think about this is model-view-controller (MVC). _objects is your model representing whatever the user thinks is in the table. Say it's a to-do list, then objects could be an array of NSObject subclass you create like TodoItem.
You would insert new TodoItems into _objects, then tell your table (the "View" in MVC) that it's model has changed. You can do that imprecisely using reloadData, or in a more targeted fashion as your code suggests, calling insertRowsAtIndexPaths - but that call must be sandwiched between tableView beginUpdates and endUpdates.
You can add textFields in code in your cellForRowAtIndexPath, or in the cell prototype in storyboard. Your table view datasource should always refer to objects... i.e. numberOfRows answers self.objects.count, cellForRowAtIndexPath gets:
TodoItem *item = [self.objects objectAtIndexPath:indexPath.row];
and uses that item's properties to initialize the textField's text. Also, incidentally, objects should be declared like this:
#property(strong,nonatomic) NSMutableArray *objects;
...and your code should refer to self.objects almost everywhere (not _objects). Initializing it on the first insert is too late, because the table needs it to be valid right-away, as soon as it's visible. Usually, a good practice is a "lazy" init replacing the synthesized getter...
- (NSMutableArray *)objects {
if (!_objects) { // this one of just a few places where you should refer directly to the _objects
_objects = [NSMutableArray array];
}
return _objects;
}
You might find using the free Sensible TableView framework really helpful here. Here is some sample code to illustrate how you'd do this using the framework:
- (void)insertNewObject:(id)sender{
SCTableViewSection *section = [self.tableViewModel sectionAtIndex:0];
[section addCell:[SCTextFieldCell cellWithText:#"Enter Text"]];
}
Comes in really handy for these types of situations.
I've read a lot about this but i can't get it to work, i have a custom NSCell with this code
#import "ServiceTableCell.h"
#implementation ServiceTableCell
-(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(#"I'm being called");
NSView *newview = [[NSView alloc] initWithFrame:cellFrame];
NSImage *image = [NSImage imageNamed:#"bg.png"];
NSRect imagesize;
NSImageView *IMV = [[NSImageView alloc] initWithFrame:imagesize];
[IMV setImage:image];
[newview addSubview:IMV];
[controlView addSubview:newview];
}
And this my NSTableView data source:
- (long)numberOfRowsInTableView:(NSTableView *)tableView {
return 3;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(long)row
{
return [[ServiceTableCell alloc] initTextCell:#"dd"];
}
As i understand, the drawwithframe... gets called when the cell is initialized but it never gets called, so, what am I missing?
The method tableView:objectValueForTableColumn:row: should return an object value, not a cell.
Note that NSTableView is substantially different from UITableView, which you may be familiar with. For example, the data source doesn't return cells that are filled with the data, but returns the data. And the cell type in an NSTableView is set per column, you can't have a different kinds of cells in one column (well, technically, that's not entirely true, you could have different cells through -[NSTableColumn dataCellForRow:]).
So thanks to #puzzle answer and a little more digging the answer was to set my subclass of NSCell as the main cell in the InterfaceBuilder, then the method was being called, and as he said, in tableView:objectValueForTableColumn:row: i needed to return the data to then draw it.
Okay, I've got a custom class called "Task", which represents a task to be done. I've got an NSMatrix which acts as a calendar. I want the user to be able to drag an icon from an nscollectionview (I've had no trouble setting up the nscollectionview) onto a cell in the nsmatrix, thereby assigning that task to that day. I just can't seem to get the nsmatrix to respond to the drag or the drop at all.
I've implemented the method:
- (BOOL)collectionView:(NSCollectionView *)cv writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard
{
[pasteboard declareTypes:[NSArray arrayWithObject:TASK_UTI] owner:self];
NSUInteger index=[indexes firstIndex];
Task* task=[[cv content] objectAtIndex:index];
NSData* taskData=[NSKeyedArchiver archivedDataWithRootObject:task];
[taskData retain];
BOOL success=[pasteboard setData:taskData forType:TASK_UTI];
return success;
}
in my nscollectionview delegate as shown above.
I've sent [self registerForDraggedTypes:[NSArray arrayWithObjects:TASK_UTI, nil]] in my NSMatrix subclass (called "Calendar").
I've implemented the methods:
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
in my Calendar (NSMatrix subclass) class.
Some debugging shows that the NSMatrix/Calendar object is not even running the dragging methods above. What gives?
Did you define your Calendar class to implement dragging destination protocol?
First off, you should use your own domain name, not “com.yourcompany”, in the UTI.
Second, did you export the UTI in your Info.plist?