From 3aa67dddc39b1acd1fc01264616a93bc357612be Mon Sep 17 00:00:00 2001 From: Yogendra Shelke <25844542+YogendraShelke@users.noreply.github.com> Date: Mon, 7 Sep 2026 13:02:06 +0530 Subject: [PATCH 1/2] feat: migrate to scene delegate lifecycle --- ios/AppDelegate.swift | 77 +++---------- ios/Dev/AppDelegate.swift | 89 +++------------ .../LaunchApp/LaunchAppViewController.swift | 6 +- ios/Dev/SceneDelegate.swift | 98 +++++++++++++++++ ios/Extensions/AppDelegate+Extension.swift | 15 --- ios/Podfile | 3 + ios/Podfile.lock | 14 +-- ios/SceneDelegate.swift | 104 ++++++++++++++++++ ios/nativeTemplate.xcodeproj/project.pbxproj | 26 ++++- ios/nativeTemplate/Info-dev.plist | 17 +++ ios/nativeTemplate/Info.plist | 17 +++ package-lock.json | 5 +- package.json | 2 +- 13 files changed, 312 insertions(+), 161 deletions(-) create mode 100644 ios/Dev/SceneDelegate.swift create mode 100644 ios/SceneDelegate.swift diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index 108a1ae59..e9dec13f7 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -1,67 +1,26 @@ +import UIKit import MendixNative -@UIApplicationMain -class AppDelegate: ReactAppProvider { - - var shouldOpenInLastApp = false - var hasHandledLaunchAppWithOptions = false - - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - - SessionCookieStore.restore() // iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + @objc var window: UIWindow? { + get { SceneDelegate.delegateInstance()?.window } + set { SceneDelegate.delegateInstance()?.window = newValue } + } + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + SessionCookieStore.restore() MendixAppDelegate.application(application, didFinishLaunchingWithOptions: launchOptions) - setupApp(application: application, launchOptions: launchOptions) - changeRoot(to: UIViewController()) - - guard let url = Bundle.main.object(forInfoDictionaryKey: "Runtime url") as? String, !url.isEmpty else { - showUnrecoverableDialog( - title: "The runtime URL is missing", - message: "Missing the 'Runtime url' configuration within the Info.plist file. The app will close." - ) - return false - } - - if let bundleUrl = MendixAppDelegate.getJSBundleFile() { - - let runtimeUrl = AppUrl.forRuntime((url).replacingOccurrences(of: "\\", with: "")) - - let mendixApp = MendixApp( - identifier: nil, - bundleUrl: bundleUrl, - runtimeUrl: runtimeUrl, - warningsFilter: .none, - isDeveloperApp: false, - clearDataAtLaunch: false, - splashScreenPresenter: SplashScreenPresenter(), - reactLoading: nil, - enableThreeFingerGestures: false - ) - - ReactNative.shared.setup(mendixApp, launchOptions: launchOptions) - ReactNative.shared.start() - } else { - showUnrecoverableDialog( - title: "No Mendix bundle found", - message: "Missing the Mendix app bundle. Make sure that the index.ios.bundle file is available in ios/NativeTemplate/Bundle folder. If building locally consult the documentation on how to generate a bundle from your project." - ) - } - + clearKeychain() + setupUI() return true } - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - return MendixAppDelegate.application(app, openURL: url, options: options) - } - - func getWarningFilterValue() -> WarningsFilter { - return .none - } - + func applicationWillTerminate(_ application: UIApplication) { - SessionCookieStore.persist() // iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android - } - - func applicationDidEnterBackground(_ application: UIApplication) { - SessionCookieStore.persist() // iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android + SessionCookieStore.persist() } } diff --git a/ios/Dev/AppDelegate.swift b/ios/Dev/AppDelegate.swift index 6b7c6322f..e9dec13f7 100644 --- a/ios/Dev/AppDelegate.swift +++ b/ios/Dev/AppDelegate.swift @@ -1,79 +1,26 @@ -import Foundation +import UIKit import MendixNative -@UIApplicationMain -class AppDelegate: ReactAppProvider { - - var shouldOpenInLastApp = false - var hasHandledLaunchAppWithOptions = false - - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - - SessionCookieStore.restore() //iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + @objc var window: UIWindow? { + get { SceneDelegate.delegateInstance()?.window } + set { SceneDelegate.delegateInstance()?.window = newValue } + } + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + SessionCookieStore.restore() MendixAppDelegate.application(application, didFinishLaunchingWithOptions: launchOptions) - setupApp(application: application, launchOptions: launchOptions) - - IQKeyboardManager.shared().isEnabled = false - - let controller = UIStoryboard.launchApp.instantiateInitialViewController() ?? UIViewController() - changeRoot(to: controller) - window?.isUserInteractionEnabled = true - + clearKeychain() + setupUI() return true } - - func getWarningFilterValue() -> WarningsFilter { - #if DEBUG - return .all - #else - return AppPreferences.devModeEnabled ? .partial : .none - #endif - } - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - - let handled = MendixAppDelegate.application(app, openURL: url, options: options) - - let appUrl = AppPreferences.safeAppUrl - - if (!handled || appUrl.isEmpty || ReactAppProvider.isReactAppActive()) { - return false - } - - let bundleUrl = AppUrl.forBundle( - appUrl, - port: AppPreferences.remoteDebuggingPackagerPort, - isDebuggingRemotely: AppPreferences.remoteDebuggingEnabled, - isDevModeEnabled: AppPreferences.devModeEnabled - ) - - let launchOptions = NSMutableDictionary(dictionary: options) - launchOptions[UIApplication.LaunchOptionsKey.url] = url - launchOptions[UIApplication.OpenURLOptionsKey.annotation] = options[UIApplication.OpenURLOptionsKey.annotation] - - let mxApp = MendixApp.init( - identifier: nil, - bundleUrl: bundleUrl, - runtimeUrl: AppUrl.forRuntime(appUrl), - warningsFilter: getWarningFilterValue(), - isDeveloperApp: true, - clearDataAtLaunch: false, - splashScreenPresenter: nil, - reactLoading: nil, - enableThreeFingerGestures: false - ) - - ReactNative.shared.setup(mxApp, launchOptions: launchOptions as? [AnyHashable : Any]) - ReactNative.shared.start() - - return handled - } - + func applicationWillTerminate(_ application: UIApplication) { - SessionCookieStore.persist() //iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android - } - - func applicationDidEnterBackground(_ application: UIApplication) { - SessionCookieStore.persist() //iOS does not persist session cookies across app restarts, this helps persisting session cookies to match behaviour with Android + SessionCookieStore.persist() } } diff --git a/ios/Dev/LaunchApp/LaunchAppViewController.swift b/ios/Dev/LaunchApp/LaunchAppViewController.swift index 968ebc081..a768f1a10 100644 --- a/ios/Dev/LaunchApp/LaunchAppViewController.swift +++ b/ios/Dev/LaunchApp/LaunchAppViewController.swift @@ -108,9 +108,9 @@ class LaunchAppViewController: UIViewController, QRViewDelegate { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - let appDelegate = (UIApplication.shared.delegate as! AppDelegate); - if (appDelegate.shouldOpenInLastApp) { - appDelegate.shouldOpenInLastApp = false; + guard let sceneDelegate = SceneDelegate.delegateInstance() else { return } + if (sceneDelegate.shouldOpenInLastApp) { + sceneDelegate.shouldOpenInLastApp = false self.performSegue(withIdentifier: "MendixApp", sender: nil) } } diff --git a/ios/Dev/SceneDelegate.swift b/ios/Dev/SceneDelegate.swift new file mode 100644 index 000000000..eb86ee134 --- /dev/null +++ b/ios/Dev/SceneDelegate.swift @@ -0,0 +1,98 @@ +import UIKit +import MendixNative + +class SceneDelegate: ReactAppProvider { + + var shouldOpenInLastApp = false + var hasHandledLaunchAppWithOptions = false + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + + setUpProvider() + IQKeyboardManager.shared().isEnabled = false + + let controller = UIStoryboard.launchApp.instantiateInitialViewController() ?? UIViewController() + changeRoot(to: controller) + window?.isUserInteractionEnabled = true + + if let context = connectionOptions.urlContexts.first { + handle(url: context.url, options: openURLOptions(from: context)) + } + } + + func getWarningFilterValue() -> WarningsFilter { + #if DEBUG + return .all + #else + return AppPreferences.devModeEnabled ? .partial : .none + #endif + } + + @objc func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let context = URLContexts.first else { return } + handle(url: context.url, options: openURLOptions(from: context)) + } + + private func handle(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) { + let handled = MendixAppDelegate.application(UIApplication.shared, openURL: url, options: options) + + let appUrl = AppPreferences.safeAppUrl + + if (!handled || appUrl.isEmpty || ReactAppProvider.isReactAppActive()) { + return + } + + let bundleUrl = AppUrl.forBundle( + appUrl, + port: AppPreferences.remoteDebuggingPackagerPort, + isDebuggingRemotely: AppPreferences.remoteDebuggingEnabled, + isDevModeEnabled: AppPreferences.devModeEnabled + ) + + var launchOptions: [AnyHashable: Any] = options + launchOptions[UIApplication.LaunchOptionsKey.url] = url + launchOptions[UIApplication.LaunchOptionsKey.annotation] = options[UIApplication.OpenURLOptionsKey.annotation] ?? [] + + let mxApp = MendixApp( + identifier: nil, + bundleUrl: bundleUrl, + runtimeUrl: AppUrl.forRuntime(appUrl), + warningsFilter: getWarningFilterValue(), + isDeveloperApp: true, + clearDataAtLaunch: false, + splashScreenPresenter: nil, + reactLoading: nil, + enableThreeFingerGestures: false + ) + + ReactNative.shared.setup(mxApp, launchOptions: launchOptions) + ReactNative.shared.start() + } + + @objc func sceneDidEnterBackground(_ scene: UIScene) { + SessionCookieStore.persist() + } + + static func delegateInstance() -> SceneDelegate? { + if let provider = ReactAppProvider.shared() as? SceneDelegate { + return provider + } + return UIApplication.shared.connectedScenes.compactMap { $0.delegate as? SceneDelegate }.first + } + + private func openURLOptions(from context: UIOpenURLContext) -> [UIApplication.OpenURLOptionsKey: Any] { + var options: [UIApplication.OpenURLOptionsKey: Any] = [.openInPlace: context.options.openInPlace] + if let sourceApplication = context.options.sourceApplication { + options[.sourceApplication] = sourceApplication + } + if let annotation = context.options.annotation { + options[.annotation] = annotation + } + return options + } +} diff --git a/ios/Extensions/AppDelegate+Extension.swift b/ios/Extensions/AppDelegate+Extension.swift index f6e90065d..9ed8542b6 100644 --- a/ios/Extensions/AppDelegate+Extension.swift +++ b/ios/Extensions/AppDelegate+Extension.swift @@ -14,19 +14,4 @@ extension AppDelegate { func setupUI() { UIDatePicker.appearance().preferredDatePickerStyle = .wheels } - - func showUnrecoverableDialog(title: String, message: String) { - let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) - controller.addAction(.init(title: "Close", style: .default, handler: {_ in - print(message) - exit(0) - })) - window?.rootViewController?.present(controller, animated: true, completion: nil) - } - - func setupApp(application: UIApplication, launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) { - setUpProvider() - clearKeychain() - setupUI() - } } diff --git a/ios/Podfile b/ios/Podfile index a4e1f3fca..734b66744 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -112,6 +112,9 @@ post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| + + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + if config.name == "ReleaseDevApp" config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [] config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] |= ['$(inherited)', 'NDEBUG=1', 'REACT_NATIVE_DEBUG=1'] diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4a5aacc40..86e676e25 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -21,7 +21,7 @@ PODS: - libwebp/sharpyuv (1.5.0) - libwebp/webp (1.5.0): - libwebp/sharpyuv - - MendixNative (0.5.1): + - MendixNative (0.5.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2902,12 +2902,12 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da - hermes-engine: 67c41b335723f72b2a182dbaeeebd97a73fc2775 + hermes-engine: 69685c8bd68491a4ff7f075256f5b2dfab04de21 IQKeyboardManager: c8665b3396bd0b79402b4c573eac345a31c7d485 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - MendixNative: b0ea153b893ce40b90016f397e9a5acfe4444c33 + MendixNative: b789c95cb7e776e332a0dabd4f39b91e20c1f31d op-sqlite: e9ef65bcf95a97863874cee87841425bb71c8396 OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2 RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7 @@ -2918,7 +2918,7 @@ SPEC CHECKSUMS: React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f - React-Core-prebuilt: 24a3f8fa32b407405d9e1085d3b09014f79c0870 + React-Core-prebuilt: 203358738dba539388b07a09a926858081125bc1 React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42 React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc @@ -2988,7 +2988,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: e96e93b493d8d86eeaee3e590ba0be53f6abe46f ReactCodegen: 797de5178718324c6eba3327b07f9a423fbd5787 ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e - ReactNativeDependencies: 061101f5e362f1f5d0687dd5b92432f7b76f4317 + ReactNativeDependencies: 770e0777d2bd1891fd654bdbb3da9e9905a96394 RNBootSplash: 13b7612788960d0c5446e68489ac2509e506cfcd RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca @@ -3011,6 +3011,6 @@ SPEC CHECKSUMS: SwiftAudioEx: f6aa653770f3a0d3851edaf8d834a30aee4a7646 Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2 -PODFILE CHECKSUM: 7a33012cce520784bb5b691549f5ed2af273b7e3 +PODFILE CHECKSUM: 8d31f00ca87f4bcff1bd77f07e1f39545e895320 -COCOAPODS: 1.17.0 +COCOAPODS: 1.16.2 diff --git a/ios/SceneDelegate.swift b/ios/SceneDelegate.swift new file mode 100644 index 000000000..a2dae6a57 --- /dev/null +++ b/ios/SceneDelegate.swift @@ -0,0 +1,104 @@ +import UIKit +import MendixNative + +class SceneDelegate: ReactAppProvider { + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + + setUpProvider() + changeRoot(to: UIViewController()) + + let launchOptions = connectionOptions.urlContexts.first.map { context in + let options = openURLOptions(from: context) + MendixAppDelegate.application(UIApplication.shared, openURL: context.url, options: options) + return reactLaunchOptions(url: context.url, options: options) + } + + guard let url = Bundle.main.object(forInfoDictionaryKey: "Runtime url") as? String, !url.isEmpty else { + showUnrecoverableDialog( + title: "The runtime URL is missing", + message: "Missing the 'Runtime url' configuration within the Info.plist file. The app will close." + ) + return + } + + if let bundleUrl = MendixAppDelegate.getJSBundleFile() { + let runtimeUrl = AppUrl.forRuntime((url).replacingOccurrences(of: "\\", with: "")) + + let mendixApp = MendixApp( + identifier: nil, + bundleUrl: bundleUrl, + runtimeUrl: runtimeUrl, + warningsFilter: .none, + isDeveloperApp: false, + clearDataAtLaunch: false, + splashScreenPresenter: SplashScreenPresenter(), + reactLoading: nil, + enableThreeFingerGestures: false + ) + + ReactNative.shared.setup(mendixApp, launchOptions: launchOptions) + ReactNative.shared.start() + } else { + showUnrecoverableDialog( + title: "No Mendix bundle found", + message: "Missing the Mendix app bundle. Make sure that the index.ios.bundle file is available in ios/NativeTemplate/Bundle folder. If building locally consult the documentation on how to generate a bundle from your project." + ) + } + } + + @objc func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let context = URLContexts.first else { return } + MendixAppDelegate.application( + UIApplication.shared, + openURL: context.url, + options: openURLOptions(from: context) + ) + } + + @objc func sceneDidEnterBackground(_ scene: UIScene) { + SessionCookieStore.persist() + } + + static func delegateInstance() -> SceneDelegate? { + if let provider = ReactAppProvider.shared() as? SceneDelegate { + return provider + } + return UIApplication.shared.connectedScenes.compactMap { $0.delegate as? SceneDelegate }.first + } + + private func openURLOptions(from context: UIOpenURLContext) -> [UIApplication.OpenURLOptionsKey: Any] { + var options: [UIApplication.OpenURLOptionsKey: Any] = [.openInPlace: context.options.openInPlace] + if let sourceApplication = context.options.sourceApplication { + options[.sourceApplication] = sourceApplication + } + if let annotation = context.options.annotation { + options[.annotation] = annotation + } + return options + } + + private func reactLaunchOptions( + url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] + ) -> [AnyHashable: Any] { + var launchOptions: [AnyHashable: Any] = options + launchOptions[UIApplication.LaunchOptionsKey.url] = url + launchOptions[UIApplication.LaunchOptionsKey.annotation] = options[UIApplication.OpenURLOptionsKey.annotation] ?? [] + return launchOptions + } + + private func showUnrecoverableDialog(title: String, message: String) { + let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) + controller.addAction(.init(title: "Close", style: .default, handler: {_ in + print(message) + exit(0) + })) + window?.rootViewController?.present(controller, animated: true, completion: nil) + } +} diff --git a/ios/nativeTemplate.xcodeproj/project.pbxproj b/ios/nativeTemplate.xcodeproj/project.pbxproj index 157a1034b..91277e805 100644 --- a/ios/nativeTemplate.xcodeproj/project.pbxproj +++ b/ios/nativeTemplate.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ 572D87C4D9F46E7564D1FE09 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = DBB22F41E3AF5D95F9759402 /* PrivacyInfo.xcprivacy */; }; 6E2A5EF72EE2F17800FB5359 /* Info-dev.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A5EF62EE2F17800FB5359 /* Info-dev.plist */; }; 6E2A5EF82EE2F17800FB5359 /* Info-dev.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6E2A5EF62EE2F17800FB5359 /* Info-dev.plist */; }; + 6E2A5F012EE3A00000FB5359 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2A5F002EE3A00000FB5359 /* SceneDelegate.swift */; }; + 6E2A5F032EE3A00000FB5359 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2A5F022EE3A00000FB5359 /* SceneDelegate.swift */; }; 9DF4091425A46A56E2A9A405 /* libPods-nativeTemplate.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 73F36AC2CCD37CCD4E7B1BB8 /* libPods-nativeTemplate.a */; }; 9E9B12F82EAB8BA500714B41 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9B12F62EAB8BA500714B41 /* AppDelegate.swift */; }; 9E9B12FA2EAB8C2800714B41 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9B12F92EAB8C2800714B41 /* AppDelegate.swift */; }; @@ -71,6 +73,8 @@ 64F8A8A81DAAF68662AD5926 /* Pods-nativeTemplate.releasedevapp.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nativeTemplate.releasedevapp.xcconfig"; path = "Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate.releasedevapp.xcconfig"; sourceTree = ""; }; 6ABE4BDB54D431090249D8EC /* Pods-dev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.release.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.release.xcconfig"; sourceTree = ""; }; 6E2A5EF62EE2F17800FB5359 /* Info-dev.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-dev.plist"; path = "nativeTemplate/Info-dev.plist"; sourceTree = ""; }; + 6E2A5F002EE3A00000FB5359 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 6E2A5F022EE3A00000FB5359 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 73F36AC2CCD37CCD4E7B1BB8 /* libPods-nativeTemplate.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-nativeTemplate.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7EEDF4024CB6865C31DBA5BB /* Pods-nativeTemplate.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nativeTemplate.release.xcconfig"; path = "Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate.release.xcconfig"; sourceTree = ""; }; 9A27E05F0AD54B417B79E303 /* Pods-dev.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.debug.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.debug.xcconfig"; sourceTree = ""; }; @@ -93,8 +97,6 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 9E9B12FB2EAB8C9300714B41 /* Extensions */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Extensions; sourceTree = ""; }; @@ -134,6 +136,7 @@ 2C887FD924B88A0E0003DC53 /* LaunchScreen.storyboard */, 2C005A7823D5B1E200003E29 /* nativeTemplate.entitlements */, 9E9B12F62EAB8BA500714B41 /* AppDelegate.swift */, + 6E2A5F002EE3A00000FB5359 /* SceneDelegate.swift */, 9E9B12FF2EAB8D2600714B41 /* MendixAppDelegate.swift */, 9E9B13022EAB8EB700714B41 /* SplashScreenPresenter.swift */, ); @@ -147,6 +150,7 @@ 1A0B3AD12397E14800388BF7 /* MendixApp */, 1A0B3ACD2397E06900388BF7 /* LaunchApp */, 9E9B12F92EAB8C2800714B41 /* AppDelegate.swift */, + 6E2A5F022EE3A00000FB5359 /* SceneDelegate.swift */, ); path = Dev; sourceTree = ""; @@ -396,10 +400,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks.sh\"\n"; @@ -413,10 +421,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks.sh\"\n"; @@ -452,10 +464,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources.sh\"\n"; @@ -491,10 +507,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources.sh\"\n"; @@ -508,6 +528,7 @@ buildActionMask = 2147483647; files = ( 9E9B12F82EAB8BA500714B41 /* AppDelegate.swift in Sources */, + 6E2A5F012EE3A00000FB5359 /* SceneDelegate.swift in Sources */, 9E9B13002EAB8D2600714B41 /* MendixAppDelegate.swift in Sources */, 9E9B13042EAB8EB700714B41 /* SplashScreenPresenter.swift in Sources */, ); @@ -518,6 +539,7 @@ buildActionMask = 2147483647; files = ( 9E9B12FA2EAB8C2800714B41 /* AppDelegate.swift in Sources */, + 6E2A5F032EE3A00000FB5359 /* SceneDelegate.swift in Sources */, 9E9B13012EAB8D2600714B41 /* MendixAppDelegate.swift in Sources */, 1A0B3AD52397E16900388BF7 /* MendixAppViewController.swift in Sources */, 9E9B13032EAB8EB700714B41 /* SplashScreenPresenter.swift in Sources */, diff --git a/ios/nativeTemplate/Info-dev.plist b/ios/nativeTemplate/Info-dev.plist index 015ca46c7..7a25ba3ba 100644 --- a/ios/nativeTemplate/Info-dev.plist +++ b/ios/nativeTemplate/Info-dev.plist @@ -86,6 +86,23 @@ SimpleLineIcons.ttf Zocial.ttf + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/ios/nativeTemplate/Info.plist b/ios/nativeTemplate/Info.plist index 4a26d980b..c86c42717 100644 --- a/ios/nativeTemplate/Info.plist +++ b/ios/nativeTemplate/Info.plist @@ -86,6 +86,23 @@ SimpleLineIcons.ttf Zocial.ttf + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/package-lock.json b/package-lock.json index ac1941b9c..8f3f122c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@react-native-vector-icons/material-icons": "12.4.1", "@react-navigation/native": "7.2.4", "@shopify/flash-list": "2.3.1", - "mendix-native": "https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz", + "mendix-native": "github:mendix/mendix-native#moo/MOO-2398-scene-delegate-migration", "react-native": "0.84.1", "react-native-blob-util": "0.24.7", "react-native-bootsplash": "6.3.12", @@ -6274,8 +6274,7 @@ }, "node_modules/mendix-native": { "version": "0.5.2", - "resolved": "https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz", - "integrity": "sha512-CqGEqSy+c+zoYHmphVg5CXR9JdAr7wZkkjgPgO41Pfar9KESIKcE7frfk+MHlpnhZH254xuH19XkIBQGpuFNeA==", + "resolved": "git+ssh://git@github.com/mendix/mendix-native.git#07cf015be2a1e4bf84d39c05fc2563a883aaf9d1", "license": "MIT", "workspaces": [ "example" diff --git a/package.json b/package.json index e6a24b6fb..f0fbf1278 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dependencies": { "@d11/react-native-fast-image": "8.13.0", "@gorhom/bottom-sheet": "5.2.14", - "mendix-native": "https://github.com/mendix/mendix-native/releases/download/v0.5.2/mendix-native-v0.5.2.tgz", + "mendix-native": "github:mendix/mendix-native#moo/MOO-2398-scene-delegate-migration", "@op-engineering/op-sqlite": "15.2.5", "@shopify/flash-list": "2.3.1", "@react-native-async-storage/async-storage": "2.2.0", From a6f5a5190bdcab33a22ea66eef451d2eedace3f9 Mon Sep 17 00:00:00 2001 From: Yogendra Shelke <25844542+YogendraShelke@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:31:17 +0530 Subject: [PATCH 2/2] fix: respect launch options and improve URL handling --- ios/Dev/SceneDelegate.swift | 33 +++++--------------- ios/Podfile | 6 +++- ios/SceneDelegate.swift | 20 +++--------- ios/nativeTemplate.xcodeproj/project.pbxproj | 18 ++--------- package-lock.json | 2 +- 5 files changed, 20 insertions(+), 59 deletions(-) diff --git a/ios/Dev/SceneDelegate.swift b/ios/Dev/SceneDelegate.swift index eb86ee134..8421332b5 100644 --- a/ios/Dev/SceneDelegate.swift +++ b/ios/Dev/SceneDelegate.swift @@ -20,8 +20,8 @@ class SceneDelegate: ReactAppProvider { changeRoot(to: controller) window?.isUserInteractionEnabled = true - if let context = connectionOptions.urlContexts.first { - handle(url: context.url, options: openURLOptions(from: context)) + if !connectionOptions.urlContexts.isEmpty { + launchMendixApp(with: ReactAppProvider.launchOptions(from: connectionOptions)) } } @@ -33,19 +33,16 @@ class SceneDelegate: ReactAppProvider { #endif } - @objc func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - guard let context = URLContexts.first else { return } - handle(url: context.url, options: openURLOptions(from: context)) + override func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + handleURLContexts(URLContexts) { [weak self] launchOptions in + self?.launchMendixApp(with: launchOptions) + } } - private func handle(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) { - let handled = MendixAppDelegate.application(UIApplication.shared, openURL: url, options: options) - + private func launchMendixApp(with launchOptions: [AnyHashable: Any]) { let appUrl = AppPreferences.safeAppUrl - if (!handled || appUrl.isEmpty || ReactAppProvider.isReactAppActive()) { - return - } + guard !appUrl.isEmpty else { return } let bundleUrl = AppUrl.forBundle( appUrl, @@ -54,10 +51,6 @@ class SceneDelegate: ReactAppProvider { isDevModeEnabled: AppPreferences.devModeEnabled ) - var launchOptions: [AnyHashable: Any] = options - launchOptions[UIApplication.LaunchOptionsKey.url] = url - launchOptions[UIApplication.LaunchOptionsKey.annotation] = options[UIApplication.OpenURLOptionsKey.annotation] ?? [] - let mxApp = MendixApp( identifier: nil, bundleUrl: bundleUrl, @@ -85,14 +78,4 @@ class SceneDelegate: ReactAppProvider { return UIApplication.shared.connectedScenes.compactMap { $0.delegate as? SceneDelegate }.first } - private func openURLOptions(from context: UIOpenURLContext) -> [UIApplication.OpenURLOptionsKey: Any] { - var options: [UIApplication.OpenURLOptionsKey: Any] = [.openInPlace: context.options.openInPlace] - if let sourceApplication = context.options.sourceApplication { - options[.sourceApplication] = sourceApplication - } - if let annotation = context.options.annotation { - options[.annotation] = annotation - } - return options - } } diff --git a/ios/Podfile b/ios/Podfile index 734b66744..30b56d1bf 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -113,7 +113,11 @@ post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + minimum_ios_version = Gem::Version.new(deployment_target) + current_deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] + if current_deployment_target && Gem::Version.new(current_deployment_target) < minimum_ios_version + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = deployment_target + end if config.name == "ReleaseDevApp" config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [] diff --git a/ios/SceneDelegate.swift b/ios/SceneDelegate.swift index a2dae6a57..14e57d520 100644 --- a/ios/SceneDelegate.swift +++ b/ios/SceneDelegate.swift @@ -13,11 +13,9 @@ class SceneDelegate: ReactAppProvider { setUpProvider() changeRoot(to: UIViewController()) - let launchOptions = connectionOptions.urlContexts.first.map { context in - let options = openURLOptions(from: context) - MendixAppDelegate.application(UIApplication.shared, openURL: context.url, options: options) - return reactLaunchOptions(url: context.url, options: options) - } + let launchOptions = connectionOptions.urlContexts.isEmpty + ? nil + : ReactAppProvider.launchOptions(from: connectionOptions) guard let url = Bundle.main.object(forInfoDictionaryKey: "Runtime url") as? String, !url.isEmpty else { showUnrecoverableDialog( @@ -52,7 +50,7 @@ class SceneDelegate: ReactAppProvider { } } - @objc func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + override func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { guard let context = URLContexts.first else { return } MendixAppDelegate.application( UIApplication.shared, @@ -83,16 +81,6 @@ class SceneDelegate: ReactAppProvider { return options } - private func reactLaunchOptions( - url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] - ) -> [AnyHashable: Any] { - var launchOptions: [AnyHashable: Any] = options - launchOptions[UIApplication.LaunchOptionsKey.url] = url - launchOptions[UIApplication.LaunchOptionsKey.annotation] = options[UIApplication.OpenURLOptionsKey.annotation] ?? [] - return launchOptions - } - private func showUnrecoverableDialog(title: String, message: String) { let controller = UIAlertController(title: title, message: message, preferredStyle: .alert) controller.addAction(.init(title: "Close", style: .default, handler: {_ in diff --git a/ios/nativeTemplate.xcodeproj/project.pbxproj b/ios/nativeTemplate.xcodeproj/project.pbxproj index 91277e805..d5e0e2aa7 100644 --- a/ios/nativeTemplate.xcodeproj/project.pbxproj +++ b/ios/nativeTemplate.xcodeproj/project.pbxproj @@ -97,6 +97,8 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 9E9B12FB2EAB8C9300714B41 /* Extensions */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); path = Extensions; sourceTree = ""; }; @@ -400,14 +402,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-frameworks.sh\"\n"; @@ -421,14 +419,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks.sh\"\n"; @@ -464,14 +458,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-resources.sh\"\n"; @@ -507,14 +497,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-nativeTemplate/Pods-nativeTemplate-resources.sh\"\n"; diff --git a/package-lock.json b/package-lock.json index 8f3f122c9..d18c81faa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6274,7 +6274,7 @@ }, "node_modules/mendix-native": { "version": "0.5.2", - "resolved": "git+ssh://git@github.com/mendix/mendix-native.git#07cf015be2a1e4bf84d39c05fc2563a883aaf9d1", + "resolved": "git+ssh://git@github.com/mendix/mendix-native.git#0141d71066f41ce4120551dab29563a8bf55ca35", "license": "MIT", "workspaces": [ "example"