Skip to content

Add support for Composer Icons#24722

Open
rolfbjarne wants to merge 10 commits intomainfrom
dev/rolf/support-composer-icons
Open

Add support for Composer Icons#24722
rolfbjarne wants to merge 10 commits intomainfrom
dev/rolf/support-composer-icons

Conversation

@rolfbjarne
Copy link
Member

@rolfbjarne rolfbjarne commented Feb 17, 2026

Add support for Icon Composer (.icon) app icons

This adds support for Xcode Icon Composer based .icon folders introduced in macOS 26 Tahoe's Liquid Glass design system (see #24132).

Changes

  • Recognize .icon folders: Treats .icon directories as asset catalogs alongside .xcassets
  • "Process"/Detect icon.json files: Handles icon.json metadata files in addition to Contents.json
  • Enable --app-icon flag: .icon-based icons now pass validation and get the --app-icon flag passed to actool
  • Support alternate icons: Works with IncludeAllAppIcons for runtime icon switching

Background

The new .icon format is a folder structure containing:

  • icon.json - Icon metadata (layers, materials, effects)
  • Assets/ subfolder - Vector graphics and image assets

This replaces static .icns files to support Liquid Glass features like translucency, specular lighting, and cross-platform rendering.

Usage

Add .icon folders to your project:

<ItemGroup>
  <ImageAsset Include="Resources\AppIcon.icon\**" />
</ItemGroup>

<PropertyGroup>
  <!-- Optional: Specify which icon to use -->
  <AppIcon>AppIcon</AppIcon>
  
  <!-- Optional: Include all icons for runtime switching -->
  <IncludeAllAppIcons>true</IncludeAllAppIcons>
</PropertyGroup>

The build system will:

  1. Recognize the .icon folder as a valid app icon
  2. Pass it to actool with the --app-icon AppIcon flag
  3. Compile it into Assets.car

Testing

I could not figure out based on the docs how to get it installed locally to test it with my MAUI app. Advices would be appreciated.


This is a recreation of #24476 (from @paulober), because our CI can't work with pull requests from forks.

paulober and others added 9 commits December 23, 2025 21:19
Signed-off-by: paulober <44974737+paulober@users.noreply.github.com>
- Add .icon glob patterns to ImageAsset auto-include and BundleResource
  exclusion in Microsoft.Sdk.DefaultItems.template.props
- Fix tvOS support: register .icon assets in both brandAssetsInAssets and
  imageStacksInAssets for primary/alternate icon validation
- Add tvOS test case to IconFileSupport
- Add tests: IconFileSupportWithIncludeAllAppIcons,
  IconFileSupportAsAlternateIcon, InexistentIconFile,
  MixedXCAssetsAndIconFile
- Update DefaultCompilationIncludes.md and build-items.md documentation

Fixes #24132
Add a test project that uses .icon (Xcode Icon Composer) directories
instead of .xcassets for app icons. The test builds the project for
all platforms and verifies that raw .icon files don't end up in the
app bundle as BundleResources (they should be processed by actool).
Add a basic .xcassets directory alongside .icon in the test project
so the build can succeed even when actool's icon export subprocess
fails (status 255 on some systems). The test gracefully skips with
Assert.Ignore when icon export is not supported.
The icon.json format used 'version'+'layers'+'filename' but the correct
Icon Composer format uses 'groups' containing 'layers' with 'image-name'.
The old format caused actool's icon export subprocess to fail with
status 255.

With the correct format, actool successfully compiles .icon files into
app icons on all platforms.

Also removed the Images.xcassets fallback from the test project (no
longer needed) and updated the integration test to assert success
instead of skipping.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds build- and test-level support for Xcode Icon Composer (.icon folder) app icons so they’re treated like asset catalogs and can be validated/passed through to actool as the selected AppIcon (including alternate icons).

