File icons getting changed to App icon in macOS Catalina - objective-c

I have a Mac app that opens office files(.doc, .xls, etc.) and I need to show custom icon for these supported files.
I used to achieve by adding the supported UTI types in CFBundleDocumentTypes and assign my custom icns icon. Also set my app as the default app which opens these file types.
Now this approach worked flawlessly until macOS Catalina beta, even with Microsoft apps being present along with my app. In macOS Catalina beta onwards, I am seeing my app icon in place of all the file icons.
I tried clearing icon cache and even relaunching Finder, but to no avail.
Later on I even tried by adding the UTI types under UTExportedTypeDeclarations and UTImportedTypeDeclarations.
Is this a bug with Catalina Beta? Or anything I can do to get this working.
My plist has UTExportedTypeDeclarations looking like this. UTImportedTypeDeclarations and CFBundleDocumentTypes have similar values.
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>org.openxmlformats.spreadsheetml.sheet</string>
<string>org.openxmlformats.openxml</string>
<string>public.composite-content</string>
</array>
<key>UTTypeDescription</key>
<string>Excel Open XML spreadsheet</string>
<key>UTTypeIconFile</key>
<string>custom.icns</string>
<key>UTTypeIdentifier</key>
<string>com.microsoft.excel.openxmlformats.spreadsheetml.sheet</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>com.apple.ostype</key>
<string>XLSX</string>
<key>public.filename-extension</key>
<array>
<string>xlsx</string>
</array>
</dict>
</dict>
</array>

It looks like macOS 10.15 changed the way how the corresponding icon for a type is resolved.
I found a way how to get proper document icons for my app on Catalina:
Previously I only had an icon defined for the CFBundleTypeIconFile key in my CFBundleDocumentTypes dictionaries. But since Catalina, the system uses the LSItemContentTypes array to find an exported UTI and then uses the icon of that UTI (defined via the UTTypeIconFile).
If the LSItemContentTypes array contains types that are not known to the system yet (= newly introduced custom types), those types must also be exported by defining dictionaries in UTExportedTypeDeclarations.
This works for my case because I use a custom file format with its own UTI.
For the case you are describing, where existing UTIs are used, I am not sure if it is still possible to override the icons of UTIs that you don't "own".
I think the correct way to define custom icons for 3rd party types would be to define dictionaries in the imported types array (UTImportedTypeDeclarations). In that case the system should choose your custom icon as long as no other app declares ownership of an UTI by exporting it. I suppose that the icon of the app that is set as Default application for a type wins in the case where 2 or more apps claim ownership for it (Haven't tried that).
Another issue I ran into was, that the icon file assigned to UTTypeIconFile can't come from an Asset Catalog (this worked for CFBundleTypeIconFile). It needs to refer to an .icns file in the Resource folder in your bundle.
Existing .iconset folders from the Asset Catalog can be converted to icns with the following command:
iconutil --convert icns iconname.iconset
I am not sure if this intended behavior or if this is just a bug in the Asset Catalog compiler build phase of Xcode 11. I filed a bug via Feedback Assistant.

I also have this issue. Tried empty project and reproduced it with bundled macOS applications, so it's mostly safe to assume this is a bug in macOS Catalina.
As trojanfoe suggested in a comment the only thing to do right now is reporting it to Apple via the Feedback Assistant.
Thomas Zoechling refers to another issue that may affect earlier macOS versions too, but does not solve the main issue here.

Related

Default container on iCloud not working for key-value storage

Somewhere in the change from Xcode 5 to Xcode 6, and the changes online to the iCloud management system, I lost the ability to store data into key-value storage in iCloud. More specifically, now, both:
[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
and
[NSFileManager defaultManager].ubiquityIdentityToken
now both return nil. Previously (at least) URLForUbiquityContainerIdentifier returned non-nil.
I tried the answers given here: Why ubiquityIdentityToken returns nil? and here https://devforums.apple.com/thread/229509 but no joy.
I have found a fix to this. I first tried a sample project with Xcode 6, targeting iOS6 (I need to maintain iOS6 compatibility). I had these settings under Capabilities:
It seems like the default container setting is not working with just key-value storage. That is, with settings as above, ubiquityIdentityToken returns nil.
I next tried checking the CloudKit box, and then specifying a custom container. That might have worked except for my iOS6 compatibility issue. CloudKit doesn't work on iOS6.
I then tried checking the iCloud Documents box (hacking my way to greatness? ;)):
This causes ubiquityIdentityToken to return non-nil! And when I make this change in my actual application, as opposed to this test application, my key-value storage works again! (It does also work on iOS8.1).
The relevant parts of my entitlement file are now:
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>

application:handleOpenUrl: is appending the wrong extension to the file

When I attempt to export a file to my app, from within Mail, it is sending the file to export with the wrong extension.
Here's an example:
I e-mail an attachment to myself
Open the attachment in Mail
Export the file and open with my app
The url passed to application:handleOpenUrl: is wrong
The name of the file I am attempting to export is named 'NSIS.nsi' (without quotes). Mail (iOS?) renames the file to 'NSIS.4th' when it passes it back to my app via application:handleOpenUrl. One thing I did notice is that the first extension (item) within the Exported Type UTIs > Item 0 > Conforms to UTIs > Equivalent Types > public.filename-extension is '4th' (again, without quotes).
Another thing to note is that I do have the 'nsi' extension in the same public.filename-extension array.
And yet another thing to note is that this appears to happen only with the 'nsi' extension. I haven't tested all of the extensions my app supports (there's 92 of them. I got tired after about the 40th one). But this extension in particular is having issues.
By the way here is the code sample... it really is this simple. The NSURL passed contains the wrong extension. This will log the url passed -- which is the wrong one. So, I can say for certain that there is nothing in my app that is doing something with the NSURL before it gets passed to application:handleOpenUrl:.
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
NSLog(#"url: %#", url);
...
}
I also don't know if this helps, but in the Mail app it does show my app's icon next to the NSIS.nsi file. And yes, the file's name displays as 'NSIS.nsi' within Mail.
Update 2
I was able to confirm that changing the first extension to '5th' (for kicks) and then imported the 'NSIS.nsi' file it indeed changed the file extension to 'NSIS.5th'. I think what's happening is I'm defining the UTIs that my app supports incorrectly. All I want to do is say, "Hey, iOS, I support all of these file extensions. Don't change the name. Just give me the file and let me do what I need to do with it." In reality I just want my app to support all types of text files (text, utf8, utf16, etc). That's the end result.
The problem is, when I tell iOS that I support plain.text, utf8 and utf16 it still won't display my app in the list of apps that can open a file unless I specifically set the extension in the public.filename-extension array. I wish there was just an all encompassing "I support all text files" configuration value in the plist.
One other thing I noticed is that the issue does affect other file extensions. For example, I have a file that I am exporting from Mail to my app called 'Ada.adb'. iOS is returning the file as 'Ada.ada' (notice the 'a' at the end instead of a 'b'). This makes sense... I don't even have 'ada' in my list of file extensions within the public.filename-extension array or anywhere in my app for that matter.
Update 3
I figured out how to support all types of public.text files.
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array>
<string>icon_buffer_ipad_small.png</string>
<string>icon_buffer_iphone_small.png</string>
</array>
<key>LSItemContentTypes</key>
<array>
<string>public.text</string>
</array>
<key>NSExportableTypes</key>
<array>
<string>public.text</string>
</array>
<key>CFBundleTypeName</key>
<string>Source File</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
Essentially you need to include the NSExportableTypes in addition to the LSItemContentTypes. I'll add an update when I figure out how to make other "public.text" file types such as 4th, md, and the huge list of other file types that are public.text, associated to your app. Hopefully this won't be necessary. Regardless, I'll update with my findings.
Update 4
You guys aren't going to believe this. This issue affects every app I have tested this with. Essentially, when I attempt to export the 'Ada.adb' file it exports the file as 'Ada.ada' to every application. Wow! I'm going to figure out how to file this bug with Apple. Unless, of course, this is expected behavior!
Small update: I filed the bug to Apple. Bug ID# 12802236
I do not recall this occurred in iOS 5. I believe this is a regression introduced in iOS 6.
If the file name doesn't change, how about change its extension manually after - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url receive the url? Of course, you have to know what the right extension is.

Creating a QuickLook plug-in for text files in Xcode 4.1

Background
I'm learning Objective-C and Cocoa, and I thought creating simple programs to answer my needs would be a nice thing. I already have a solid .NET C# background, some Python knowledge, and a little of C.
One of my "simple first app" I tried to do is a simple QuickLook plug-in for Arduino's sketch files. I thought it would be a simple task to accomplish since these files are plain text C-like scripts, the only "different" thing is they have a .pde extension.
I uploaded the project on GitHub at ArduinoQuickLook as a reference (the first commit contains a vanilla Xcode 4.1 QuickLook plugin-project).
What I found
Looking around the net I found these resources:
QLStephen
QLColorCode
Introduction to Quick Look Programming Guide
What are my problems
Both of them use GeneratePreviewForURL.m and GenerateThumbnailForURL.m files, but when I created the project in Xcode 4.1 it created GeneratePreviewForURL.c and GenerateThumbnailForURL.c (note .c instead of .m).
Both QLStephen and QLColorCode use #import <Foundation/Foundation.h> in their GeneratePreviewForURL.m and GenerateThumbnailForURL.m files, but if I try to #import it it yields to many errors. (Maybe because my files are .c instead of .m?)
It's not clear to me how I declare which files my plug-in will handle, I understood I need to change ArduinoQuickLook/ArduinoQuickLook-Info.plist (row 14) but what I have to write there? Something like cc.arduino.pde?
This tutorial on creating a Quicklook plugin explains things nicely, but to summarise:
Point 1 and 2 are related - for some strange reason the Quicklook plugin template only contains .c files - as such, importing the Obj-C Foundation.h header causes errors.
You should just be able to rename the files from .c to .m and it will work as expected.
It's not clear to me how I declare which files my plug-in will handle
You need to do two things - one is say which UTI (Uniform Type Identifier) your plugin handles (e.g cc.arduino.pde), by changing the line you mention:
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>QLGenerator</string>
<key>LSItemContentTypes</key>
<array>
<string>cc.arduino.pde</string>
</array>
</dict>
</array>
...but you also have to describe that UTI (mostly so it can map the file-extension to that UTI)
There are two slightly different ways to declare UTI's, [as "Declaring New Uniform Type Identifiers"] describes:
Your UTI declarations must be either imported or exported:
An exported UTI declaration means that the type is available for use by all other parties. For example, an application that uses a proprietary document format should declare it as an exported UTI.
An imported UTI declaration is used to declare a type that the bundle does not own, but would like to see available on the system. For example, say a video-editing program creates files using a proprietary format whose UTI is declared in its application bundle. If you are writing an application or plugin that can read such files, you must make sure that the system knows about the proprietary UTI, even if the actual video-editing application is not available. To do so, your application should redeclare the UTI in its own bundle but mark it as an imported declaration.
For a Quicklook plugin, you probably want an "imported" UTI declaration, in which you would add something like this to your Info.plist:
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>cc.arduino.pde</string>
<key>UTTypeReferenceURL</key>
<string>http://www.example.com</string>
<key>UTTypeDescription</key>
<string>Arduino PDE file</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.c-source</string>
<string>public.text</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pde</string>
</array>
</dict>
</dict>
</array>
Here is the source for a project that provides QuickLook for Processing and Arduino .pde files: https://github.com/kroko/ProcessingQL I know this question is a year old, but this project might be helpful for anyone else attempting this.

Writing a Snow Leopard Service for Finder.app

I am currently looking into solving the problem with the inability to quickly create new files in the Finder. I will open source what I write because I think the Mac community needs this solved.
On Windows, you can right-click, create new text file.
OS X, you should be able to do this with a service which would work like this:
Right-Click > Services > Create New Text File
Writing a Finder Service in Snow Leopard is theoretically the way to accomplish this, although I haven't been able to find any sample code. (I'll admit I've only looked at the documentation very briefly).
I'm not sure how to get started, whether Apple provide a Services template in Xcode. Basically I'm looking for help with getting a working services project running. The implementation code should then be rather trivial for me to write in Obj-C. So what can I do to create a new working Services project? If i'm wrong about this then tell me the right way to do this and please provide sample code or some steps to get me started.
Edit: Trust me guys, I'm not an OS X noob. Have tried many apps to achieve work arounds: PathFinder, Automator, Terminal,etc and I'm happy with none of them.
I want to create a right-clickable menu item for creating New files, the same as Windows has. If this API doesn't let me do this, then I will modify system files if necessary. But I would rather do it in such a way that doesn't require me hack OS X.
The sad fact is that Apple disabled 3rd party contextual menu items when Snow Leopard was released and devs weren't happy. You could create services under the contextual menu with Automator, but it was very limited.
Yes, Quicksilver is the way I create files at the moment, unless I'm in the terminal when I touch ~/Desktop/file.txt or wherever.
If you cannot answer my question by providing source code for an Xcode project for writing a Service, please keep your opinions on how I should be using my computer to yourself. At any rate, I think I will probably be answering my own question after I go and implement this myself.
Read Services Implementation Guide. If you want a working sample code, see this project I cooked up. As explained in the guide, what you need to do is to install the service handler:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setServicesProvider:self];
NSUpdateDynamicServices();
}
- (void)handleServices:(NSPasteboard *)pboard
userData:(NSString *)userData
error:(NSString **)error {
if([[pboard types] containsObject:NSFilenamesPboardType]){
NSArray* fileArray=[pboard propertyListForType:NSFilenamesPboardType];
// do something...
}
}
and advertise it in your Info.plist:
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Service Handling Demo</string>
</dict>
<key>NSMessage</key>
<string>handleServices</string> <!-- This specifies the selector -->
<key>NSPortName</key>
<string>services</string> <!-- This is the name of the app -->
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
</array>
</dict>
</array>
It's as easy as this! You might need to manually turn on the service entry in the Keyboard Shortcut section of the System Preferences.app. If you want to turn it on automatically, you can write into pbs.plist inside Library/Preferences/, but that's not a documented way of doing things.
The problem is that Finder doesn't show this service item when you right-click an empty region in the Finder window. You can't do anything with it, other than injecting the code. This is inherent in the Finder's support of the service system. If you want to change that behavior, you need to inject code inside Finder.app. That's not that hard. On Snow Leopard, it's standard to use the OSAX loading trick, described at e.g. in this blog post. Then you can use Objective-C runtime functions to patch the behavior of the right-clicking of the Finder, by changing the methods described in this Apple document. (I don't know which of the method Finder uses to respond to the right-click event, though.)
Instead, if you're OK with clicking a button on the toolbar of the Finder window instead of right-clicking, you can add a button, as in this utility cd-to. This uses the ability to put an icon of the app to the Finder toolbar. The app itself just reads the path of the frontmost Finder window via Apple events, and opens a Terminal window for that. I think you can tweak the code of this app to do what you want.
Here follow subjective stuffs:
Honestly, you don't have to use Objective-C to write a Finder service if you just want to make a new file. Automator can do the same thing with a shell script and/or Applescript.
If you want to manage files efficiently on Mac, there are already great utilities around: try Butler or launchbar or Quicksilver, for example.

How do I launch a non-document-based Cocoa application by dropping files on it?

The only way I've been able to get this working is with a document-based application (which this app isn't). In my non-document-based application I've defined the supported Document Types (from the Properties tab of the Target info window) and my AppDelegate implements application:openFile: and application:openFiles:. That enables dropping files on the application's Dock icon when it has already been launched but not its icon in the Finder (launched or otherwise). What am I missing?
Updated
As requested, my Document Types array:
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nsf</string>
<string>nsfe</string>
</array>
<key>CFBundleTypeName</key>
<string>NSF Soundtrack</string>
<key>CFBundleTypeRole</key>
<string>None</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSPersistentStoreTypeKey</key>
<string>InMemory</string>
</dict>
</array>
Two parts of this make me suspicious:
<key>CFBundleTypeRole</key>
<string>None</string>
Don't you mean this to be Viewer at least?
<key>NSPersistentStoreTypeKey</key>
<string>InMemory</string>
What are you trying to do here? Why would your on-disk file be specified as an in-memory Core Data persistent store? If you want to just hoist the whole thing into memory when you load it, that's called Binary or XML, not InMemory.
I also recommend that you define UTIs for your document types, not just extensions.
You should also double-check what Launch Services thinks is going on by using lsregister. Sometimes the problem is that you have multiple versions of your app lying around and Launch Services hasn't found the one you think it should have. You can look the Launch Services database like this:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -dump
This should work on 10.5 and 10.6. 10.4 has lsregister in a different place. I usually use locate to find it rather than trying to memorize it.
In your Target settings, go to the Properties tab and add to the Document Types table.