简体   繁体   English

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

[英]NSDocumentController.openDocument not allowing selection of custom file type

I suspect this is a finicky configuration issue that I'm getting wrong with Document Type / UTI declarations in my Info.plist, but have tried several things and haven't found the solution.我怀疑这是一个挑剔的配置问题,我在 Info.plist 中的文档类型/UTI 声明有误,但尝试了几件事并没有找到解决方案。

I have an NSDocument-based Mac application, written in Swift, Xcode 11. It reads and writes a custom document type with suffix "mpxml".我有一个基于 NSDocument 的 Mac 应用程序,用 Swift、Xcode 11 编写。它读取和写入后缀为“mpxml”的自定义文档类型。

在此处输入图像描述

During most early development I did not have a custom UTI identifier for the type (the identifier field was left blank, the project default), and the app was able to read and write these files.在大多数早期开发过程中,我没有自定义 UTI 类型的标识符(标识符字段留空,项目默认),并且应用程序能够读取和写入这些文件。

Recently I changed to have a fully qualified document type identifier and editor, which seemed to be necessary to get my document icon to show up in the Finder.最近我更改为拥有一个完全限定的文档类型标识符和编辑器,这似乎是让我的文档图标显示在 Finder 中所必需的。 I changed all the places in my code referencing document type to use this fully qualified UTI.我更改了代码引用文档类型中的所有位置以使用这个完全限定的 UTI。 Everything now works except that the open panel (run by the default NSDocumentController openDocument) no longer recognizes my file type - all files with "mpxml" suffix are now grayed out in the open panel, including any files freshly created (the save panel works fine to write the documents).现在一切正常,除了打开面板(由默认的 NSDocumentController openDocument 运行)不再识别我的文件类型 - 所有带有“mpxml”后缀的文件现在在打开面板中显示为灰色,包括任何新创建的文件(保存面板工作正常写文件)。