Changes:

  • Update ACTool to treat .icon folders as asset catalogs and recognize icon.json alongside Contents.json.
  • Extend default MSBuild item globs/docs to include .icon directories as ImageAsset (and exclude them from BundleResource).
  • Add MSBuild task tests and a new dotnet test app/project to exercise .icon handling.

Reviewed changes

Copilot reviewed 21 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs Recognizes .icon directories and icon.json so .icon-based AppIcon/alternate icons can pass validation and be compiled via actool.
dotnet/targets/Microsoft.Sdk.DefaultItems.template.props Includes .icon/** in default ImageAsset globs and excludes .icon content from default BundleResource inclusion.
dotnet/DefaultCompilationIncludes.md Documents default inclusion behavior for .icon (Icon Composer) directories.
docs/building-apps/build-items.md Documents that ImageAsset includes both .xcassets and .icon inputs.
tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs Adds task-level tests covering .icon validation scenarios (main icon, include-all, alternate icon, missing icon, mixed catalogs).
tests/dotnet/UnitTests/AppIconTest.cs Adds a unit test project build validating .icon inputs are not copied as raw bundle resources.
tests/dotnet/AppWithComposerIcon/shared.mk Shared make configuration for the new composer-icon test app.
tests/dotnet/AppWithComposerIcon/shared.csproj Shared project settings enabling AppIcon=AppIcon for the new composer-icon test app.
tests/dotnet/AppWithComposerIcon/AppDelegate.cs Minimal app entry point for the composer-icon test app.
tests/dotnet/AppWithComposerIcon/iOS/Makefile Platform makefile for iOS composer-icon test app.
tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj iOS target project for composer-icon test app.
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json iOS Icon Composer metadata fixture.
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/front.png iOS Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/back.png iOS Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/tvOS/Makefile Platform makefile for tvOS composer-icon test app.
tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj tvOS target project for composer-icon test app.
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json tvOS Icon Composer metadata fixture.
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/front.png tvOS Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/back.png tvOS Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile Platform makefile for Mac Catalyst composer-icon test app.
tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj Mac Catalyst target project for composer-icon test app.
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json Mac Catalyst Icon Composer metadata fixture.
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/front.png Mac Catalyst Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/back.png Mac Catalyst Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/macOS/Makefile Platform makefile for macOS composer-icon test app.
tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj macOS target project for composer-icon test app.
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json macOS Icon Composer metadata fixture.
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/front.png macOS Icon Composer asset fixture.
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/back.png macOS Icon Composer asset fixture.

Comment on lines +668 to +674
// actool may fail on the placeholder .icon content, but the validation phase should pass
actool.Execute ();

// Verify that no error was logged about the icon not being found
var errorMessages = Engine.Logger.ErrorEvents.Select (e => e.Message).ToList ();
Assert.IsFalse (errorMessages.Any (m => m.Contains ("Can't find the AppIcon")),
"Should not report that AppIcon is not found among image resources");
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

These new tests call actool.Execute () directly and then only assert that a specific validation error message wasn't logged. This can allow unrelated task failures (including unexpected logged errors) to slip through without failing the test, and it also bypasses the ExecuteTask helper that surfaces logged events on failure. Consider using ExecuteTask with an assertion strategy that still tolerates actool compilation failing, or at least assert on the task return value and/or that any errors are only the expected actool/tooling ones (while still ensuring the AppIcon/AlternateAppIcon validation passes).

Copilot uses AI. Check for mistakes.
Assert.That (frontPngInBundle, Does.Not.Exist, "front.png should not be in the app bundle as a raw BundleResource");

var backPngInBundle = Path.Combine (resourcesDirectory, "AppIcon.icon", "Assets", "back.png");
Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource");
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

This new test verifies that the raw .icon inputs aren't copied as BundleResources, but it doesn't assert that asset compilation actually produced an Assets.car in the app bundle. Without that assertion, the test could pass even if the icon catalog was ignored (resulting in missing app icons). Consider asserting Assets.car exists (as TestXCAssetsImpl does) and/or validating that the compiled assets contain at least the expected icon entry.

Suggested change
Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource");
Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource");
// Verify that the compiled asset catalog exists in the app bundle
string? assetsCarPath = null;
foreach (var file in Directory.GetFiles (resourcesDirectory, "Assets.car", SearchOption.AllDirectories)) {
assetsCarPath = file;
break;
}
Assert.That (assetsCarPath, Is.Not.Null, "Compiled asset catalog (Assets.car) should exist in the app bundle");

Copilot uses AI. Check for mistakes.
Comment on lines 12 to 35
public class AppDelegate : UIApplicationDelegate {
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
return true;
}
}
#endif

public class Program {
static int Main (string [] args)
{
#if __MACCATALYST__ || __MACOS__
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly

Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));

return args.Length;
#else
UIApplication.Main (args, null, typeof (AppDelegate));
return 0;
#endif
}
}
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

AppDelegate.cs is not formatted consistently with the rest of the test apps in this repo (missing tab indentation and standard brace layout). Please reformat to match the existing style (see tests/dotnet/AppWithXCAssets/AppDelegate.cs for an in-repo example) so that future diffs remain readable and editors can apply the repo's .editorconfig settings cleanly.

Suggested change
public class AppDelegate : UIApplicationDelegate {
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
return true;
}
}
#endif
public class Program {
static int Main (string [] args)
{
#if __MACCATALYST__ || __MACOS__
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));
return args.Length;
#else
UIApplication.Main (args, null, typeof (AppDelegate));
return 0;
#endif
}
}
}
public class AppDelegate : UIApplicationDelegate {
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
return true;
}
}
#endif
public class Program {
static int Main (string [] args)
{
#if __MACCATALYST__ || __MACOS__
GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly
Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD"));
return args.Length;
#else
UIApplication.Main (args, null, typeof (AppDelegate));
return 0;
#endif
}
}
}

Copilot uses AI. Check for mistakes.
@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [PR Build #40f00db] Build passed (Detect API changes) ✅

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [CI Build #40f00db] Build passed (Build packages) ✅

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ API diff for current PR / commit

NET (empty diffs)

✅ API diff vs stable

NET (empty diffs)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2
Copy link
Collaborator

✅ [CI Build #40f00db] Build passed (Build macOS tests) ✅

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build #40f00db] Tests on macOS X64 - Mac Sonoma (14) passed 💻

All tests on macOS X64 - Mac Sonoma (14) passed.

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build #40f00db] Tests on macOS M1 - Mac Monterey (12) passed 💻

All tests on macOS M1 - Mac Monterey (12) passed.

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build #40f00db] Tests on macOS arm64 - Mac Sequoia (15) passed 💻

All tests on macOS arm64 - Mac Sequoia (15) passed.

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build #40f00db] Tests on macOS M1 - Mac Ventura (13) passed 💻

All tests on macOS M1 - Mac Ventura (13) passed.

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build #40f00db] Tests on macOS arm64 - Mac Tahoe (26) passed 💻

All tests on macOS arm64 - Mac Tahoe (26) passed.

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

🔥 [CI Build #40f00db] Test results 🔥

Test results

❌ Tests failed on VSTS: test results

0 tests crashed, 1 tests failed, 130 tests passed.

Failures

❌ linker tests

1 tests failed, 43 tests passed.

Failed tests

  • link sdk/iOS - simulator/Debug: LaunchFailure

Html Report (VSDrops) Download

Successes

✅ cecil: All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 5 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 6 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 11 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 15 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 12 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 11 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ sharpie: All 1 tests passed. Html Report (VSDrops) Download
✅ windows: All 3 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

Pipeline on Agent
Hash: 40f00db5ee03047a1dbb6d97d9cf0a0f4c0f8a7a [PR build]

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

Labels

community Community contribution ❤ copilot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments