diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java index 188bd09878..ddb3e90687 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java @@ -3185,32 +3185,65 @@ private String buildLocalizedIconSelectorObjC() { mapping.append("@\"").append(entry.getValue()).append("\": @\"").append(entry.getKey()).append("\""); } mapping.append(" }"); + // Wait for UIApplicationDidBecomeActiveNotification before calling + // -[UIApplication setAlternateIconName:]. Calling from didFinishLaunching -- + // even via dispatch_async on the main queue -- routinely fails with + // NSCocoaErrorDomain Code=3072 ("operation was cancelled") because the + // system alert iOS shows for an icon change has no active foreground scene + // to anchor to yet. Deferring to the active state fixes the silent failure + // that the user reported on top of #4870. The completion handler is wired + // up so any remaining bundle-configuration problem surfaces in the device + // log instead of being swallowed (the original nil handler hid this). return "\n // Codename One localized app icon selection\n" + " if ([[UIApplication sharedApplication] respondsToSelector:@selector(setAlternateIconName:completionHandler:)]) {\n" + " NSDictionary *cn1LocalizedIcons =\n" + mapping + ";\n" - + " NSString *cn1CurrentIcon = [[UIApplication sharedApplication] alternateIconName];\n" - + " NSString *cn1TargetIcon = nil;\n" - + " NSArray *cn1PrefLangs = [NSLocale preferredLanguages];\n" - + " if (cn1PrefLangs.count > 0) {\n" - + " NSString *cn1PrefLang = [cn1PrefLangs objectAtIndex:0];\n" - + " NSArray *cn1LangParts = [cn1PrefLang componentsSeparatedByCharactersInSet:\n" - + " [NSCharacterSet characterSetWithCharactersInString:@\"-_\"]];\n" - + " if (cn1LangParts.count >= 2) {\n" - + " NSString *cn1Key = [NSString stringWithFormat:@\"%@_%@\",\n" - + " [[cn1LangParts objectAtIndex:0] lowercaseString],\n" - + " [[cn1LangParts objectAtIndex:1] uppercaseString]];\n" - + " cn1TargetIcon = [cn1LocalizedIcons objectForKey:cn1Key];\n" + + " void (^cn1ApplyIcon)(void) = ^{\n" + + " NSString *cn1CurrentIcon = [[UIApplication sharedApplication] alternateIconName];\n" + + " NSString *cn1TargetIcon = nil;\n" + + " NSArray *cn1PrefLangs = [NSLocale preferredLanguages];\n" + + " if (cn1PrefLangs.count > 0) {\n" + + " NSString *cn1PrefLang = [cn1PrefLangs objectAtIndex:0];\n" + + " NSArray *cn1LangParts = [cn1PrefLang componentsSeparatedByCharactersInSet:\n" + + " [NSCharacterSet characterSetWithCharactersInString:@\"-_\"]];\n" + + " if (cn1LangParts.count >= 2) {\n" + + " NSString *cn1Key = [NSString stringWithFormat:@\"%@_%@\",\n" + + " [[cn1LangParts objectAtIndex:0] lowercaseString],\n" + + " [[cn1LangParts objectAtIndex:1] uppercaseString]];\n" + + " cn1TargetIcon = [cn1LocalizedIcons objectForKey:cn1Key];\n" + + " }\n" + + " if (cn1TargetIcon == nil && cn1LangParts.count >= 1) {\n" + + " NSString *cn1Key = [[cn1LangParts objectAtIndex:0] lowercaseString];\n" + + " cn1TargetIcon = [cn1LocalizedIcons objectForKey:cn1Key];\n" + + " }\n" + " }\n" - + " if (cn1TargetIcon == nil && cn1LangParts.count >= 1) {\n" - + " NSString *cn1Key = [[cn1LangParts objectAtIndex:0] lowercaseString];\n" - + " cn1TargetIcon = [cn1LocalizedIcons objectForKey:cn1Key];\n" + + " BOOL cn1NeedsUpdate = (cn1TargetIcon == nil && cn1CurrentIcon != nil)\n" + + " || (cn1TargetIcon != nil && ![cn1TargetIcon isEqualToString:cn1CurrentIcon]);\n" + + " if (!cn1NeedsUpdate) {\n" + + " return;\n" + " }\n" - + " }\n" - + " BOOL cn1NeedsUpdate = (cn1TargetIcon == nil && cn1CurrentIcon != nil)\n" - + " || (cn1TargetIcon != nil && ![cn1TargetIcon isEqualToString:cn1CurrentIcon]);\n" - + " if (cn1NeedsUpdate) {\n" - + " [[UIApplication sharedApplication] setAlternateIconName:cn1TargetIcon completionHandler:nil];\n" + + " NSString *cn1FinalTarget = cn1TargetIcon;\n" + + " [[UIApplication sharedApplication] setAlternateIconName:cn1FinalTarget completionHandler:^(NSError * _Nullable cn1IconErr) {\n" + + " if (cn1IconErr != nil) {\n" + + " NSLog(@\"[CodenameOne] Failed to set alternate app icon '%@': %@\", cn1FinalTarget ?: @\"(primary)\", cn1IconErr);\n" + + " } else {\n" + + " NSLog(@\"[CodenameOne] Set alternate app icon to '%@'\", cn1FinalTarget ?: @\"(primary)\");\n" + + " }\n" + + " }];\n" + + " };\n" + + " if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {\n" + + " dispatch_async(dispatch_get_main_queue(), cn1ApplyIcon);\n" + + " } else {\n" + + " __block id cn1ActiveObs = nil;\n" + + " cn1ActiveObs = [[NSNotificationCenter defaultCenter]\n" + + " addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue]\n" + + " usingBlock:^(NSNotification *cn1Note) {\n" + + " if (cn1ActiveObs != nil) {\n" + + " [[NSNotificationCenter defaultCenter] removeObserver:cn1ActiveObs];\n" + + " cn1ActiveObs = nil;\n" + + " }\n" + + " cn1ApplyIcon();\n" + + " }];\n" + " }\n" + " }\n"; }