NSDocumentController.openDocument 不允许选择自定义文件类型

NSDocumentController.openDocument not allowing selection of custom file type

我怀疑这是一个棘手的配置问题,我在 Info.plist 中的文档类型/UTI 声明中遇到了错误,但尝试了几种方法仍未找到解决方案。

我有一个基于 NSDocument 的 Mac 应用程序,用 Swift、Xcode 11 编写。它读取和写入后缀为 "mpxml" 的自定义文档类型。

在最早期的开发过程中,我没有为该类型设置一个自定义 UTI 标识符(标识符字段留空,项目默认),并且该应用程序能够读取并写入这些文件。

最近我更改了一个完全合格的文档类型标识符和编辑器,这似乎是让我的文档图标显示在 Finder 中所必需的。我更改了代码引用文档类型中的所有位置以使用此完全限定的 UTI。现在一切正常,除了打开面板(运行 默认 NSDocumentController openDocument)不再识别我的文件类型 - 所有带有 "mpxml" 后缀的文件现在在打开面板中显示为灰色,包括任何新创建的文件(保存面板可以很好地编写文档)。

我尝试过的一些事情:

值得注意:documentation on CFBundleTypeExtensions(相关的文档类型 plist 键)表示如果设置了 LSItemContentTypes,它会被忽略 - 事实就是如此,因为 LSItemContentTypes 是 UTI 标识符的键。但是,如果设置此设置会破坏文档类型后缀从属关系,我希望 UTI 导出从属关系重新连接它。

另外:打开最近也坏了,在尝试打开最近保存的文档时报告的错误是应用 "cannot open files of this type".

我不确定绕过 NSDocumentController 的解决方法在这里是否有效,因为我不想弄乱它在幕后设置的文档实例/window/文件关联。

使自定义 UTI 和扩展在此应用程序中正常工作缺少什么?


UPDATE 根据这里的请求是与此错误相关的额外 Info.plist 数据(这与上面屏幕截图中的 XCode 文档类型信息基本一致).我现在创建了一个最小的示例应用程序,它重现了我将用于 Apple 错误报告的错误。

在项目的原始形式中,没有声明自定义UTI,Info.plist文档类型声明是:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeExtensions</key>
        <array>
            <string>asdfg</string>
        </array>
        <key>CFBundleTypeIconFile</key>
        <string></string>
        <key>CFBundleTypeName</key>
        <string>example document</string>
        <key>CFBundleTypeOSTypes</key>
        <array>
            <string>CCdc</string>
        </array>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSTypeIsPackage</key>
        <integer>0</integer>
        <key>NSDocumentClass</key>
        <string>$(PRODUCT_MODULE_NAME).CCDocument</string>
    </dict>
</array>

本项目用于成功读写后缀为.asdfg的非空文件

然后我通过为此扩展创建自定义 UTI 来更新 Info.plist。此时Info.plist如下(document-type和UTI唯一变化):

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeExtensions</key>
        <array/>
        <key>CFBundleTypeIconFile</key>
        <string></string>
        <key>CFBundleTypeName</key>
        <string>ccutibug document</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.mathaesthetics.ccutibug</string>
        </array>
        <key>LSTypeIsPackage</key>
        <integer>0</integer>
        <key>NSDocumentClass</key>
        <string>$(PRODUCT_MODULE_NAME).CCDocument</string>
    </dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeDescription</key>
        <string>ccutibug document</string>
        <key>UTTypeIdentifier</key>
        <string>com.mathaesthetics.ccutibug</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>com.apple.ostype</key>
            <array>
                <string>CCdc</string>
            </array>
            <key>public.filename-extension</key>
            <array>
                <string>asdfg</string>
            </array>
        </dict>
    </dict>
</array>

在对最小测试项目进行此更改后,出现与原始文件中描述的相同症状 - 打开面板现在已禁用所有 .asdfg 文档,打开最近不再有效,但我仍然可以创建和保存这些文档。 @catlan 建议的干净重建加上 lsregister 修复仍然没有纠正它。

我再次可以通过直接使用打开面板来确认,单独提供 UTI 并不能使打开面板支持扩展,只有明确提供扩展才能让我打开保存的文档,但无法通过NSDocumentController 对打开的面板或最近打开的菜单 AFAIK 的处理。

您的 UTExportedTypeDeclarations 条目丢失 UTTypeConformsTo。此密钥是必需的。参见 Uniform Type Identifier Concepts - Conformance and Declaring New Uniform Type Identifiers

Although a custom UTI can conform to any UTI, public.data or com.apple.package must be at the root of the conformance hierarchy for all custom UTIs that are file formats (such as documents);

还有:

You need to declare conformance only with your type’s immediate “superclass,” because the conformance hierarchy allows for inheritance between identifiers. That is, if you declare your identifier as conforming to the public.tiff identifier, it automatically conforms to identifiers higher up in the hierarchy, such as public.image and public.data.

System-Declared Uniform Type Identifiers

<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.data</string>
        </array>
        <key>UTTypeDescription</key>
        <string>ccutibug document</string>
        <key>UTTypeIdentifier</key>
        <string>com.mathaesthetics.ccutibug</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <array>
                <string>asdfg</string>
            </array>
        </dict>
    </dict>
</array>

我还删除了 com.apple.ostype,它在经典 Mac OS 中使用,新文件类型不需要。

并匹配 public.filename-extensionCFBundleTypeExtensions:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeExtensions</key>
        <array>
            <string>asdfg</string>
        </array>
        <key>CFBundleTypeIconFile</key>
        <string></string>
        <key>CFBundleTypeName</key>
        <string>ccutibug document</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.mathaesthetics.ccutibug</string>
        </array>
        <key>LSTypeIsPackage</key>
        <false/>
        <key>NSDocumentClass</key>
        <string>$(PRODUCT_MODULE_NAME).CCDocument</string>
    </dict>
</array>

注意:我还将 <integer>0</integer> 更改为 <false/> 以使其更具可读性。

调试

在开发期间更改 UTI 可能会混淆 LaunchServices 数据库。您可以尝试通过 运行:

重置它

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user

注意:确保您的系统上没有任何旧的开发版本,例如 Xcode 构建文件夹或 Xcode 产品档案。这些可能会继续混淆 LaunchServices 数据库

-dump 选项有助于查看当前 UTI 声明:

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump

在验证所有设置似乎正确之后,我能够通过在我的应用程序委托中实现 openDocument() 和 application(openFile...) 来解决 NSDocumentController 问题。

对于打开的面板:

@IBAction func openDocument(_ sender: Any?) {
    let openPanel = NSOpenPanel()
    openPanel.canChooseFiles = true
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = false

    // here we set both custom UTI and suffix explicitly as allowed file types
    openPanel.allowedFileTypes = [MPDocument.UTI, MPDocument.mpxml_suffix]
    openPanel.allowsOtherFileTypes = false

    openPanel.begin(completionHandler: { repsonse in 
        if let docURL = openPanel.url {

            do {
                let mpdoc = try MPDocument(contentsOf: docURL, ofType: MPDocument.UTI)
                NSDocumentController.shared.addDocument(mpdoc)
                mpdoc.makeWindowControllers()
                mpdoc.showWindows()
                if let newDocWC = mpdoc.windowControllers.first {
                    newDocWC.window?.makeKeyAndOrderFront(nil)
                }
            } catch {
                os_log(.error, "Could not load file from URL - error: %s", String(describing:error))
            }
        }
    })
}

Open Recent 仍然会失败(不支持的文档类型),因此此 AppDelegate 覆盖通过将类型强制转换为自定义 UTI 来纠正该问题:

func application(_ sender: NSApplication, openFile filename: String) -> Bool {
    var result = false
    let docURL = URL(fileURLWithPath: filename)
    do {
        // for Open Recent - specify type explicitly rather than relying on suffix
        let mpdoc = try MPDocument(contentsOf: docURL, ofType: MPDocument.UTI)
        NSDocumentController.shared.addDocument(mpdoc)
        mpdoc.makeWindowControllers()
        mpdoc.showWindows()
        if let newDocWC = mpdoc.windowControllers.first {
            newDocWC.window?.makeKeyAndOrderFront(nil)
            result = true
        }
    } catch {
        os_log(.error, "Could not load file from URL - error: %s", String(describing:error))
    }
    return result
}

我在开发第一个基于 SwiftUI App 的应用程序时遇到了同样的问题。 NSDocumentController 打开命令不允许我 select 我的自定义文档类型。 我找到了一个与 Apple 的自定义文档类型文档一致的解决方案。该文档的问题是您经常会发现过时的文档,而且我倾向于混合所有信息。

如前所述,关键是定义正确的“导出类型标识符”。就我而言,我还有一个额外的问题,即我的文档格式是一个包,需要被系统识别为一个包。

决定性的事情是选择正确的“符合”条目(完全正确!)。它需要是系统声明的统一类型标识符之一(请参阅文档 here)。对我来说,这是“com.apple.package”。有趣的是,文件扩展名仅出现在“导出类型标识符”中。完成此操作后,NSDocumentController 打开命令允许我 select 正确的文件(这些目录被正确识别为包)。

这里是完整的条目:

现在是 2022 年,Xcode 12 和 SwiftUI,我已经 运行 进入了同样的事情,就像 OP 我已经尝试了我能想到的一切,然后是一些。

以下是对我有用的: – 在信息设置中,我填写了文档类型和 ExportedType 标识符。

重要: – 我最初将 ImportedType 添加到我的 plist 中,但必须删除它才能正常工作。

– 我声明了一个 UTType:

extension UTType {
    static var myExtension: UTType {
       UTType(exportedAs: "com.MyDomain.myAppName")
    }
}

(如果你在这里使用 importedAs 构造函数,它仍然可以工作,但编译器 - 正确地 - 会抱怨)

并将 static var readableContentTypes: [UTType] { [.myExtension] } 添加到我的文档中。

在 运行 应用程序上读取、写入、恢复 windows:一切都自动运行。