diff --git a/CHANGELOG.md b/CHANGELOG.md index cde843b..1d04fd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- We migrated iOS to the UIKit scene-based life cycle. + ## [v0.5.2] - 2026-07-23 - We addressed a scenario where, in iOS, an Auth dialogue could continuously popup on app startup due to access of either large keychain blob items or blocked items. diff --git a/example/ios/MendixNativeExample.xcodeproj/project.pbxproj b/example/ios/MendixNativeExample.xcodeproj/project.pbxproj index 8d0597b..59a47e5 100644 --- a/example/ios/MendixNativeExample.xcodeproj/project.pbxproj +++ b/example/ios/MendixNativeExample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 1499ABBDB7935B67C3951198 /* libPods-MendixNativeExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 24A5FB93E78C6A91516539F4 /* libPods-MendixNativeExample.a */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 761780EF2CA45674006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EE2CA45674006654EE /* SceneDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; E7824D7967CD7EE7C8E76686 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -22,6 +23,7 @@ 24A5FB93E78C6A91516539F4 /* libPods-MendixNativeExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MendixNativeExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 503B3921D3702B3C7DF3D941 /* Pods-MendixNativeExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MendixNativeExample.release.xcconfig"; path = "Target Support Files/Pods-MendixNativeExample/Pods-MendixNativeExample.release.xcconfig"; sourceTree = ""; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = MendixNativeExample/AppDelegate.swift; sourceTree = ""; }; + 761780EE2CA45674006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = MendixNativeExample/SceneDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MendixNativeExample/LaunchScreen.storyboard; sourceTree = ""; }; 9EF6D8952E93D80B00BFE8AE /* MendixNativeExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MendixNativeExample-Bridging-Header.h"; sourceTree = ""; }; DC6440C7A3C29FAC4633A591 /* Pods-MendixNativeExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MendixNativeExample.debug.xcconfig"; path = "Target Support Files/Pods-MendixNativeExample/Pods-MendixNativeExample.debug.xcconfig"; sourceTree = ""; }; @@ -49,6 +51,7 @@ 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, 9EF6D8952E93D80B00BFE8AE /* MendixNativeExample-Bridging-Header.h */, + 761780EE2CA45674006654EE /* SceneDelegate.swift */, ); name = MendixNativeExample; sourceTree = ""; @@ -249,6 +252,7 @@ buildActionMask = 2147483647; files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 761780EF2CA45674006654EE /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/example/ios/MendixNativeExample/AppDelegate.swift b/example/ios/MendixNativeExample/AppDelegate.swift index d86af0f..6ae8776 100644 --- a/example/ios/MendixNativeExample/AppDelegate.swift +++ b/example/ios/MendixNativeExample/AppDelegate.swift @@ -1,41 +1,9 @@ import UIKit -import React -import MendixNative @main -class AppDelegate: ReactAppProvider { +class AppDelegate: UIResponder, UIApplicationDelegate { - override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - setUpProvider() - guard let bundleUrl = bundleURL() else { - let message = "No script URL provided. Make sure the metro packager is running or you have embedded a JS bundle in your application bundle." - fatalError(message) - } - let mendixApp = MendixApp.init( - identifier: nil, - bundleUrl: bundleUrl, - runtimeUrl: URL(string: "http://localhost:8081")!, - warningsFilter: .none, - isDeveloperApp: true, - clearDataAtLaunch: false, - splashScreenPresenter: nil, - reactLoading: nil, - enableThreeFingerGestures: false - ) - - AppPreferences.devModeEnabled = true - AppPreferences.remoteDebuggingEnabled = true - - ReactNative.shared.setup(mendixApp, launchOptions: nil) - ReactNative.shared.start() - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - func bundleURL() -> URL? { - #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") - #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") - #endif + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + return true } } diff --git a/example/ios/MendixNativeExample/Info.plist b/example/ios/MendixNativeExample/Info.plist index d934e25..d7851ed 100644 --- a/example/ios/MendixNativeExample/Info.plist +++ b/example/ios/MendixNativeExample/Info.plist @@ -35,6 +35,23 @@ RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/example/ios/MendixNativeExample/SceneDelegate.swift b/example/ios/MendixNativeExample/SceneDelegate.swift new file mode 100644 index 0000000..23562ab --- /dev/null +++ b/example/ios/MendixNativeExample/SceneDelegate.swift @@ -0,0 +1,39 @@ +import UIKit +import React +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() + guard let bundleUrl = bundleURL() else { + let message = "No script URL provided. Make sure the metro packager is running or you have embedded a JS bundle in your application bundle." + fatalError(message) + } + let mendixApp = MendixApp.init( + identifier: nil, + bundleUrl: bundleUrl, + runtimeUrl: URL(string: "http://localhost:8081")!, + warningsFilter: .none, + isDeveloperApp: true, + clearDataAtLaunch: false, + splashScreenPresenter: nil, + reactLoading: nil, + enableThreeFingerGestures: false + ) + AppPreferences.devModeEnabled = true + AppPreferences.remoteDebuggingEnabled = true + ReactNative.shared.setup(mendixApp) + ReactNative.shared.start() + } + + func bundleURL() -> URL? { + #if DEBUG + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") + #else + Bundle.main.url(forResource: "main", withExtension: "jsbundle") + #endif + } +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index af703dc..12ad051 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -3,7 +3,7 @@ PODS: - hermes-engine (250829098.0.9): - hermes-engine/Pre-built (= 250829098.0.9) - hermes-engine/Pre-built (250829098.0.9) - - MendixNative (0.4.1): + - MendixNative (0.5.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2126,7 +2126,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da hermes-engine: a7179a4cd45fa3f8143712e52bd3c2d20b5274a0 - MendixNative: 0014d648c1ad67c7da144e99603ad636b017b424 + MendixNative: b789c95cb7e776e332a0dabd4f39b91e20c1f31d op-sqlite: e9ef65bcf95a97863874cee87841425bb71c8396 OpenSSL-Universal: 9110d21982bb7e8b22a962b6db56a8aa805afde7 RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7 diff --git a/ios/Modules/Helper/ReactAppProvider.swift b/ios/Modules/Helper/ReactAppProvider.swift index 9b73e91..1ee03bb 100644 --- a/ios/Modules/Helper/ReactAppProvider.swift +++ b/ios/Modules/Helper/ReactAppProvider.swift @@ -4,7 +4,7 @@ import React_RCTAppDelegate import ReactAppDependencyProvider @objcMembers -open class ReactAppProvider: UIResponder, UIApplicationDelegate { +open class ReactAppProvider: UIResponder, UIWindowSceneDelegate { public static let defaultName = "App" @@ -15,26 +15,50 @@ open class ReactAppProvider: UIResponder, UIApplicationDelegate { var reactRootViewName: String = defaultName + private static weak var currentProvider: ReactAppProvider? + private var reactRootViewController: UIViewController? + + public var hasStartedReact: Bool { + return reactRootViewController != nil + } + public func setUpProvider( moduleName: String = ReactAppProvider.defaultName, reactRootViewName: String = ReactAppProvider.defaultName ) { self.moduleName = moduleName self.reactRootViewName = reactRootViewName + guard reactNativeFactory == nil else { + return + } let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider() reactNativeDelegate = delegate reactNativeFactory = factory - window = UIWindow(frame: UIScreen.main.bounds) } - - open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - return true + + open func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = scene as? UIWindowScene else { + return + } + ReactAppProvider.currentProvider = self + window = UIWindow(windowScene: windowScene) + + if let reactRootViewController = reactRootViewController { + changeRoot(to: reactRootViewController) + } + } + + open func sceneDidDisconnect(_ scene: UIScene) { + if ReactAppProvider.currentProvider === self { + ReactAppProvider.currentProvider = nil + } } public func setReactViewController(_ controller: UIViewController) { controller.view = reactAppView() + reactRootViewController = controller changeRoot(to: controller) } @@ -43,7 +67,7 @@ open class ReactAppProvider: UIResponder, UIApplicationDelegate { return nil } view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - view.frame = window?.rootViewController?.view.frame ?? .zero + view.frame = window?.bounds ?? .zero return view } @@ -55,7 +79,7 @@ open class ReactAppProvider: UIResponder, UIApplicationDelegate { } public static func shared() -> ReactAppProvider? { - return UIApplication.shared.delegate as? ReactAppProvider + return currentProvider } public func changeRoot(to controller: UIViewController) { diff --git a/ios/Modules/Helper/ReactHostHelper.mm b/ios/Modules/Helper/ReactHostHelper.mm index f9ee17f..8eb159e 100644 --- a/ios/Modules/Helper/ReactHostHelper.mm +++ b/ios/Modules/Helper/ReactHostHelper.mm @@ -37,7 +37,7 @@ - (nullable id) getModuleForClass: (Class) clazz { } - (RCTHost *) currentHost { - ReactAppProvider *reactAppProvider = (ReactAppProvider *) [[UIApplication sharedApplication] delegate]; + ReactAppProvider *reactAppProvider = [ReactAppProvider shared]; RCTReactNativeFactory *reactNativeFactory = [reactAppProvider reactNativeFactory]; RCTRootViewFactory *rootViewFactory = [reactNativeFactory rootViewFactory]; RCTHost *reactHost = [rootViewFactory reactHost];