Replace folders with wrappers, without rewriting too much code?

Hello,

My app uses folders as if they were files: a folder is a container for multiple file types. It’s handy, but I think that my “Open” and “Save” dialogs will be a bit confusing for my users.

If you have an Open dialog filtering folders, when you double-click on a folder it does not open this folder into the application, but indeed opens the folder into the dialog window, with all contained files disabled. You have to click on the “Choose” button. Same thing when you double click on a desktop’s folder: it opens, and does not launch my app. Not good.

I want my folders to disguise in files, to be filtered like files, to open like files when you double-click on them.

I read about the NSFileWrapper class, but as usual it’s not just «set the wrapper bit of the folder to transform it into a wrapper». It seems to be complicated (and of course it’s probably “very simple indeed” if you are used to do this).

Question: how can I, with minimal change of code, transform my folders into wrappers? I want to keep the folders contents as they are, just transform the folder into a (file? directory?) wrapper. Is it possible, or do I have to change all my app’s design?

Regards,

Is it a Cocoa document-based app, or are you ding your own open/save?

I do my own open/save. It follows the “choose folder” standard. Then I read files into this folder automatically, the user has nothing more to do.

You need to declare your file type as a package in the target properties. Then to save a package you can just create an empty file wrapper, write it, then write the contents to it as if it’s just a folder. Something like:

