Skip to content

Fix iOS localized icons: inject CFBundleIconName so actool emits CFBundleAlternateIcons#4870

Merged
shai-almog merged 1 commit intomasterfrom
fix-ios-localized-icons-cfbundleiconname
May 5, 2026
Merged

Fix iOS localized icons: inject CFBundleIconName so actool emits CFBundleAlternateIcons#4870
shai-almog merged 1 commit intomasterfrom
fix-ios-localized-icons-cfbundleiconname

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

  • The asset-catalog approach for localized iOS launcher icons (introduced in Adding support for localized app icons #4789, refined in Fixed localized icon generation in iOS (again) #4831) relies on actool generating a partial Info.plist containing CFBundleIcons / CFBundleAlternateIcons from the asset catalog. Xcode then merges that partial into the app's final Info.plist so -[UIApplication setAlternateIconName:completionHandler:] can resolve alternate icons at runtime.
  • actool only emits that partial plist when CFBundleIconName is present in the source Info.plist. Our template (vm/ByteCodeTranslator/src/template/template/template-Info.plist) only has the legacy CFBundleIconFiles array, so the alternate icons get compiled into the .car but the bundle never advertises them. Calling setAlternateIconName: then fails with "alternate icon name not found in bundle" — which is the symptom a customer just hit on a build.
  • Inject <key>CFBundleIconName</key><string>AppIcon</string> into Info.plist whenever localizedIcons is non-empty (the only path that needs it), so actool produces the merged plist with both CFBundlePrimaryIcon and CFBundleAlternateIcons, and the runtime selector wired up in CodenameOne_GLAppDelegate.m can switch to the locale-specific icon.

Verification on a customer build

Customer's generated sources had everything but the plist key:

  • Images.xcassets/AppIcon_ar_AE.appiconset and AppIcon_en_AE.appiconset correctly created with PNGs and Contents.json
  • project.pbxproj had ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon_ar_AE AppIcon_en_AE"
  • CodenameOne_GLAppDelegate.m had the setAlternateIconName: selector ✅
  • hPLUS-Info.plist was missing CFBundleIconName ❌ ← root cause

A matching change is going out to the BuildDaemon (the production build server uses that codebase, not this maven plugin source directly).

Test plan

  • mvn test -Plocal-dev-javase in maven/codenameone-maven-plugin/ passes (43/43 ✅ locally)
  • On a project with cn1_icon_<lang>[_<COUNTRY>].png files in native/ios/ (or in the resources), trigger an iOS build and confirm:
    • *-Info.plist in the generated Xcode source now contains <key>CFBundleIconName</key><string>AppIcon</string>
    • The built .app's final Info.plist (post-actool merge) contains CFBundleIconsCFBundleAlternateIcons with one entry per locale
    • On device: switching the system language to one of the registered locales causes the launcher icon to switch on next launch
  • Build a project without any cn1_icon_*.png files and confirm the new key is not injected (regression guard)

🤖 Generated with Claude Code

…ndleAlternateIcons

The asset-catalog-based approach for localized iOS launcher icons relies on
actool generating a partial Info.plist that contains CFBundleIcons /
CFBundleAlternateIcons, which is then merged into the app's Info.plist.
actool only emits that partial plist when CFBundleIconName is present in
the source Info.plist; without it the alternate icons get compiled into
the .car file but the bundle never advertises them, so
-[UIApplication setAlternateIconName:completionHandler:] fails at runtime
("alternate icon name not found in bundle").

Inject `<key>CFBundleIconName</key><string>AppIcon</string>` whenever
localized icons are present so actool produces the correct merged plist
and the runtime selector wired up in CodenameOne_GLAppDelegate.m can
resolve the locale-specific icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 5, 2026

Compared 89 screenshots: 89 matched.

Native Android coverage

  • 📊 Line coverage: 9.93% (5401/54409 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.81% (26553/339885), branch 3.58% (1168/32604), complexity 4.59% (1435/31231), method 8.09% (1180/14594), class 13.42% (263/1960)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.93% (5401/54409 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.81% (26553/339885), branch 3.58% (1168/32604), complexity 4.59% (1435/31231), method 8.09% (1180/14594), class 13.42% (263/1960)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 1278.000 ms
Base64 CN1 encode 100.000 ms
Base64 encode ratio (CN1/native) 0.078x (92.2% faster)
Base64 native decode 1039.000 ms
Base64 CN1 decode 206.000 ms
Base64 decode ratio (CN1/native) 0.198x (80.2% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 5, 2026

Compared 87 screenshots: 87 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 330 seconds

Build and Run Timing

Metric Duration
Simulator Boot 104000 ms
Simulator Boot (Run) 2000 ms
App Install 33000 ms
App Launch 8000 ms
Test Execution 282000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 3660.000 ms
Base64 CN1 encode 3338.000 ms
Base64 encode ratio (CN1/native) 0.912x (8.8% faster)
Base64 native decode 2224.000 ms
Base64 CN1 decode 1312.000 ms
Base64 decode ratio (CN1/native) 0.590x (41.0% faster)
Base64 SIMD encode 493.000 ms
Base64 encode ratio (SIMD/native) 0.135x (86.5% faster)
Base64 encode ratio (SIMD/CN1) 0.148x (85.2% faster)
Base64 SIMD decode 525.000 ms
Base64 decode ratio (SIMD/native) 0.236x (76.4% faster)
Base64 decode ratio (SIMD/CN1) 0.400x (60.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 80.000 ms
Image createMask (SIMD on) 12.000 ms
Image createMask ratio (SIMD on/off) 0.150x (85.0% faster)
Image applyMask (SIMD off) 260.000 ms
Image applyMask (SIMD on) 63.000 ms
Image applyMask ratio (SIMD on/off) 0.242x (75.8% faster)
Image modifyAlpha (SIMD off) 196.000 ms
Image modifyAlpha (SIMD on) 75.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.383x (61.7% faster)
Image modifyAlpha removeColor (SIMD off) 184.000 ms
Image modifyAlpha removeColor (SIMD on) 92.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.500x (50.0% faster)
Image PNG encode (SIMD off) 1255.000 ms
Image PNG encode (SIMD on) 1197.000 ms
Image PNG encode ratio (SIMD on/off) 0.954x (4.6% faster)
Image JPEG encode 623.000 ms

@shai-almog shai-almog merged commit 0e4590f into master May 5, 2026
23 of 25 checks passed
shai-almog added a commit that referenced this pull request May 6, 2026
…surface errors (#4870 follow-up) (#4873)

Builds on #4870 (CFBundleIconName injection) which made actool emit the
correct partial Info.plist. Even with a correctly merged
CFBundleIcons.CFBundleAlternateIcons table, calling
-[UIApplication setAlternateIconName:completionHandler:] from
application:didFinishLaunchingWithOptions: still fails silently with
NSCocoaErrorDomain Code=3072 ("operation was cancelled") because no
foreground UIScene is yet available to anchor the system icon-change
alert. Without a completion handler the failure was silent, which is
why developers reported "no localized icon and no permission dialog"
even after #4870.

Changes in buildLocalizedIconSelectorObjC():
* Defer the icon switch to the next UIApplicationDidBecomeActiveNotification
  (or run immediately on the main queue if the app is already active),
  giving iOS an active scene to host the system alert.
* Pass a real completion handler that NSLogs successes and errors so any
  remaining bundle-configuration problem is visible in the device log
  instead of being swallowed.

Validated end-to-end against a freshly generated cn1app-archetype
project: actool produces the expected CFBundleAlternateIcons entry, the
resulting .app builds for the iOS 18.6 and iOS 26.3 simulators, and
LaunchServices logs "Setting preferredIconName to AppIcon_es" on launch
under es_ES locale, where the prior selector logged
NSUserCancelledError. The matching change is in the BuildDaemon repo.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant