Cocoa and Objective-C: Up and Running (by me) is now available from O'Reilly.

Using NSWorkspace with Files

As a follow-up to the previous article on NSWorkspace and applications, we're going to take a look at how to use NSWorkspace to deal with files. You can think of NSWorkspace's file management as a Cocoa-front end to the type of functions offered by the Finder (although NSWorkspace predates the Finder).

First, let's take a look at the basic task of opening a file:

NSString * path    = @"/Developer/About Xcode Tools.pdf";
NSURL    * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws openURL: fileURL];


This should open the Xcode Tools PDF in Preview. Now what if you want to open the PDF in, say, Safari? Easy enough:

NSString * path    = @"/Developer/About Xcode Tools.pdf";
NSURL * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws openFile:[fileURL path] withApplication:@"Safari"];


It might seem strange to take a path, convert it to a NSURL, then convert it back to a path. The problem is that there's no method to open a URL with a specific app in Tiger, so we have to ask the NSURL for its path.

Why bother with this approach at all? Why not just use normal file paths? Slowly but surely, a number of different parts of Cocoa are demphasizing static path strings and recommending the use of NSURLs. The idea here is to get into the habit early and often.

If you want to select a file in the Finder, you can do something like the following example. This is useful for implementing a "Reveal in Finder" contextual menu item.

NSString * path    = @"/Developer/About Xcode Tools.pdf";
NSURL    * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];
[ws selectFile:[fileURL path] inFileViewerRootedAtPath:nil];



We can also gather some information about a file:

NSString    * path = @"/Developer/About Xcode Tools.pdf";
NSURL       * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];

NSString    * appName;
NSString    * fileType;

[ws getInfoForFile: [fileURL path]
       application: &appName
              type: &fileType];
        
NSLog (@"appName: %@", appName);
NSLog (@"type: %@", fileType);


Note how we pass the appName and fileType params in with the ampersand (&) in front. C calls this the addressof operator. Without getting into the nitty gritty, this means that the method can write directly to variables we've defined. You know you need to use an addressof operator when you see a (**) parameter like this:

- (BOOL)getInfoForFile:(NSString *)fullPath
  application:(NSString **)appName
         type:(NSString **)type;


Cocoa does this in some places where a method needs to return more than one thing. In any case, the above example gives us something like this:

appName: /Applications/Preview.app
type: pdf


Now this is an interesting case because the docs for this method say the following about the "type" parameter:

The NSString pointed to by type contains one of the values described in "Constants".


In theory, that means that type should be one of the following:

NSPlainFileType
NSDirectoryFileType
NSApplicationFileType
NSFilesystemFileType
NSShellCommandFileType


In practice, though, the type param seems to actually contain the file type, such as "pdf" or "rtf". I've filed a documentation bug.  :)

Moving on, we can also get some information about the things we're allowed to do to a file:

NSString * path    = @"/Developer/About Xcode Tools.pdf";
NSURL    * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];

BOOL removable;
BOOL writeable;
BOOL unmountable;                

[ws getFileSystemInfoForPath:[fileURL path]
                 isRemovable: &removable
                  isWritable: &writeable
               isUnmountable: &unmountable
                 description: NULL
                        type: NULL];
        
NSLog (@"removable: %i", removable);
NSLog (@"writeable: %i", writeable);
NSLog (@"unmountable: %i", unmountable);


Which gives us output like this:

removable: 0
writeable: 1
unmountable: 0


The "unmountable" param essentially tells us if the file is on a removable device or not. If these two methods don't quite quench your thirst, the NSFileManager class can provide more details about a file.

Now, onto file operations. If you want to copy a file, you can do it as such:

NSString * name  = @"About Xcode Tools.pdf";
NSArray  * files = [NSArray arrayWithObject: name];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];

[ws performFileOperation: NSWorkspaceCopyOperation
                  source: @"/Developer/"
             destination: @"/Users/scott/Desktop/"
                   files: files
                     tag: 0];


Obviously, you can add more file (or directory) names to the files array, but they must all reside at the root level of the source directory.

You can also move files to the Trash:

NSString * name  = @"About Xcode Tools.pdf";
NSArray  * files = [NSArray arrayWithObject: name];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];

[ws performFileOperation: NSWorkspaceRecycleOperation
                  source: @"/Users/scott/Desktop/"
             destination: @""
                   files: files
                     tag: 0];


In the above example, we use an empty string for the destination because it's unnecessary for this operation. You might also want to take a look at NSWorkspaceDestroyOperation and  NSWorkspaceDuplicateOperation. (side note: NSFileManager also has some of its own file manipulation methods).

Although this was touched on briefly in the last installment, it's worth mentioning that -iconForFile: works for any kind of file, not just applications.

NSString * path    = @"/Developer/About Xcode Tools.pdf";
NSURL    * fileURL = [NSURL fileURLWithPath: path];

NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSImage  * icon = [ws iconForFile: [fileURL path]];
NSLog (@"icon: %@", icon);


You can also use -iconForFileType: to get a icon for a particular kind of file, such as @"xcodeproj".
Design Element
Using NSWorkspace with Files
Posted Nov 11, 2005 — 12 comments below




 

Olivier — Nov 11, 05 533

How can NSWorkspace predate the Finder? The Mac already existed when Jobs created NeXT. ;-)

Scott Stevenson — Nov 11, 05 535 Scotty the Leopard

Technically correct (two apps, both called Finder), though you know what I mean. :)

Jussi — Nov 13, 05 540

another great read, thanks

Dominik Wagner — Mar 12, 06 928

Hi!

Do you know of any way to do reveal in finder that doesn't cause all finder windows to come in front of my app?

cheers,
dom

Scott Stevenson — Mar 13, 06 930 Scotty the Leopard

Hi Dom --

As far as I know, that's a behavior of the Finder more than NSWorkspace. Once it's activated, everything comes to the front. If you find a solution, let me know.

Philip Orr — Feb 02, 07 3483

I have to admit that all of your articals I've read so far are fantastic and please keep up the good work.

One quick question though. Is it possible to use the NSWorkspace to monitor if a folder has been changed, i.e. a new file added, or removed.

Thanks
Philip

Scott Stevenson — Feb 02, 07 3484 Scotty the Leopard

Is it possible to use the NSWorkspace to monitor if a folder has been changed
I don't think so. There's a lower-level API for that called kqueue. Uli Kusterer has a Objective-C wrapper for it called UKKQueue. It's on this page, about halfway down.

Philip Orr — Feb 02, 07 3485

Ah, very good. Looks like what I'm looking for. Thanks a lot for the quick reply, most sites take at least a few days or more often weeks.

Philip

Lars Sonchocky-Helldorf — May 29, 08 5955

You shouldn't have filed a documentation bug. The behaviour is outright wrong as you can see if you run the following code:

(code snipped by Scott because it's far too long for a comment)

sadly Apple closed my bug report long time ago

regards,

Lars

Scott Stevenson — May 29, 08 5959 Scotty the Leopard

@Lars Sonchocky-Helldorf: You shouldn't have filed a documentation bug. The behaviour is outright wrong as you can see if you run the following code

Hi, Lars. I edited the comment because it was far too verbose for this context. If you want to link the source file, that's fine.

Thanks.

victor jalencas — Apr 17, 09 6689

Great info. Do you know if there is any Cocoa call (I only found how to do it in plain C) to change the label of a file (i.e., the colour of the file in Finder)?

cheers, victor

Andre — Jun 30, 09 6821

Also, there doesnt seem to be an easy way to select multiple files all in the same window ... or is there?




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.




Technorati Profile
Copyright © Scott Stevenson 2004-2008