set aWrapper to current application's NSFileWrapper's alloc()'s initDirectoryWithFileWrappers_(missing value)
tell aWrapper to writeToFile_options_originalContentsURL_error_(theURL, current application's NSFileWrapperWritingAtomic, missing value, missing value)

Shane,

May I ask you for more help, please?

  1. In XCode 3.x, there was a check box to mark a document as package. Where is it in XCode 4? I can’t find it.
  2. what about the “Open” and “Save” dialogs (I’m using AS’s Choose folder, not your helpers, but this can be changed)
  3. is it possible to limit the dialog’s display to my package “file extension” as if it was a file?
  4. in my app, I use the NSFileManager (pretty handy class!) to make my folder the default one, so I don’t have to specify a complete path, only the file name. Can I do the same with a wrapper/package?
  5. Can I, for debug, open my package with right-click, as with TextEdit’s RTFD or application packages?

Thank you very much.

I think you have to add it under Additional properties (I’ve gone back to Xcode 3). Make a test app in Xcode 3 and open it in Xcode 4 to see.

You will probably also have to export the UTI. Off to the documentation for that (and that’s actually easier in Xcode 4 than Xcode 3. Sigh.)

Once you’ve done the above, it behaves just like a file.

Don’t know what you mean.

Yes, that’s standard package behavior.

In the example given in the doc, I found:

#import “FileWrapperSupport.h”
.

/*
NSPersistentDocument’s implementation is bypassed using the FileWrapperSupport category. The persistent
store coordinator is directed to use an internal URL rather than NSPersistentDocument’s default (the main file URL).
*/

  1. Do I have to import FileWrapperSupport.h explicitly or is it included into the frameworks?
  2. My files were saved with the class NSPersistentDocument. Do I have to change this?

Option 3 (because of unnecessarily complication of Cocoa): leaving things as they are and add to my application’s documentation a clause like “For God’s sake, do NOT modify the folder contents”.

Reminder: the former version of my app was dealing with indexed resources in one single file. :frowning:

Why? Are you really using Core Data? And if you are, you’re on your own :expressionless:

Regardless, try exporting the UTI first. That may be enough to fix the problem on its own.

Oh, I don’t know what I’m using, in fact. When I defined my docs types, “Persistent document” seemed a logical choice, that’s all, I didn’t look further – and my files were persistent, so I let this option on.

If I read the doc correctly, a file wrapper is a kind of, or retains somewhere, a dictionary which contains the relative path (URL) of its files. So I’m not sure it can’t be so simple. The code you proposed to me gives this error:

Indeed I found no method with this name. There is a:
“ writeToURL:options:originalContentsURL:error:
and a:
“ writeToFile:atomically:updateFilenames: Deprecated in Mac OS X v10.6
when I send writeToURL with POSIX path of returnedFolder as first parameter (and a posix file is equivalent to an URL, right?) I get:

Bad start. :confused:

Persistent documents are for core data only. If you’re ding all your own saving and opening, you may not need anything other than NSObject anyway – the point of using NSDocument is to get all the open/save/revert/etc behavior for free.

That’s the one.

Wrong. A path and a URL are different. Back to the book …

I shall do this, Master. :wink:

    on testCreatePackage_(sender)
        set sp to current application's NSSavePanel's savePanel()
        sp's runModal()
        set aWrapper to current application's NSFileWrapper's alloc()'s initDirectoryWithFileWrappers_(missing value)
        tell aWrapper to writeToURL_options_originalContentsURL_error_(sp's |URL|(), current application's NSFileWrapperWritingAtomic, missing value, missing value)
   end

creates a normal folder. Well, it’s already better.

The problem is, I don’t see where, in these calls, the NSFileWrapper guesses what is the file extension.

Still testing, getting nothing than errors or normal folders. In theory it seemed sooo simple.

It sounds like you’re no exporting the UTI properly. Post the relevant XML.

<?xml version="1.0" encoding="UTF-8"?> CFBundleDevelopmentRegion English CFBundleDisplayName AutoText CFBundleDocumentTypes CFBundleTypeExtensions atxt CFBundleTypeIconFile AtxtCardFileIcon CFBundleTypeName AutoText Card Document CFBundleTypeRole Editor NSDocumentClass CFBundleTypeExtensions atob CFBundleTypeIconFile AtxtObjectFileIcon CFBundleTypeName AutoText Object Document CFBundleTypeRole Editor NSDocumentClass CFBundleTypeExtensions atmp CFBundleTypeIconFile AtxtMapFileIcon CFBundleTypeName AutoText Map Document CFBundleTypeRole Editor NSDocumentClass CFBundleTypeExtensions atfh CFBundleTypeIconFile AtxtHeaderFileIcon CFBundleTypeName AutoText File Header Document CFBundleTypeRole Editor CFBundleExecutable ${EXECUTABLE_NAME} CFBundleGetInfoString Relational Editor CFBundleIconFile AutoTextIcns.icns CFBundleIdentifier com.IdemSoft.${PRODUCT_NAME:identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 5.6 CFBundleSignature ATXX CFBundleURLTypes CFBundleVersion 5.6.5d LSApplicationCategoryType public.app-category.educational-games NSHumanReadableCopyright ©1991 - 2011 IdemSoft NSMainNibFile MainMenu NSPrincipalClass NSApplication NSServices UIUpgradeOtherBundleIdentifier AutoTextIcns UTExportedTypeDeclarations UTTypeConformsTo UTTypeIconFile AtxtWrapper UTTypeIdentifier atwr UTImportedTypeDeclarations UTTypeConformsTo UTTypeIconFile AtxtWrapper UTTypeIdentifier atwr

Looks like you’re missing the key LSTypeIsPackage set to in your CFBundleDocumentTypes… This is what mine looks like and it works just fine:

	<dict>
		<key>CFBundleTypeExtensions</key>
		<array>
			<string>slugconfig</string>
		</array>
		<key>CFBundleTypeIconFile</key>
		<string>slugconfig.icns</string>
		<key>CFBundleTypeName</key>
		<string>NDSlugConfigurationDocument</string>
		<key>CFBundleTypeRole</key>
		<string>Editor</string>
		<key>LSTypeIsPackage</key>
		<true/>
		<key>NSDocumentClass</key>
		<string>NSDocument</string>
		<key>NSPersistentStoreTypeKey</key>
		<string>XML</string>
	</dict>

Could be the reason why it doesn’t work.

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

As well as what leonsimard says, your UTI exporting could do with some work. I don’t know why it’s in twice. Try something like this, putting in the correct bundle identifier:

	<dict>
		<key>LSTypeIsPackage</key>
		<true/>
		<key>UTTypeConformsTo</key>
		<array>
			<string>public.item</string>
			<string>public.composite-content</string>
			<string>com.apple.package</string>
		</array>
		<key>UTTypeDescription</key>
		<string>AtxtWrapper Document</string>
		<key>UTTypeIconFile</key>
		<string>AtxtWrapper</string>
		<key>UTTypeIdentifier</key>
		<string>com.yourbusiness.atwr-doc</string>
		<key>UTTypeTagSpecification</key>
		<dict>
			<key>public.filename-extension</key>
			<array>
				<string>atwr</string>
			</array>
		</dict>
	</dict>

I have a couple of questions on this topic too.

  1. What is the purpose for exporting the UTI? Is this an alternative to setting a file extension and clicking the package box in the target info pane? That worked fine for me – is there something else that exporting the UTI does for you?

  2. Is there a way, with or without file wrappers, to have an open panel that only shows files with a particular extension – and by shows, I mean that’s all it shows, not grayed out files of other types or folders?

  3. Am I right in thinking that there are two ways to save files in a directory file wrapper – one, by using addRegularFileWithContents_preferredFilename_ to add files to the file wrapper and then writing out the file wrapper to disc? And two, by writing out an empty directory file wrapper to disc and then saving files to it as if it were a regular folder?

Ric

To let other apps, like the Finder, know about the file type. Presumably it’s used/stored by launchd. It’s also a way of staking a claim to ownership of the filetype and extension.

No, it’s something you should do as well. See the doc “Declaring New Uniform Type Identifiers”.

I don’t think so. I suspect that falls into the category of subverting the HIG, so you’re going to have to fight the frameworks (or build your own dialog).

I have a feeling that using initRegularFileWithContents: and initDirectoryWithFileWrappers: is a third method. I’ve been using the wrapper-first method with no problems.

But why? Is it just good programming practice, or does it do something else that I don’t get by setting a document type, file extension and whether the file is package or not – when I do that, I get the correct extension and my app is the default opener of that type of file. So, if I’m not looking to share my data with other apps or have it somehow publicly available is there a need to do it?

As far as the document, it’s pretty much completely non-informative. It gives one example with no instructions, so I have no Idea how to write my own. I don’t even know the process – do you write that all manually yourself? It looks like I would have to learn XML to do that. When I look at the code you posted above and the example code in the docs, I see some similarities and some differences – so, I don’t know what is necessary in these declarations and I don’t see anything in the docs that teaches me how to do this.

Ric

Exporting a UTI makes it the definitive word on how files with that extension are treated. It also lets you add extra stuff like conformance and pasteboard types, should they apply to your docs. You can probably get away without it, but I’m not sure why you wouldn’t want to give the system the info it wants in the form it wants it.

You can write it by using the example in the docs as a template. There’s also a form for it in Xcode 4, although they seem to have left out the Package checkbox.

On a more pragmatic level, I got a log message when I made a mistake in one recently, so maybe it’s a useful way to check ow you’ve set up document UTIs.