Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/developer-guide/Miscellaneous-Features.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ No code changes are required — Android's resource framework switches icons whe

iOS does not localize launcher icons natively, so Codename One wires up https://developer.apple.com/documentation/uikit/uiapplication/2806818-setalternateiconname[alternate app icons] for you:

* For each detected locale the build generates `AppIcon_<lang>_<COUNTRY>60x60@2x.png` (120×120), `@3x.png` (180×180) and `76x76@2x~ipad.png` (152×152) in the app bundle root.
* For each detected locale the build generates `AppIcon_<lang>_<COUNTRY>@2x.png` (120×120), `@3x.png` (180×180), `@2x~ipad.png` (152×152) and `83.5x83.5@2x~ipad.png` (167×167 for iPad Pro) in the app bundle root.
* A `CFBundleIcons` (and `CFBundleIcons~ipad`) entry is injected into `Info.plist` containing a `CFBundleAlternateIcons` dictionary with one entry per locale. `CFBundlePrimaryIcon` continues to reference the default `iPhone7App`/`iPadApp7` image families.
* The `CodenameOne_GLAppDelegate` is patched to call `-[UIApplication setAlternateIconName:completionHandler:]` at launch. The delegate reads `[NSLocale preferredLanguages]`, tries the full `<lang>_<COUNTRY>` key first, then falls back to the language-only key, and clears the alternate icon (reverting to the default) if no variant matches.
* The injection is idempotent and runs before the `ios.afterFinishLaunching` hook, so any custom code you supply via that build hint is unaffected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3159,18 +3159,36 @@ private void copyIcon(String name, File srcDir, File destDir) throws IOException
}

private String buildLocalizedIconsPlistFragment() {
StringBuilder alternateIcons = new StringBuilder();
// iOS resolves alternate icons by appending @2x/@3x/~ipad to each basename
// listed in CFBundleIconFiles. The iPhone basename catches AppIcon_<loc>@2x.png
// (120) and AppIcon_<loc>@3x.png (180); the iPad basename catches
// AppIcon_<loc>@2x~ipad.png (152). The 167x167 iPad Pro size cannot be
// expressed via @-modifiers, so it is referenced through its explicit
// 83.5x83.5 basename.
StringBuilder iphoneAlternate = new StringBuilder();
StringBuilder ipadAlternate = new StringBuilder();
for (Map.Entry<String, String> entry : localizedIcons.entrySet()) {
String iconName = entry.getKey();
alternateIcons.append(" <key>").append(iconName).append("</key>\n");
alternateIcons.append(" <dict>\n");
alternateIcons.append(" <key>CFBundleIconFiles</key>\n");
alternateIcons.append(" <array>\n");
alternateIcons.append(" <string>").append(iconName).append("</string>\n");
alternateIcons.append(" </array>\n");
alternateIcons.append(" <key>UIPrerenderedIcon</key>\n");
alternateIcons.append(" <false/>\n");
alternateIcons.append(" </dict>\n");
iphoneAlternate.append(" <key>").append(iconName).append("</key>\n");
iphoneAlternate.append(" <dict>\n");
iphoneAlternate.append(" <key>CFBundleIconFiles</key>\n");
iphoneAlternate.append(" <array>\n");
iphoneAlternate.append(" <string>").append(iconName).append("</string>\n");
iphoneAlternate.append(" </array>\n");
iphoneAlternate.append(" <key>UIPrerenderedIcon</key>\n");
iphoneAlternate.append(" <false/>\n");
iphoneAlternate.append(" </dict>\n");

ipadAlternate.append(" <key>").append(iconName).append("</key>\n");
ipadAlternate.append(" <dict>\n");
ipadAlternate.append(" <key>CFBundleIconFiles</key>\n");
ipadAlternate.append(" <array>\n");
ipadAlternate.append(" <string>").append(iconName).append("</string>\n");
ipadAlternate.append(" <string>").append(iconName).append("83.5x83.5</string>\n");
ipadAlternate.append(" </array>\n");
ipadAlternate.append(" <key>UIPrerenderedIcon</key>\n");
ipadAlternate.append(" <false/>\n");
ipadAlternate.append(" </dict>\n");
}
StringBuilder out = new StringBuilder();
out.append("\n<key>CFBundleIcons</key>\n");
Expand All @@ -3185,7 +3203,7 @@ private String buildLocalizedIconsPlistFragment() {
out.append(" </dict>\n");
out.append(" <key>CFBundleAlternateIcons</key>\n");
out.append(" <dict>\n");
out.append(alternateIcons);
out.append(iphoneAlternate);
out.append(" </dict>\n");
out.append("</dict>\n");
out.append("<key>CFBundleIcons~ipad</key>\n");
Expand All @@ -3200,7 +3218,7 @@ private String buildLocalizedIconsPlistFragment() {
out.append(" </dict>\n");
out.append(" <key>CFBundleAlternateIcons</key>\n");
out.append(" <dict>\n");
out.append(alternateIcons);
out.append(ipadAlternate);
out.append(" </dict>\n");
out.append("</dict>\n");
return out.toString();
Expand Down Expand Up @@ -3370,9 +3388,15 @@ public boolean accept(File dir, String name) {
candidate.delete();
continue;
}
createIconFile(new File(resDir, iconName + "60x60@2x.png"), img, 120, 120);
createIconFile(new File(resDir, iconName + "60x60@3x.png"), img, 180, 180);
createIconFile(new File(resDir, iconName + "76x76@2x~ipad.png"), img, 152, 152);
// File names must match the basenames listed in CFBundleIconFiles so iOS
// can resolve them via the standard @2x/@3x/~ipad modifiers; the
// 83.5x83.5 iPad Pro icon cannot be expressed via modifiers and so
// carries the size in the file name and is referenced explicitly in
// buildLocalizedIconsPlistFragment.
createIconFile(new File(resDir, iconName + "@2x.png"), img, 120, 120);
createIconFile(new File(resDir, iconName + "@3x.png"), img, 180, 180);
createIconFile(new File(resDir, iconName + "@2x~ipad.png"), img, 152, 152);
createIconFile(new File(resDir, iconName + "83.5x83.5@2x~ipad.png"), img, 167, 167);
localizedIcons.put(iconName, localeKey);
// Remove the original so it isn't bundled as a stray resource.
candidate.delete();
Expand Down
Loading