Accept drag'n'drop from iPhoto or Aperture - objective-c

I've created an app, containing an ImageView subclass which accepts drag'n'dropping files/folders directly from Finder.
The thing is I'm now trying to make it accept photos, either from iPhoto or Aperture, as well.
Which PboardTypes should I register for?
All I'm currently doing is :
[self registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
Any ideas?

Using Pasteboard Peeker (from Apple) shows me that Aperture gives you file names/URLs as well as "aperture image data" (whatever that is). iPhoto appears only to give "ImageDataListPboardType", which is a PLIST. I'm guessing you could NSLog() that out to see its structure and pull the image information from it. It may possibly include the filename/URL info as well as the actual image as data.

You are correct to register for NSFilenamesPboardType. To complete the task:
1: Make sure you accept the copy operation in draggingEntered. The generic operation is insufficient.
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
NSPasteboard *pasteboard = [sender draggingPasteboard];
if ( [[pasteboard types] containsObject:NSFilenamesPboardType] ) {
if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
2: There will be one filename per photo. Do something with them.
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pasteboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:NSFilenamesPboardType])
{
NSData* data = [pasteboard dataForType:NSFilenamesPboardType];
if(data)
{
NSString *errorDescription;
NSArray *filenames = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:kCFPropertyListImmutable format:nil errorDescription:&errorDescription];
for (NSString* filename in filenames)
{
NSImage* image = [[NSImage alloc]initWithContentsOfFile:filename];
//Do something with the image
}
}
}
return YES;
}

Related

Accepting TextClipping with NSImageView Drop Box

