objective-cinfo.plistnsdocumentutinssavepanel

Cocoa app refuses to save document in user selected file format (export)


I'm trying to modernize a 20+ year old cocoa/objective-c application. It is document-based and it can read/write its own data to file, but the user can also choose saveAs… to export the document in standard formats (not read by the app).

From the (somewhat confusing) documentation I got the impression that an accessory panel shouldn't be needed to achieve this, given the correct information in Info.plist.

I was expecting the user to pick a file format in the save panel and that that selection would be passed as the type when -dataOfType:error: is invoked. However, type is always the document's native type, regardless of the choice in the save panel.

enter image description here

I'm trying this out on a toy project with a vanilla document based project with the following changes:

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>LSHandlerRank</key>
            <string>Default</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>net.sourceforge.foo</string>
            </array>
            <key>NSDocumentClass</key>
            <string>Document</string>
        </dict>
    </array>
    <key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.data</string>
            </array>
            <key>UTTypeIcons</key>
            <dict/>
            <key>UTTypeIdentifier</key>
            <string>net.sourceforge.foo</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>foo</string>
                </array>
            </dict>
        </dict>
    </array>
    <key>UTImportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.plain-text</string>
            </array>
            <key>UTTypeDescription</key>
            <string>Example Text</string>
            <key>UTTypeIdentifier</key>
            <string>com.example.plain-text</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>exampletext</string>
                </array>
            </dict>
        </dict>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>net.daringfireball.markdown</string>
            </array>
            <key>UTTypeIcons</key>
            <dict/>
            <key>UTTypeIdentifier</key>
            <string>com.example.mrkdwn</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>mrkdwn</string>
                </array>
            </dict>
        </dict>
    </array>
</dict>
</plist>

Document.m

+ (BOOL)autosavesInPlace {
    return NO;
}

+ (NSArray *)writableTypes {
    return @[
        @"net.sourceforge.foo",
        @"com.example.plain-text",
        @"com.example.mrkdwn",
        @"net.daringfireball.markdown"
    ];
}

- (BOOL) prepareSavePanel:(NSSavePanel *) savePanel {
    savePanel.allowedContentTypes = @[
        [UTType typeWithIdentifier:@"com.example.plain-text"],
        [UTType typeWithIdentifier:@"net.sourceforge.foo"],
        [UTType typeWithIdentifier:@"com.example.mrkdwn"],
    ];
    savePanel.showsContentTypes = YES;
    return true;
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
    NSLog(@"typeName: %@", typeName);
    return nil;
}

Obviously I'm doing something wrong. Either I´m interpreting the docs wrong (and this approach is totally insane) or there is something not quite right in the Info.plist and/or the code. Any hints to what is going on here would be much appreciated.


Solution

  • Here's how to implement Export in the vanilla document based project:

    1. Add the imported file types (with a description) to info.plist

    2. Implement writableTypes

    3. Add a menu item "Export…" and connect it to the First Responder and action saveDocumentTo:

    I vaguely remember reading documentation about this somewhere, but I can't find it anymore.