Some things I've tried:我尝试过的一些事情:

  • adding some additional overrides to my NSDocument subclass: fileNameExtension(), writableTypes, etc.向我的 NSDocument 子类添加一些额外的覆盖:fileNameExtension()、writableTypes 等。
  • setting / omitting mime-type设置/省略 mime-type
  • setting / omitting a 4-char OSType设置/省略 4 字符 OSType
  • setting / omitting a reference URL设置/省略参考 URL
  • removing mpxml extension from document type (so it's only defined in the UTI) - didn't work从文档类型中删除 mpxml 扩展名(所以它只在 UTI 中定义) - 不起作用
  • declaring the type as an Imported UTI as well (should not be needed, didn't fix the issue)也将类型声明为导入的 UTI(应该不需要,没有解决问题)
  • reviewing docs: Developing a Document-Based App , Declaring New Uniform Type Identifiers审查文档: 开发基于文档的应用程序声明新的统一类型标识符

Worth noting: the documentation on CFBundleTypeExtensions (the relevant document-type plist key) says that it's ignored if LSItemContentTypes is set - which is the case, since LSItemContentTypes is the key for the UTI identifier.值得注意的是: 关于 CFBundleTypeExtensions 的文档(相关的文档类型 plist 键)说,如果设置了 LSItemContentTypes,它会被忽略 - 就是这种情况,因为 LSItemContentTypes 是 UTI 标识符的键。 But if setting this breaks the document-type suffix affiliation, I'd expect the UTI export affiliation to re-connnect it.但是,如果设置这会破坏文档类型后缀从属关系,我希望 UTI 导出从属关系能够重新连接它。

Also: Open Recent is also broken, and on attempting to open a recently-saved document the error reported is that the app "cannot open files of this type".另外:Open Recent 也坏了,在尝试打开最近保存的文档时,报告的错误是应用程序“无法打开这种类型的文件”。

I'm not sure a workaround bypassing NSDocumentController will work here, because I don't want to mess with the document instance / window / file associations it sets up behind the scenes.我不确定绕过 NSDocumentController 的解决方法在这里是否有效,因为我不想弄乱它在幕后设置的文档实例/window/文件关联。

What is missing to make the custom UTI and extension work correctly in this app?使自定义 UTI 和扩展在此应用程序中正常工作缺少什么?


UPDATE based on request here is additional Info.plist data relevant to this bug (which essentially agrees with the XCode document type information in the screenshot above).根据此处的请求更新是与此错误相关的附加 Info.plist 数据(基本上与上面屏幕截图中的 XCode 文档类型信息一致)。 I've now created a minimal sample application which reproduces the bug that I'll be using for an Apple bug report.我现在创建了一个最小的示例应用程序,它重现了我将用于 Apple 错误报告的错误。

In the original form of the project, which no custom UTI declared, the Info.plist document type declaration is:在项目的原始形式中,没有自定义 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>

This project is used to successfully read and write non-empty documents with the suffix.asdfg.本项目用于成功读写后缀为.asdfg的非空文档。

I then update the Info.plist by creating a custom UTI for this extension.然后,我通过为此扩展创建自定义 UTI 来更新 Info.plist。 At this point the Info.plist is as follows (document-type and UTI the only changes):此时 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>

The same symptoms described in the original occur after this change to the minimal test project - Open Panel now has all.asdfg documents disabled, Open Recent no longer works, but I can still create and save these documents.在对最小测试项目进行此更改后,会出现原始描述的相同症状 - 打开面板现在禁用了 all.asdfg 文档,打开最近不再有效,但我仍然可以创建并保存这些文档。 The clean rebuild plus lsregister fix suggested by @catlan still does not correct it. @catlan 建议的干净重建加上 lsregister 修复仍然不能纠正它。

Again I can confirm by directly using an open panel that supplying the UTI alone does not enable open panel to honor the extension, only explicitly supplying the extension lets me open the saved documents, and there's no way to do that through NSDocumentController's handling of the open panel or open-recent menu AFAIK.再次,我可以通过直接使用打开面板来确认单独提供 UTI 并不能启用打开面板来支持扩展,只有显式提供扩展才能让我打开保存的文档,并且无法通过 NSDocumentController 处理打开来做到这一点面板或打开最近的菜单 AFAIK。

Your UTExportedTypeDeclarations entry is missing UTTypeConformsTo .您的UTExportedTypeDeclarations条目缺少UTTypeConformsTo This key is required.此密钥是必需的。 See 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);尽管自定义 UTI 可以符合任何 UTI,但 public.datacom.apple.package必须位于所有作为文件格式(例如文档)的自定义 UTI 的一致性层次结构的根;

Also:还:

You need to declare conformance only with your type's immediate “superclass,” because the conformance hierarchy allows for inheritance between identifiers.您只需要声明与您的类型的直接“超类”的一致性,因为一致性层次结构允许标识符之间存在 inheritance。 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 .也就是说,如果您声明您的标识符符合public.tiff标识符,它会自动符合层次结构中更高的标识符,例如public.imagepublic.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>

I also removed com.apple.ostype , which was used in classic Mac OS and is not required for new file types.我还删除了 com.apple.ostype ,它在经典 Mac OS 中使用,新文件类型不需要。

And match public.filename-extension and CFBundleTypeExtensions :并匹配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>

Note: I also changed <integer>0</integer> to <false/> to make it more readable.注意:我还将<integer>0</integer>更改为<false/>以使其更具可读性。

Debugging调试

During development changing the UTI can confuse the LaunchServices database.在开发期间更改 UTI 可能会混淆LaunchServices数据库。 You can try to reset it by running:您可以尝试通过运行来重置它:

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

Note : Make sure that you don't have any old dev build on your system, like in the Xcode Build Folder or Xcode Product Archives.注意:确保您的系统上没有任何旧的开发版本,例如 Xcode 构建文件夹或 Xcode 产品档案。 These could continue to confuse the LaunchServices database这些可能会继续混淆LaunchServices数据库

The -dump option is helpful to see current UTI declarations: -dump选项有助于查看当前的 UTI 声明:

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

I had the same problem developing my first SwiftUI App based application.我在开发第一个基于 SwiftUI App 的应用程序时遇到了同样的问题。 The NSDocumentController open command would not allow me to select my custom document type. NSDocumentController 打开命令不允许我 select 我的自定义文档类型。 I found a solution which is in agreement with Apples documentation on custom document types.我找到了一个与 Apples 关于自定义文档类型的文档一致的解决方案。 The problem with this documentation is that you find often outdated documentation as well and I tended to mix all the information.这个文档的问题是你经常会发现过时的文档,我倾向于混合所有信息。

The key is - as mentioned - to define a correct "Exported Type Identifier".如前所述,关键是定义正确的“导出类型标识符”。 In my case I had the additional problem that my document format is a package which needs to be recognised by the system as a package.在我的情况下,我遇到了另一个问题,即我的文档格式是 package,需要系统将其识别为 package。

The decisive thing is to choose the correct "Conforms To" entry (precisely correct.).决定性的事情是选择正确的“符合”条目(完全正确。)。 It needs to be one of the system declared uniform type identifiers (see documentation here ).它必须是系统声明的统一类型标识符之一(请参阅此处的文档)。 For me this was "com.apple.package".对我来说,这是“com.apple.package”。 Interesting is that the file extension is only present in the "Exported Type Identifier".有趣的是,文件扩展名仅存在于“导出的类型标识符”中。 After I had done this the NSDocumentController open command allowed me to select the correct files (which were directories correctly recognised as packages).完成此操作后,NSDocumentController 打开命令允许我 select 正确的文件(这些目录被正确识别为包)。

Here the complete entry:这里是完整的条目: 声明自定义文档类型和关联的导出类型标识符

After verifying that all the settings seem correct, I was able to workaround the NSDocumentController issues by just implementing openDocument() and application(openFile...) in my app delegate.在验证所有设置似乎都正确之后,我能够通过在我的应用程序委托中实现 openDocument() 和 application(openFile...) 来解决 NSDocumentController 问题。

For the open panel:对于打开的面板:

@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 would still fail (unsupported document type), so this AppDelegate override corrects that by coercing type to the custom UTI:打开最近仍然会失败(不支持的文档类型),因此此 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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM