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
8 changes: 0 additions & 8 deletions CodenameOne/src/com/codename1/annotations/buildhints/Ios.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,6 @@
@Hint(valuePattern = "auto|modern|ios7|legacy")
ThemeMode themeMode() default ThemeMode.DEFAULT;

/// true/false (defaults to true). Enables iOS UIScene lifecycle support.
/// UIScene lets iOS manage one or more app UI sessions independently,
/// improving lifecycle handling in modern iOS versions. Apple has indicated
/// UIScene will be required starting with iOS 27, so this is now on by
/// default; set the flag to `false` only if you need to temporarily fall back
/// to the legacy `UIApplicationDelegate` lifecycle.
Toggle uiscene() default Toggle.DEFAULT;

/// Allows intercepting a URL call using the syntax `<string>urlPrefix<string>`
String urlScheme() default "";
}
15 changes: 9 additions & 6 deletions Ports/iOSPort/nativeSources/CodenameOne_GLAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
* need additional information or have any questions.
*/
#import "CN1AppleUI.h"
#ifdef CN1_USE_UI_SCENE
#import <UIKit/UIScene.h>
#endif
//#define CN1_INCLUDE_NOTIFICATIONS
#ifdef CN1_INCLUDE_NOTIFICATIONS
#import <UserNotifications/UserNotifications.h>
Expand Down Expand Up @@ -51,11 +49,17 @@

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
// No IBOutlet on either: nothing loads a nib into this class any more. MainWindow.xib wired
// both, and it went away with NSMainNibFile -- UIApplicationMain creates the delegate from the
// class name, the scene delegate hands it the window, and cn1EnsureViewController builds the
// view controller.
@property (nonatomic, retain) UIWindow *window;

@property (nonatomic, retain) IBOutlet CodenameOne_GLViewController *viewController;
@property (nonatomic, retain) CodenameOne_GLViewController *viewController;

#ifdef CN1_USE_UI_SCENE
// Called by CodenameOne_GLSceneDelegate, which is the only lifecycle there is: UIKit
// creates the window from the scene, and these are the app-level steps the delegate used
// to run itself when it owned the window.
- (void)cn1InstallRootViewControllerIntoWindow:(UIWindow *)window;
- (void)cn1ApplicationWillResignActive;
- (void)cn1ApplicationDidEnterBackground;
Expand All @@ -66,6 +70,5 @@
url:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation;
#endif

@end
20 changes: 8 additions & 12 deletions Ports/iOSPort/nativeSources/CodenameOne_GLAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
#if !TARGET_OS_WATCH

#import "CodenameOne_GLAppDelegate.h"
#ifdef CN1_USE_UI_SCENE
#import "CodenameOne_GLSceneDelegate.h"
#endif
#import "CN1JailbreakDetector.h"
#include "xmlvm.h"
#import <objc/message.h>
Expand Down Expand Up @@ -586,9 +584,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
cn1_debugger_start();
#endif
[self cn1EnsureViewController];
#ifndef CN1_USE_UI_SCENE
[self cn1InstallRootViewControllerIntoWindow:self.window];
#endif
// The root view controller is installed by CodenameOne_GLSceneDelegate, not here: under
// the scene lifecycle self.window is nil at this point, because UIKit has not connected
// a scene yet.
Comment on lines +587 to +589

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Generate scene metadata for standalone iOS translations

When ByteCodeTranslator is invoked directly in ios mode, handleAppleOutput() copies template-Info.plist into the generated Xcode project without the later IPhoneBuilder.injectToPlist() pass. That template now contains neither UIApplicationSceneManifest nor launch metadata, while this code no longer installs the root controller through the legacy lifecycle and the MainWindow NIB was deleted. Consequently, standalone translated projects never connect CodenameOne_GLSceneDelegate, launch without a window, and are also invalid when linked with the iOS 27 SDK; generate the required metadata in the translator path or retain a working fallback.

Useful? React with 👍 / 👎.

NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
[self cn1StoreAppArgForURL:url];
if (@available(iOS 8, *)) {
Expand Down Expand Up @@ -767,20 +765,18 @@ - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:
return YES;
}