If you drag and drop a selection of text onto a folder, you will get a file with an extension of textClipping. TextEdit's document window accepts a text selection. Many applications do accept textClipping. How do you receive a text selection on an NSImageView drop box? The regular operation of performDragOperation doesn't appear to accept a selection of text.
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *urls;
if ([[pboard types] containsObject:NSURLPboardType]) {
urls = [pboard readObjectsForClasses:#[[NSURL class]] options:nil];
}
AppDelegate *appDelegate = (AppDelegate *)[NSApp delegate];
...
...
return YES;
}
These lines of code let me accept files, but not textClipping. What is the secret of accepting textClipping? Maybe, you can't accept it with NSImageView? Running a search with 'Objective-C textClipping' turns up nothing.
Thank you for your advice.
Text clippings are either strings or attributed strings (if the contents contain rich text).
To read those objects from the pasteboard you have to search for NSStringPboardType or NSRTFPboardType respectively.
NSStringPboardType can be read as NSString.
NSRTFPboardType can be read as NSAttributedString.
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
NSPasteboard* pboard = [sender draggingPasteboard];
NSArray* pboardContents = nil;
if ([[pboard types] containsObject:NSURLPboardType])
{
pboardContents = [pboard readObjectsForClasses:#[[NSURL class]] options:nil];
}
if ([[pboard types] containsObject:NSStringPboardType])
{
pboardContents = [pboard readObjectsForClasses:#[[NSString class]] options:nil];
}
if ([[pboard types] containsObject:NSRTFPboardType])
{
pboardContents = [pboard readObjectsForClasses:#[[NSAttributedString class]] options:nil];
}
NSLog(#"Pasteboard contents:%#", pboardContents);
return YES;
}

How Can I Save This Array of Images?

I'm very new to programming, and I jumped right into a project (I know thats not the smartest thing to do, but I'm learning as I go). The app that I'm writing has 10 UIImageViews that display a picture from the users camera roll. The code I'm using needs each of the UIImageViews to have tags. I'm currently using NSData to save the array images, and it works great, but I can't use this method anymore because NSData doesn't support the use of tags. I also can't use NSUserDefaults, because I can't save images to a plist. Here is how I'm attempting to do this (using the NSData method, which works but I have to edit this so that my tags work.)
This is my current code:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[self.array addObject:imageView.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:imageView2.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
...
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
[self.array addObject:imageView.image];
[self.array addObject:imageView2.image];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[self.array objectAtIndex:0] copy];
imageView2.image = [[self.array objectAtIndex:1] copy];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
[super viewDidLoad];
}
Any help or suggestions on how to edit this code so that I can save the images, while using tags is much appreciated, thanks!
EDIT: Here is my updated code:
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
NSLog(#"Saved Button Pressed");
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
}
-(void)viewDidLoad {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL];
for (NSString *fileName in docFiles) {
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
if (!imageView.image) {
imageView.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
You need to use "Fast Enumeration" to parse the array's objects, and write each object to disk sequentially. First, you're going to need to add the UIImageView objects to the array instead of the UIImage property of the UIImageView, so you can recover the tag. So instead of writing
[self.array addObject:imageView.image];
It will be
[self.array addObject:imageView];
Try to follow along with my code. I inserted comments on each line to help.
-(void)applicationDidEnterBackground:(UIApplication *)application {
//Obtain the documents directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//begin fast enumeration
//this is special to ObjC: it will iterate over any array one object at a time
//it's easier than using for (i=0;i<array.count;i++)
for (UIImageView *imageView in self.array) {
//get the imageView's tag to append to the filename
NSInteger tag = imageView.tag;
//get the image from the imageView;
UIImage *image = imageView.image;
//create a filename, in this case "ImageTAGNUM.png"
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
//concatenate the docsDirectory and the filename
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
}
To load the images from disk, you'll have to look at your viewDidLoad method
-(void)viewDidLoad {
//get the contents of the docs directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//Get the list of files from the file manager
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL]);
//use fast enumeration to iterate the list of files searching for .png extensions and load those
for (NSString *fileName in docFiles) {
//check to see if the file is a .png file
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
//you'll have to sort out how to put these images in their proper place
if (!imageView1.image) {
imageView1.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
Hope this helps
One thing you need to be aware of is that when an app enters the background it has about 5 seconds to clean up its act before it's suspended. The UIPNGRepresentation() function takes a significant amount of time and is not instantaneous. You should be aware of this. It would probably be better to write some of this code in other places and do it earlier than at app backgrounding. FWIW
You can use the [NSbundle Mainbundel] to store that images.
To get path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
First, there's still a problem in your for loop.
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
// ...
}
Before you make any other changes, you must understand why. imageView is your for loop control variable, which changes on each iteration through the loop. self.imageView is a different thing. It is the first of the 10 imageViews attached to your viewController. Every time this loop cycles, it looks at the first imageView, and only the first.
As for why saving doesn't work, it's probably because the arrays elsewhere aren't working. Add some logging to make sure there's something in the array, and that it has as many elements as you expect.
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
// Log to make sure the views expected have previously been stored.
// If the array is empty, or shorter than expected, the problem is elsewhere.
NSLog(#"Image view array before saving = %#", self.array);
for (UIImageView *imageViewToSave in self.array) {
NSInteger tag = imageViewToSave.tag;
UIImage *image = imageViewToSave.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
// Log the image and path being saved. If either of these are nil, nothing will be written.
NSLog(#"Saving %# to %#", image, imagePath);
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:NO];
}
NSLog(#"Save Button Pressed");
}

NSTextView Drag & Drop -- Characters not visible after drop

I've subclasses an NSTextView so that I can drop a file and copy the string contents of the file into the view (as opposed to the standard implementation which drops the filepath into the view). The text seems to be dropping correctly, but then is not visible after the drop. I can see that the cursor has moved and can even copy the dropped text out of the view and paste into, for example, TextEdit. I tried adding [self setNeedsDisplay:YES] at the end of my -performDragOperation: method, but the behavior did not change.
Here's the code I've written so far. I imagine this is not the best way to implement this. I'm new to drag and drop implementation in cocoa.
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
NSPasteboard *pb = [sender draggingPasteboard];
NSDragOperation dragOperation = [sender draggingSourceOperationMask];
if ([[pb types] containsObject:NSFilenamesPboardType]) {
if (dragOperation & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
if ([[pb types] containsObject:NSPasteboardTypeString]) {
if (dragOperation & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pb = [sender draggingPasteboard];
if ( [[pb types] containsObject:NSFilenamesPboardType] ) {
NSArray *filenames = [pb propertyListForType:NSFilenamesPboardType];
for (NSString *filename in filenames) {
NSStringEncoding encoding;
NSError * error;
NSString * fileContents = [NSString stringWithContentsOfFile:filename usedEncoding:&encoding error:&error];
if (error) {
// handle error
}
else {
[self setString:fileContents];
}
}
}
else if ( [[pb types] containsObject:NSPasteboardTypeString] ) {
NSString *draggedString = [pb stringForType:NSPasteboardTypeString];
[self setString:draggedString];
}
return YES;
}
I had a stub for -drawRect: that had no implementation.
After removing the stub, everything works exactly as intended.

Opening 'New' Document?

I have some code implemented in my myDocument.m file that simply attempts to load the last used document upon launch. However, upon launching from a fresh install, (or running after deleting the last used file) a 'new' document window does not appear. Does anyone know what to add to my code to do this? Here it is:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSURL *lastURL=[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:lastURL display:YES error:nil];
if (lastURL!=nil)
{
[docController openDocumentWithContentsOfURL:lastURL display:YES error:nil];
return NO;
}
return YES;
}
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
NSArray* urls = [[NSDocumentController sharedDocumentController] recentDocumentURLs];
if ([urls count] > 0){
NSURL *lastURL= [urls objectAtIndex: 0];
if ([[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:lastURL display:YES error:nil]){
return NO;
}
}
return YES;
}
EDIT
I changed it and tried it out it should work now.
What is docController, and why are you sending -openDocumentWithContentsOfURL:display:error: twice? Note that that method returns a document, not a URL, so using the return value as a URL wouldn’t work anyway.
The following is a cleaner, equivalent code:
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
id lastDoc = [[NSDocumentController sharedDocumentController]
openDocumentWithContentsOfURL:lastURL
display:YES error:NULL];
return (lastDoc == nil);
}
However, it still doesn’t explain why you don’t get an untitled document. What happens if you comment out -applicationShouldOpenUntitledFile: so that the application follows standard Cocoa behaviour? It could be the case that the problem lies elsewhere.

How to add file contents by dragging that file

I am using the following code to perform drag operation on NSTextView object.
- (BOOL)performDragOperation:(id )sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] )
{
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if ([[fileURL path] hasSuffix:#"plist"])
{
NSString *code = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:NULL];
int cnt = [[self string] length];
if (cnt) [self setSelectedRange:NSMakeRange(0, cnt)];
[self insertText:code];
return YES;
}
}
return NO;
}
I have declared this method in the .h file as well.
But after running the code it showing following warnings.
warning: 'AppConroller' may not respond to '-string'
(Messages without a matching method signature will be assumed to return 'id' and accept'...' as arguments.)
warning: 'AppConroller' may not respond to '-setSelectedRange:'
warning: 'AppConroller' may not respond to '-insertText:'
You're sending self (the AppController) messages it doesn't support. I think you mean to have [sender string], [sender setSelectedRange:], and [sender insertText:].
Is it possible it's just a typo? All your warnings refer to "AppConroller", but I bet the class is actually named "AppController".