From 457985dddc229b5d5b69951567d65775b721e9ce Mon Sep 17 00:00:00 2001 From: markm39 Date: Wed, 14 Oct 2026 15:25:28 -0500 Subject: [PATCH] fix(ios): adopt UIScene lifecycle so iOS 27 launches the app (1.3.6 build 20) --- app.json | 4 +- ios/OpenNotes.xcodeproj/project.pbxproj | 8 ++-- ios/OpenNotes/AppDelegate.swift | 55 +++++++++++++++++++++---- ios/OpenNotes/Info.plist | 21 +++++++++- 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/app.json b/app.json index 1540413..4a26269 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "OpenNotes", "slug": "open-notes", - "version": "1.3.5", + "version": "1.3.6", "scheme": "opennotes", "orientation": "default", "icon": "./assets/icon.png", @@ -16,7 +16,7 @@ "ios": { "bundleIdentifier": "com.builderpro.opennotes", "icon": "./assets/icon.png", - "buildNumber": "19", + "buildNumber": "20", "supportsTablet": true, "usesIcloudStorage": true, "entitlements": { diff --git a/ios/OpenNotes.xcodeproj/project.pbxproj b/ios/OpenNotes.xcodeproj/project.pbxproj index d487137..4746d45 100644 --- a/ios/OpenNotes.xcodeproj/project.pbxproj +++ b/ios/OpenNotes.xcodeproj/project.pbxproj @@ -364,7 +364,7 @@ CODE_SIGN_ENTITLEMENTS = OpenNotes/OpenNotes.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = U2CPXQV7AJ; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -377,7 +377,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -403,7 +403,7 @@ CODE_SIGN_ENTITLEMENTS = OpenNotes/OpenNotes.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 19; + CURRENT_PROJECT_VERSION = 20; DEVELOPMENT_TEAM = U2CPXQV7AJ; INFOPLIST_FILE = OpenNotes/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -411,7 +411,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.5; + MARKETING_VERSION = 1.3.6; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/ios/OpenNotes/AppDelegate.swift b/ios/OpenNotes/AppDelegate.swift index a7887e1..7069d55 100644 --- a/ios/OpenNotes/AppDelegate.swift +++ b/ios/OpenNotes/AppDelegate.swift @@ -21,14 +21,8 @@ public class AppDelegate: ExpoAppDelegate { reactNativeFactory = factory bindReactNativeFactory(factory) -#if os(iOS) || os(tvOS) - window = UIWindow(frame: UIScreen.main.bounds) - factory.startReactNative( - withModuleName: "main", - in: window, - launchOptions: launchOptions) -#endif - + // React Native starts in SceneDelegate. iOS 27 terminates apps built with + // its SDK at launch unless they adopt the UIScene lifecycle. return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -52,6 +46,51 @@ public class AppDelegate: ExpoAppDelegate { } } +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene, + let appDelegate = UIApplication.shared.delegate as? AppDelegate, + let factory = appDelegate.reactNativeFactory else { + return + } + let window = UIWindow(windowScene: windowScene) + self.window = window + appDelegate.window = window + + // Scenes deliver the launch URL (Open in OpenNotes, deep links) through + // connection options; Linking.getInitialURL reads it from launchOptions. + var launchOptions: [UIApplication.LaunchOptionsKey: Any] = [:] + if let url = connectionOptions.urlContexts.first?.url { + launchOptions[.url] = url + } + if let activity = connectionOptions.userActivities.first { + launchOptions[.userActivityDictionary] = [ + UIApplication.LaunchOptionsKey.userActivityType: activity.activityType, + "UIApplicationLaunchOptionsUserActivityKey": activity, + ] + } + factory.startReactNative(withModuleName: "main", in: window, launchOptions: launchOptions) + } + + // Linking API: URLs opened while the app is running. + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + for context in URLContexts { + _ = RCTLinkingManager.application(UIApplication.shared, open: context.url, options: [:]) + } + } + + // Universal Links + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + _ = RCTLinkingManager.application(UIApplication.shared, continue: userActivity, restorationHandler: { _ in }) + } +} + class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { // Extension point for config-plugins diff --git a/ios/OpenNotes/Info.plist b/ios/OpenNotes/Info.plist index db1dc29..d0a65c8 100644 --- a/ios/OpenNotes/Info.plist +++ b/ios/OpenNotes/Info.plist @@ -34,7 +34,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.3.5 + 1.3.6 CFBundleSignature ???? CFBundleURLTypes @@ -54,7 +54,7 @@ CFBundleVersion - 19 + 20 ITSAppUsesNonExemptEncryption LSMinimumSystemVersion @@ -80,6 +80,23 @@ RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName SplashScreen UIRequiredDeviceCapabilities