#ifdef CN1_USE_UI_SCENE
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0))
{
UISceneConfiguration *sceneConfiguration = [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
sceneConfiguration.delegateClass = [CodenameOne_GLSceneDelegate class];
return sceneConfiguration;
Comment on lines 768 to 772

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return the CarPlay delegate for CarPlay scene sessions

When an app references com.codename1.car, the generated manifest correctly assigns CPTemplateApplicationSceneSessionRoleApplication to CodenameOne_CarPlaySceneDelegate, but this now-unconditional callback replaces that configuration for every role with one whose delegate is CodenameOne_GLSceneDelegate. A CarPlay scene is not a UIWindowScene, so that delegate immediately returns from willConnectToSession: and never initializes the CarPlay interface. Branch on connectingSceneSession.role to return the CarPlay delegate/configuration, or allow the manifest's CarPlay configuration to be selected.

Useful? React with 👍 / 👎.

}
#endif

// Compiled for universal links OR intents OR continuity: without the second and third conditions
// a Spotlight tap, or a handoff from the user's other device, on a legacy-lifecycle build
// (ios.uiscene=false) would silently do nothing, since the scene delegate is what routes this on
// a default build. Continuity was added here for exactly the reason intents was: the branch it
// needs inside cn1ContinueUserActivity: is compiled, and on a legacy build nothing ever calls it.
// Compiled for universal links OR intents OR continuity. The scene delegate is what routes this
// on a live app, so this app-level callback is the path UIKit takes when it hands the activity
// to the application rather than to a scene -- a Spotlight tap or a handoff from the user's
// other device. Intents and continuity are in the condition because the branch each needs inside
// cn1ContinueUserActivity: has to be compiled for that call to do anything.
#if defined(CN1_HANDLE_UNIVERSAL_LINKS) || defined(CN1_USE_INTENTS) \
|| defined(CN1_USE_CONTINUITY)
// https://developer.apple.com/documentation/uikit/core_app/allowing_apps_and_websites_to_link_to_your_content?language=objc
Expand Down
2 changes: 0 additions & 2 deletions Ports/iOSPort/nativeSources/CodenameOne_GLSceneDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@
#import "CN1AppleUI.h"
#import "CodenameOne_GLAppDelegate.h"

#ifdef CN1_USE_UI_SCENE
API_AVAILABLE(ios(13.0))
@interface CodenameOne_GLSceneDelegate : UIResponder <UIWindowSceneDelegate>

@property (nonatomic, retain) UIWindow *window;

@end
#endif
2 changes: 0 additions & 2 deletions Ports/iOSPort/nativeSources/CodenameOne_GLSceneDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
extern void CN1MacWindowDeliverVisibility(int windowId, BOOL shown);
#endif

#ifdef CN1_USE_UI_SCENE
@implementation CodenameOne_GLSceneDelegate

@synthesize window=_window;
Expand Down Expand Up @@ -351,7 +350,6 @@ - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivi
}

@end
#endif

#else
// Compiled out on watchOS: this file is OpenGL ES / Metal / UIKit-only and the watch
Expand Down
33 changes: 0 additions & 33 deletions Ports/iOSPort/nativeSources/MainWindow.xib

This file was deleted.

33 changes: 0 additions & 33 deletions Ports/iOSPort/nativeSources/MainWindowMETAL.xib

This file was deleted.

30 changes: 21 additions & 9 deletions docs/developer-guide/Working-With-iOS.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,31 @@ If you've access to a Mac, connect the device, open Xcode, and use the device ex

[[section-ios-launch-screen]]
[[ios-launch-storyboard]]
=== Launch screen storyboard best practices
=== Launch screen best practices

Launch screen storyboards are the default approach for Codename One iOS builds. Apple requires a storyboard-based launch experience for modern devices, so the legacy screenshot generator was removed in favor of a single adaptive layout. You can still opt back into the old behavior by setting the `ios.generateSplashScreens=true` build hint, but it's best to use a storyboard unless your use case can't be expressed with Auto Layout.
Every iOS build declares a launch screen. Apps linked with the iOS 27 SDK or later are rejected unless the bundle declares one of `UILaunchStoryboardName`, `UILaunchStoryboards`, `UILaunchScreen` or `UILaunchScreens`, and `UIRequiresFullScreen` isn't a substitute for any of them. The build checks the finished `Info.plist` and fails if none of the four is there, so an `ios.plistInject` that removes the generated key stops the build rather than producing an archive the App Store refuses.

What the build declares by default is `UILaunchScreen`: the system background color, which follows light and dark mode on its own, with `Launch.Foreground.png` centered on it. That's deliberate rather than a simplification. Every Codename One app runs on the UIScene lifecycle, and SplashBoard doesn't render a launch *storyboard* for a scene-based app -- it animates from a black frame instead. Since iOS prefers the storyboard whenever both keys are present, declaring one would mean a black launch.

You can still take the storyboard, and the build still ships `LaunchScreen.storyboard` for you to point at. Declare the key yourself, which overrides the generated one:

[source]
----
codename1.arg.ios.plistInject=<key>UILaunchStoryboardName</key><string>LaunchScreen</string>
----

Do that only if you've verified the result on the devices you ship to. The same applies to `UILaunchStoryboards` and `UILaunchScreens`: declare either one through `ios.plistInject` and the build leaves your launch experience alone.

The `ios.generateSplashScreens`, `ios.uiscene` and `ios.launchStoryboardName` build hints have been removed, and a build that still sets one fails with a message explaining what replaced it. The first named the pre-storyboard `Default*.png` generator, which iOS stopped using long ago; the second selected the legacy `UIApplicationDelegate` lifecycle, which Apple no longer permits; the third named the storyboard for a key only that legacy lifecycle emitted.

==== Key files

The build server provides a minimal launch storyboard automatically. Customize it by adding any of the following files under your project's `ios/src/main/resources` directory:
The build provides the default launch screen automatically. Customize it by adding either of the following files under your project's `ios/src/main/resources` directory:

. `Launch.Foreground.png` - Shown in the center of the screen instead of your app icon.
. `Launch.Background.png` - Drawn behind the content to provide a color or illustration.
. `LaunchScreen.storyboard` - A custom storyboard created in Xcode that replaces the default layout entirely.
. `Launch.Foreground.png` - Shown in the center of the screen instead of your app icon. Used by the default `UILaunchScreen`.
. `Launch.Background.png` - Drawn behind the content to provide a color or illustration. Read by `LaunchScreen.storyboard`, so it only applies if you opt into the storyboard as shown above.

IMPORTANT: Make sure to add the `ios.multitasking=true` build hint or your launch storyboard won't be used.
You can also replace `LaunchScreen.storyboard` itself with a custom storyboard created in Xcode.

==== Designing a flexible Layout

Expand All @@ -58,14 +70,14 @@ The default storyboard expects PNG assets with the following characteristics. Al
|Provide optional `Launch.Foreground@2x.png` (304×304) and `Launch.Foreground@3x.png` (456×456) for sharper output. Use transparency to let the background show through.

|`Launch.Background.png`
|Full-screen backdrop
|Full-screen backdrop, storyboard only
|1024×1024
|Supply complementary `Launch.Background@2x.png` (2048×2048) and `Launch.Background@3x.png` (3072×3072) if you rely on artwork instead of a flat color. Keep file sizes small (<2 MB) to avoid slowing startup.

|`LaunchScreen.storyboard`
|Complete custom layout
|N/A
|Target iOS 12.0 and later, enable Auto Layout, and include constraints for every view. Avoid timers or code connections.
|Only used if you declare `UILaunchStoryboardName` through `ios.plistInject`. Enable Auto Layout and include constraints for every view. Avoid timers or code connections.
|===

==== Testing changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,14 +516,6 @@ static void register(List<Hint> h) {
.def("false")
.platform("ios"));

h.add(new Hint("ios.generateSplashScreens")
.group(HintGroup.IOS)
.type(HintType.BOOLEAN)
.def("false")
.platform("ios")
.doc("Boolean true/false defaults to false. Enables legacy generation of splash screen images "
+ "instead of the current launch storyboards."));

h.add(new Hint("ios.glAppDelegateBody")
.group(HintGroup.IOS)
.type(HintType.STRING)
Expand Down Expand Up @@ -654,12 +646,6 @@ static void register(List<Hint> h) {
.def("true")
.platform("ios"));

h.add(new Hint("ios.launchStoryboardName")
.group(HintGroup.IOS)
.type(HintType.STRING)
.def("LaunchScreen")
.platform("ios"));

h.add(new Hint("ios.locationUsageDescription")
.group(HintGroup.IOS)
.type(HintType.STRING)
Expand Down
Loading
Loading