Ad

Our DNA is written in Swift
Jump

Document Menu View Controller not Showing Locations

I’ve been testing and wanting to use PSPDFKit’s PDF Viewer ever since I signed up for the beta. But I had a weird issue opening PDFs from Dropbox or iCloud; I would only see the option to add a new PDF, but no way to import a PDF from the cloud.

Peter Steinberger sent me the code they use to show the UIDocumentMenuViewController as popover. There you can add custom options and you will see document providers which support the mode and file types you specified. I did only ever see the custom option.

I put his code into a sample app and was able to reproduce the problem on my main iPhone 7+. No providers were showing but instead the following was logged to the console:

[Assert] Passed in type com.adobe.pdf doesn’t conform to either public.content or public.item. If you are exporting a new type, please ensure that it conforms to an appropriate parent type.

A long time ago, I wrote about Fun with UTI, explaining that a UTI should always conform to both a type in the physical UTI hierarchy (with public.item as its root) and the functional UTI hierarchy (with public.content being the root). iOS claiming that PDF didn’t conform to either seemed odd to me.

So I printed out the UTI’s declaration with a function provided by MobileCoreServices.

let declaration = UTTypeCopyDeclaration(kUTTypePDF)!
print(declaration)

And got the following …

{
    UTTypeDescription = "my pdf viewer";
    UTTypeIdentifier = "com.adobe.pdf";
    UTTypeTagSpecification =     {
        "public.filename-extension" =         (
            pdf
        );
        "public.mime-type" =         (
            "application/pdf"
        );
    };
}

… but should have gotten this original declaration:

{
    UTTypeConformsTo =     (
        "public.data",
        "public.composite-content"
    );
    UTTypeDescription = "Portable Document Format (PDF)";
    UTTypeIconFiles =     (
        "pdf_20x20.png",
        "pdf_20x20@2x.png",
        "pdf_145x145.png",
        "pdf_145x145@2x.png"
    );
    UTTypeIdentifier = "com.adobe.pdf";
    UTTypeTagSpecification =     {
        "public.filename-extension" =         (
            pdf
        );
        "public.mime-type" =         (
            "application/pdf"
        );
    };
}

The latter declaration is the standard definition by iOS and it did show up on other test devices. PDF does not directly conform to public.content, but does so over an intermediate layer public.composite-content. This is “content with special formatting instructions”… a fancy way to say that it is not just plain text.

The former declaration was only present on my iPhone 7+. It lacked the essential UTTypeConformsTo section; thus it was not conforming to anything. So the above mentioned assert was perfectly correct.

So who’s fault was it?

Probably my own… about 7 years ago I was dabbling with rendering PDFs on iOS. I must have added this flawed declaration and specified the type name as “my pdf viewer”. Or – since I cannot remember doing that – maybe I tried out some sample code that did. For some unknown reason the flawed declaration persisted over 5 or 6 iPhones, because the faulty UTI gets restored with iCloud backup.

There were 3 ways to remedy this situation:

  1. Ignore it, because it was a dumb developer who messed up his iPhone’s UTI registry with some experiments.
  2. At runtime you could check if the UTI in question conforms to item or content. If it doesn’t you could show a warning.
  3. As a workaround you could define your own type that both conforms to PDF and content.

Regarding option 2, the UTTypeConformsTo can answer this question, even over 2 levels:

// check that PDF conforms to composite type
let conforms1 = UTTypeConformsTo(kUTTypePDF, kUTTypeCompositeContent)
print("\(kUTTypePDF) conforms to \(kUTTypeCompositeContent): \(conforms1)")
        
// check that composite type conforms to content
let conforms2 = UTTypeConformsTo(kUTTypeCompositeContent, kUTTypeContent)
print("\(kUTTypeCompositeContent) conforms to \(kUTTypeContent): \(conforms2)")

// check that PDF conforms to content
let conforms3 = UTTypeConformsTo(kUTTypePDF, kUTTypeContent)
print("\(kUTTypePDF) conforms to \(kUTTypeContent): \(conforms3)")

Regarding option 3, the following goes into info.plist. Then you can just specify com.cocoanetics.pdf as type to open.

<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.content</string>
<string>com.adobe.pdf</string>
</array>
<key>UTTypeDescription</key>
<string>Adobe PDF</string>
<key>UTTypeIconFile</key>
<string>FOO</string>
<key>UTTypeIdentifier</key>
<string>com.cocoanetics.pdf</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pdf</string>
</array>
</dict>
</dict> </array>

I did test if a simple reset of the settings would remedy the situation, but it didn’t work. Doing a reset and then restoring an iCloud back also caused the problem to reappear. Only when I wiped the iPhone and set it up as new device I was able to get back the origin UTI declaration for PDF.

Conclusion

Peter Steinberger kept telling me for many months that I was the only one that had this problem. And – of course – he was right. Some experiments of mine, several years ago, apparently destroyed the original UTI definition and only setting up my iPhone as a new device fixed the problem.

I don’t want to know what other crud had accumulated over the years when I always restored my previous iPhone’s backup to my new device. Also there were tons of apps that I didn’t use any more. So, good riddance. What a nice feeling to have a fresh and empty iPhone….

Update: I filed rdar://32446751 for it.


Also published on Medium.


Categories: Bug Reports

Leave a Comment