Skip to content

Conversation

@JoshBashed
Copy link
Contributor

Closes #118

Copilot generated summary:

This pull request adds support for using .icon files (layered icon format) as app icons on Apple platforms, in addition to the existing support for .png and .icns files. It introduces a new LayeredIconCreator utility for converting .icon files to .icns, updates the resource bundling logic to handle these new icon types, and improves the documentation to reflect the expanded icon format support.

Support for new icon formats:

  • Added support for using .icon files as app icons on Apple platforms, alongside .png and .icns files, by updating the compileAppIcon method in DarwinBundler to handle .icon files and call the new LayeredIconCreator utility. [1] [2] [3]
  • Introduced the LayeredIconCreator utility for converting .icon files to .icns using actool, along with a dedicated error type for handling related errors. [1] [2]

Resource bundling and asset catalog improvements:

  • Updated ResourceBundler to compile asset catalogs and .icon files together when present, refactored parameters to use the full BundlerContext, and improved handling of temporary files and resource copying. [1] [2] [3] [4] [5] [6]
  • Added logic to determine the correct target device names for actool based on the Apple platform, ensuring proper icon generation for all supported targets.

Documentation updates:

  • Updated the documentation to describe the new supported icon formats and provide guidance on when to use each format. [1] [2]

Copy link
Owner

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! And thanks for documenting all your changes so nicely.

Most of my comments are just requesting small style changes or documentation wording/formatting tweaks.

My main question is regarding your changes to fixAndCopyResourceBundle. To summarise the relevant comment: why does using a layered icon (as opposed to an icns icon) require this additional resource bundle fixing logic? If it's necessary for the layered icon to work at all, then I think this should be handled differently; I don't like that layered icons are getting double handled (once in DarwinBundler to compile them there, and once here to compile them into every resource bundle with an asset catalog).

@stackotter
Copy link
Owner

I'm happy with everything other than that addition ResourceBundler asset catalog logic now.

I looked into it, and it seems like actool doesn't support taking files other than asset catalogs as inputs in compilation mode, so it doesn't seem like your approach to getting the layered icon into the Assets.car would work. Have you confirmed that it does in practice?

Also, if the app doesn't have any resources, then there won't be a resource bundle to fix, and the icon won't end up in the app's Assets.car (it won't have one).

And the icon will be inserted into every single resource bundle's Assets.car as far as I can tell, whereas I believe that you only want it in the main Assets.car file.

As an aside, what additional functionality does putting the layered icon in the Assets.car do? I assume it's to allow iOS/macOS to relight the icon and add depth effects? But I dunno exactly where that would happen


I think that ResourceBundler should insert the icon into the xcassets directory before compilation, and it should limit itself to only doing that when isMainBundle is true. Additionally, DarwinBundler should check for the presence of a main Assets.car file, and if it doesn't exist then it should synthesise an xcassets catalog with the layered icon and then compile it into the correct place.

Also, it feels weird having layered icon specific logic in ResourceBundler. But inserting synthesised assets such as layered icons or accent colors into the main resource bundle's Assets.car feels like a generic operation, so maybe the API surface can take an array of 'synthesised assets' instead of an optional layered icon url. It would then insert those synthesised assets into the main bundle's xcassets catalog before compilation. And then if there isn't a main bundle it should synthesise one with those assets.

Note that when inserting assets into the main bundle's xcassets, you may need to make a temporary copy as to not confuse swiftpm's build caching.

@JoshBashed
Copy link
Contributor Author

Thanks for the detailed review. I want to add more context about what I tested, because several of the concerns raised do not match the behavior I am seeing in practice.

  1. Regarding actool behavior
    In compilation mode, actool does require --output-partial-info-plist /dev/null. Without it, the tool exits with an error and the build fails. With the flag, it completes successfully. This is reproducible and required for the layered icon case.

  2. On whether .icon files can be compiled as bundle resources
    Layered .icon files are treated as compilable resources. They are not raw files that just sit in the bundle. They are correctly picked up by actool when included through the bundle. This is what enables the system to apply depth, tinting, and other appearance effects.

  3. Why the icon must live in the main Assets.car

    • If the ICNS is removed but the catalog remains, the system still resolves the icon correctly.
    • If the catalog is removed, dark mode, tinting, and glass effects break.
      This demonstrates that the catalog entry is authoritative and not optional.
  4. Case where the app has no resources
    The code path synthesizes an asset catalog when one does not exist. That means there will be a catalog produced in that situation, and the icon still ends up compiled correctly. So the icon does not get lost just because the app originally had no resources.

  5. About inserting into every bundle
    The logic scopes insertion based on isMainBundle, so it does not intentionally push the icon into every resource bundle. The relevant section is:

    iconURL: URL?,
    isMainBundle: bundleName == "\(packageName)_\(productName)"

    The effect is that only the main bundle receives the icon.

  6. iOS vs macOS
    At the moment this works reliably on macOS. On iOS, actool currently outputs PNGs in this scenario, so there is follow-up work needed. I plan to address that next.

I agree that thinking about a more general mechanism for synthesized assets could be interesting as a longer-term improvement. But this PR is focused on solving a concrete breakage that has been blocking layered icon support.

Given the above, my view is that:

  • this approach works,
  • it is required for correct behavior today, and
  • broader restructuring can be discussed as a separate change if we decide it is worthwhile.

If there is a specific technical case I have not covered, I am happy to test it. Otherwise, I would prefer to land this fix so that layered icon support is unblocked.

@stackotter
Copy link
Owner

  1. Case where the app has no resources
    The code path synthesizes an asset catalog when one does not exist. That means there will be a catalog produced in that situation, and the icon still ends up compiled correctly. So the icon does not get lost just because the app originally had no resources.

I know that it's synthesising an asset catalog when one doesn't exist, but it only does that if there is a main resource bundle in the first place. If the app doesn't have any resources, then I'm pretty sure (from some quick testing on my laptop) that SwiftPM doesn't produce a resource bundle for it, so this resource bundle fixing code doesn't get run.

  1. About inserting into every bundle
    The logic scopes insertion based on isMainBundle, so it does not intentionally push the icon into every resource bundle. The relevant section is:
    iconURL: URL?,
    isMainBundle: bundleName == "\(packageName)_\(productName)"
    The effect is that only the main bundle receives the icon.

I don't believe that's so. I know that the isMainBundle parameter exists, but it looks like your code runs regardless of whether it's true or not, because it's outside of the if/else that checks isMainBundle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Apple .icon (Icon Composer) layered icon format

2 participants