From 1d9be323e0b14e00beabdba0e1ee8dd4bcd530e4 Mon Sep 17 00:00:00 2001 From: Ankush523 Date: Tue, 6 Oct 2026 10:55:17 -0400 Subject: [PATCH 1/5] fix(bindings): run Offline Demo on iOS 26+ with UIScene lifecycle --- examples/demo-app/Gemfile | 1 + .../ios/OfflineDemo.xcodeproj/project.pbxproj | 4 +++ .../ios/OfflineDemo/AppDelegate.swift | 33 +++++++++++++------ examples/demo-app/ios/OfflineDemo/Info.plist | 17 ++++++++++ .../ios/OfflineDemo/SceneDelegate.swift | 29 ++++++++++++++++ examples/demo-app/ios/Podfile | 2 ++ examples/demo-app/ios/Podfile.lock | 2 +- 7 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 examples/demo-app/ios/OfflineDemo/SceneDelegate.swift diff --git a/examples/demo-app/Gemfile b/examples/demo-app/Gemfile index 6a4c5f171..97d43c694 100644 --- a/examples/demo-app/Gemfile +++ b/examples/demo-app/Gemfile @@ -14,3 +14,4 @@ gem 'bigdecimal' gem 'logger' gem 'benchmark' gem 'mutex_m' +gem 'nkf' # provides kconv, needed by CFPropertyList on Ruby 3.4+ diff --git a/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj b/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj index 2d3b2a499..c6fdcd197 100644 --- a/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj +++ b/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + 8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */; }; A6527776531B6CBC53134632 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -24,6 +25,7 @@ 5DCACB8F33CDC322A6C60F78 /* libPods-OfflineDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OfflineDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OfflineDemo/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OfflineDemo/LaunchScreen.storyboard; sourceTree = ""; }; + 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OfflineDemo/SceneDelegate.swift; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -44,6 +46,7 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +250,7 @@ buildActionMask = 2147483647; files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift index 794a886e8..2ad8f22e5 100644 --- a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift @@ -5,8 +5,9 @@ import ReactAppDependencyProvider @main class AppDelegate: UIResponder, UIApplicationDelegate { + /// React Native (`RCTDeviceInfo`, orientation notifications) still reads + /// `UIApplication.shared.delegate?.window` even when using UIScene lifecycle. var window: UIWindow? - var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? @@ -21,15 +22,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory - window = UIWindow(frame: UIScreen.main.bounds) + return true + } - factory.startReactNative( - withModuleName: "OfflineDemo", - in: window, - launchOptions: launchOptions + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role ) - - return true + configuration.delegateClass = SceneDelegate.self + return configuration } } @@ -40,9 +46,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func bundleURL() -> URL? { #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#if targetEnvironment(simulator) + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") { + return embedded + } + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#endif #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } } diff --git a/examples/demo-app/ios/OfflineDemo/Info.plist b/examples/demo-app/ios/OfflineDemo/Info.plist index 7df110e78..be01909f9 100644 --- a/examples/demo-app/ios/OfflineDemo/Info.plist +++ b/examples/demo-app/ios/OfflineDemo/Info.plist @@ -41,6 +41,23 @@ Location is used for Bluetooth peer discovery RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UIBackgroundModes bluetooth-central diff --git a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift new file mode 100644 index 000000000..435b24ee2 --- /dev/null +++ b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift @@ -0,0 +1,29 @@ +import UIKit +import React + +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 + + factory.startReactNative( + withModuleName: "OfflineDemo", + in: window, + launchOptions: nil + ) + } +} diff --git a/examples/demo-app/ios/Podfile b/examples/demo-app/ios/Podfile index 1993dd209..4af817f71 100644 --- a/examples/demo-app/ios/Podfile +++ b/examples/demo-app/ios/Podfile @@ -53,6 +53,8 @@ target 'OfflineDemo' do installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| + # MeshSdk.podspec still declares 13.0; current Xcode only accepts 15.0+. + build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' build_config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES' other_cflags = build_config.build_settings['OTHER_CFLAGS'] || '$(inherited)' other_cxxflags = build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] || '$(inherited)' diff --git a/examples/demo-app/ios/Podfile.lock b/examples/demo-app/ios/Podfile.lock index 0055905be..a3e2899da 100644 --- a/examples/demo-app/ios/Podfile.lock +++ b/examples/demo-app/ios/Podfile.lock @@ -2727,6 +2727,6 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb -PODFILE CHECKSUM: 0cad87f465583f2697b61325dad22f801c58afe9 +PODFILE CHECKSUM: 98555054fbebb78edd782446f9f6ca214c995483 COCOAPODS: 1.16.2 From 18f49fbdcad5eeaff1f850b5109167e5b7d9dcdb Mon Sep 17 00:00:00 2001 From: Ankush523 Date: Tue, 6 Oct 2026 11:12:50 -0400 Subject: [PATCH 2/5] fix(bindings): UIScene lifecycle and launchOptions for RN examples --- examples/demo-app/.gitignore | 1 + examples/demo-app/README.md | 17 ++++++++-- .../ios/OfflineDemo/AppDelegate.swift | 3 ++ .../ios/OfflineDemo/SceneDelegate.swift | 3 +- .../ios/MeshWiki.xcodeproj/project.pbxproj | 4 +++ .../mesh-wiki/ios/MeshWiki/AppDelegate.swift | 33 +++++++++++++------ examples/mesh-wiki/ios/MeshWiki/Info.plist | 17 ++++++++++ .../ios/MeshWiki/SceneDelegate.swift | 30 +++++++++++++++++ examples/mesh-wiki/ios/Podfile | 6 ++++ .../NostrExample.xcodeproj/project.pbxproj | 4 +++ .../ios/NostrExample/AppDelegate.swift | 33 +++++++++++++------ .../nostr-example/ios/NostrExample/Info.plist | 17 ++++++++++ .../ios/NostrExample/SceneDelegate.swift | 30 +++++++++++++++++ examples/nostr-example/ios/Podfile | 1 + examples/react-native-app/Gemfile | 1 + .../project.pbxproj | 4 +++ .../OfflineProtocolExample/AppDelegate.swift | 33 +++++++++++++------ .../ios/OfflineProtocolExample/Info.plist | 17 ++++++++++ .../SceneDelegate.swift | 30 +++++++++++++++++ examples/react-native-app/ios/Podfile | 1 + 20 files changed, 252 insertions(+), 33 deletions(-) create mode 100644 examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift create mode 100644 examples/nostr-example/ios/NostrExample/SceneDelegate.swift create mode 100644 examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift diff --git a/examples/demo-app/.gitignore b/examples/demo-app/.gitignore index ff343ea52..aa92260d9 100644 --- a/examples/demo-app/.gitignore +++ b/examples/demo-app/.gitignore @@ -12,6 +12,7 @@ node_modules/ ios/Pods/ ios/build/ ios/.xcode.env.local +ios/DevelopmentTeam.xcconfig *.xcworkspace/xcuserdata/ *.xcodeproj/xcuserdata/ diff --git a/examples/demo-app/README.md b/examples/demo-app/README.md index d2faba089..2315fa499 100644 --- a/examples/demo-app/README.md +++ b/examples/demo-app/README.md @@ -54,6 +54,19 @@ This example ships both an `ios/` and an `android/` project. **Signing & Capabilities** before running on a physical device. The Simulator needs no team. +## iOS development (simulator vs physical device) + +The app uses **UIScene** lifecycle (required on iOS 26+). In **Debug**: + +- **Simulator** loads JavaScript from **Metro** (`npm start` in this directory). + Use `npx react-native run-ios` as usual. +- **Physical device** loads the **embedded** `main.jsbundle` that Xcode produces + during the build. Metro is not required to open the app, but **after you change + any JS/TS you must rebuild and reinstall** (run `npx react-native run-ios + --device "Your iPhone"` again) to pick up those changes. + +Release builds always use the embedded bundle. + ## How to test peer-to-peer BLE discovery needs two real devices — the iOS Simulator and the Android @@ -65,5 +78,5 @@ emulator have no working Bluetooth stack. 4. Send a connection request, accept it on the other device 5. Open **Chats** and send a message — it is MLS-encrypted automatically -Grant Bluetooth and (on Android) nearby-devices/location permissions when -prompted, or discovery silently returns nothing. +For Android, grant location and nearby-device permissions when prompted so BLE +discovery can run. diff --git a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift index 2ad8f22e5..594cff5be 100644 --- a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift @@ -10,6 +10,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? + /// Stashed from `application(_:didFinishLaunchingWithOptions:)` for the scene. + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? func application( _ application: UIApplication, @@ -21,6 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + self.launchOptions = launchOptions return true } diff --git a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift index 435b24ee2..b79e9116f 100644 --- a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift @@ -13,6 +13,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let appDelegate = UIApplication.shared.delegate as? AppDelegate, let factory = appDelegate.reactNativeFactory else { + assertionFailure("SceneDelegate: AppDelegate or React Native factory is not ready") return } @@ -23,7 +24,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "OfflineDemo", in: window, - launchOptions: nil + launchOptions: appDelegate.launchOptions ) } } diff --git a/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj b/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj index 8a63a19de..3f4b1ecee 100644 --- a/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj +++ b/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0C80B921A6F3F58F76C31292 /* libPods-MeshWiki.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-MeshWiki.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; D376D8FE1677B6FD73C3EF9F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -23,6 +24,7 @@ 5709B34CF0A7D63546082F79 /* Pods-MeshWiki.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MeshWiki.release.xcconfig"; path = "Target Support Files/Pods-MeshWiki/Pods-MeshWiki.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-MeshWiki.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MeshWiki.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = MeshWiki/AppDelegate.swift; sourceTree = ""; }; + 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = MeshWiki/SceneDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MeshWiki/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -44,6 +46,7 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +250,7 @@ buildActionMask = 2147483647; files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift index e49758f56..eb6addf5f 100644 --- a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift +++ b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift @@ -6,9 +6,9 @@ import ReactAppDependencyProvider @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? func application( _ application: UIApplication, @@ -20,16 +20,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + self.launchOptions = launchOptions - window = UIWindow(frame: UIScreen.main.bounds) + return true + } - factory.startReactNative( - withModuleName: "MeshWiki", - in: window, - launchOptions: launchOptions + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role ) - - return true + configuration.delegateClass = SceneDelegate.self + return configuration } } @@ -40,9 +46,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func bundleURL() -> URL? { #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#if targetEnvironment(simulator) + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") { + return embedded + } + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#endif #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } } diff --git a/examples/mesh-wiki/ios/MeshWiki/Info.plist b/examples/mesh-wiki/ios/MeshWiki/Info.plist index 08f45723b..e2edc9cf2 100644 --- a/examples/mesh-wiki/ios/MeshWiki/Info.plist +++ b/examples/mesh-wiki/ios/MeshWiki/Info.plist @@ -37,6 +37,23 @@ RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift new file mode 100644 index 000000000..d1f7dda3c --- /dev/null +++ b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift @@ -0,0 +1,30 @@ +import UIKit +import React + +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 { + assertionFailure("SceneDelegate: AppDelegate or React Native factory is not ready") + return + } + + let window = UIWindow(windowScene: windowScene) + self.window = window + appDelegate.window = window + + factory.startReactNative( + withModuleName: "MeshWiki", + in: window, + launchOptions: appDelegate.launchOptions + ) + } +} diff --git a/examples/mesh-wiki/ios/Podfile b/examples/mesh-wiki/ios/Podfile index 08d8c7348..aebcf2a06 100644 --- a/examples/mesh-wiki/ios/Podfile +++ b/examples/mesh-wiki/ios/Podfile @@ -30,5 +30,11 @@ target 'MeshWiki' do :mac_catalyst_enabled => false, # :ccache_enabled => true ) + + installer.pods_project.targets.each do |target| + target.build_configurations.each do |build_config| + build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' + end + end end end diff --git a/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj b/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj index bab9bd2f7..e9d9b578a 100644 --- a/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj +++ b/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0C80B921A6F3F58F76C31292 /* libPods-NostrExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-NostrExample.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 9C2D3E4F5011228394A5B6C8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A6527776531B6CBC53134632 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -23,6 +24,7 @@ 5709B34CF0A7D63546082F79 /* Pods-NostrExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NostrExample.release.xcconfig"; path = "Target Support Files/Pods-NostrExample/Pods-NostrExample.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-NostrExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NostrExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NostrExample/AppDelegate.swift; sourceTree = ""; }; + 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = NostrExample/SceneDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NostrExample/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -44,6 +46,7 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +250,7 @@ buildActionMask = 2147483647; files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 9C2D3E4F5011228394A5B6C8 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/nostr-example/ios/NostrExample/AppDelegate.swift b/examples/nostr-example/ios/NostrExample/AppDelegate.swift index 9c0ff8033..eb6addf5f 100644 --- a/examples/nostr-example/ios/NostrExample/AppDelegate.swift +++ b/examples/nostr-example/ios/NostrExample/AppDelegate.swift @@ -6,9 +6,9 @@ import ReactAppDependencyProvider @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? func application( _ application: UIApplication, @@ -20,16 +20,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + self.launchOptions = launchOptions - window = UIWindow(frame: UIScreen.main.bounds) + return true + } - factory.startReactNative( - withModuleName: "NostrExample", - in: window, - launchOptions: launchOptions + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role ) - - return true + configuration.delegateClass = SceneDelegate.self + return configuration } } @@ -40,9 +46,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func bundleURL() -> URL? { #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#if targetEnvironment(simulator) + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") { + return embedded + } + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#endif #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } } diff --git a/examples/nostr-example/ios/NostrExample/Info.plist b/examples/nostr-example/ios/NostrExample/Info.plist index 20c26a12b..c51660be1 100644 --- a/examples/nostr-example/ios/NostrExample/Info.plist +++ b/examples/nostr-example/ios/NostrExample/Info.plist @@ -37,6 +37,23 @@ RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/examples/nostr-example/ios/NostrExample/SceneDelegate.swift b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift new file mode 100644 index 000000000..28e2bc093 --- /dev/null +++ b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift @@ -0,0 +1,30 @@ +import UIKit +import React + +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 { + assertionFailure("SceneDelegate: AppDelegate or React Native factory is not ready") + return + } + + let window = UIWindow(windowScene: windowScene) + self.window = window + appDelegate.window = window + + factory.startReactNative( + withModuleName: "NostrExample", + in: window, + launchOptions: appDelegate.launchOptions + ) + } +} diff --git a/examples/nostr-example/ios/Podfile b/examples/nostr-example/ios/Podfile index 1bfd5f6f7..e27950879 100644 --- a/examples/nostr-example/ios/Podfile +++ b/examples/nostr-example/ios/Podfile @@ -33,6 +33,7 @@ target 'NostrExample' do installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| + build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' build_config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES' other_cflags = build_config.build_settings['OTHER_CFLAGS'] || '$(inherited)' other_cxxflags = build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] || '$(inherited)' diff --git a/examples/react-native-app/Gemfile b/examples/react-native-app/Gemfile index 6a4c5f171..97d43c694 100644 --- a/examples/react-native-app/Gemfile +++ b/examples/react-native-app/Gemfile @@ -14,3 +14,4 @@ gem 'bigdecimal' gem 'logger' gem 'benchmark' gem 'mutex_m' +gem 'nkf' # provides kconv, needed by CFPropertyList on Ruby 3.4+ diff --git a/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj b/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj index 6df2e1b0d..4b84036ee 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj +++ b/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + 9B1C2D3E4F5011228394A5B7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */; }; CF91EE88E87D637E5CF013BC /* libPods-OfflineProtocolExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADD34094B13BB66AFA6B2B9F /* libPods-OfflineProtocolExample.a */; }; /* End PBXBuildFile section */ @@ -22,6 +23,7 @@ 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = OfflineProtocolExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OfflineProtocolExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OfflineProtocolExample/LaunchScreen.storyboard; sourceTree = ""; }; + 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OfflineProtocolExample/SceneDelegate.swift; sourceTree = ""; }; ADD34094B13BB66AFA6B2B9F /* libPods-OfflineProtocolExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OfflineProtocolExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DF196923822DDC30DE44E8BA /* Pods-OfflineProtocolExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OfflineProtocolExample.release.xcconfig"; path = "Target Support Files/Pods-OfflineProtocolExample/Pods-OfflineProtocolExample.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -44,6 +46,7 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -248,6 +251,7 @@ buildActionMask = 2147483647; files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + 9B1C2D3E4F5011228394A5B7 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift index f3b1770c1..eb6addf5f 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift +++ b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift @@ -6,9 +6,9 @@ import ReactAppDependencyProvider @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? func application( _ application: UIApplication, @@ -20,16 +20,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + self.launchOptions = launchOptions - window = UIWindow(frame: UIScreen.main.bounds) + return true + } - factory.startReactNative( - withModuleName: "OfflineProtocolExample", - in: window, - launchOptions: launchOptions + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role ) - - return true + configuration.delegateClass = SceneDelegate.self + return configuration } } @@ -40,9 +46,16 @@ class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func bundleURL() -> URL? { #if DEBUG - RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#if targetEnvironment(simulator) + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + if let embedded = Bundle.main.url(forResource: "main", withExtension: "jsbundle") { + return embedded + } + return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#endif #else - Bundle.main.url(forResource: "main", withExtension: "jsbundle") + return Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } } diff --git a/examples/react-native-app/ios/OfflineProtocolExample/Info.plist b/examples/react-native-app/ios/OfflineProtocolExample/Info.plist index 5271a2869..503031894 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/Info.plist +++ b/examples/react-native-app/ios/OfflineProtocolExample/Info.plist @@ -47,6 +47,23 @@ This app needs location access to discover nearby devices for offline messaging RCTNewArchEnabled + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UIBackgroundModes bluetooth-central diff --git a/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift new file mode 100644 index 000000000..e47b6eebf --- /dev/null +++ b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift @@ -0,0 +1,30 @@ +import UIKit +import React + +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 { + assertionFailure("SceneDelegate: AppDelegate or React Native factory is not ready") + return + } + + let window = UIWindow(windowScene: windowScene) + self.window = window + appDelegate.window = window + + factory.startReactNative( + withModuleName: "OfflineProtocolExample", + in: window, + launchOptions: appDelegate.launchOptions + ) + } +} diff --git a/examples/react-native-app/ios/Podfile b/examples/react-native-app/ios/Podfile index acef4f327..8d85e27cb 100644 --- a/examples/react-native-app/ios/Podfile +++ b/examples/react-native-app/ios/Podfile @@ -33,6 +33,7 @@ target 'OfflineProtocolExample' do installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| + build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' build_config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES' other_cflags = build_config.build_settings['OTHER_CFLAGS'] || '$(inherited)' other_cxxflags = build_config.build_settings['OTHER_CPLUSPLUSFLAGS'] || '$(inherited)' From c6244353985ffce653cb88bca205e6b589ced510 Mon Sep 17 00:00:00 2001 From: Ankush523 Date: Tue, 6 Oct 2026 11:20:02 -0400 Subject: [PATCH 3/5] fix(bindings): address PR review for UIScene example apps --- examples/demo-app/README.md | 4 +++- .../ios/DevelopmentTeam.xcconfig.example | 3 +++ .../ios/OfflineDemo/AppDelegate.swift | 24 +++++++++++++++++++ .../ios/OfflineDemo/PrivacyInfo.xcprivacy | 8 +++---- .../ios/OfflineDemo/SceneDelegate.swift | 4 +++- examples/demo-app/ios/Podfile | 22 +++-------------- examples/demo-app/ios/Podfile.lock | 2 +- .../mesh-wiki/ios/MeshWiki/AppDelegate.swift | 23 ++++++++++++++++++ .../ios/MeshWiki/SceneDelegate.swift | 4 +++- examples/mesh-wiki/ios/Podfile | 4 ++++ .../ios/NostrExample/AppDelegate.swift | 23 ++++++++++++++++++ .../ios/NostrExample/SceneDelegate.swift | 4 +++- examples/nostr-example/ios/Podfile | 4 ++++ .../OfflineProtocolExample/AppDelegate.swift | 23 ++++++++++++++++++ .../SceneDelegate.swift | 4 +++- examples/react-native-app/ios/Podfile | 4 ++++ .../shared/ios/pod_fmt_consteval_patch.rb | 21 ++++++++++++++++ 17 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 examples/demo-app/ios/DevelopmentTeam.xcconfig.example create mode 100644 examples/shared/ios/pod_fmt_consteval_patch.rb diff --git a/examples/demo-app/README.md b/examples/demo-app/README.md index 2315fa499..4ac35c73b 100644 --- a/examples/demo-app/README.md +++ b/examples/demo-app/README.md @@ -52,7 +52,9 @@ This example ships both an `ios/` and an `android/` project. `DEVELOPMENT_TEAM` is intentionally blank in the committed Xcode project. Open `ios/OfflineDemo.xcodeproj`, select the target, and set your own team under **Signing & Capabilities** before running on a physical device. The Simulator -needs no team. +needs no team. Optionally, copy `ios/DevelopmentTeam.xcconfig.example` to +`ios/DevelopmentTeam.xcconfig` and set your team ID if you use a local xcconfig +workflow (that file is gitignored). ## iOS development (simulator vs physical device) diff --git a/examples/demo-app/ios/DevelopmentTeam.xcconfig.example b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example new file mode 100644 index 000000000..3cf4590ad --- /dev/null +++ b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example @@ -0,0 +1,3 @@ +// Optional local signing override (copy to DevelopmentTeam.xcconfig, gitignored). +// Set your Apple Team ID from Xcode → OfflineDemo → Signing & Capabilities → Team. +DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE diff --git a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift index 594cff5be..016ca7505 100644 --- a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift @@ -40,6 +40,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } + + /// Merges `didFinishLaunchingWithOptions` with scene cold-start context for React Native. + func reactNativeLaunchOptions( + sceneConnectionOptions: UIScene.ConnectionOptions + ) -> [UIApplication.LaunchOptionsKey: Any]? { + var options = launchOptions ?? [:] + + if let url = sceneConnectionOptions.urlContexts.first?.url { + options[.url] = url + } + + if let response = sceneConnectionOptions.notificationResponse { + options[.remoteNotification] = response.notification.request.content.userInfo + } + + if let userActivity = sceneConnectionOptions.userActivities.first { + options[.userActivityType] = userActivity.activityType + options[.userActivityDictionary] = [ + "UIApplicationLaunchOptionsUserActivityKey": userActivity, + ] + } + + return options.isEmpty ? nil : options + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy b/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy index 41b8317f0..bad327615 100644 --- a/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy +++ b/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy @@ -6,18 +6,18 @@ NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - C617.1 + CA92.1 NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons - CA92.1 + C617.1 diff --git a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift index b79e9116f..fdd3dd4f7 100644 --- a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift @@ -24,7 +24,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "OfflineDemo", in: window, - launchOptions: appDelegate.launchOptions + launchOptions: appDelegate.reactNativeLaunchOptions( + sceneConnectionOptions: connectionOptions + ) ) } } diff --git a/examples/demo-app/ios/Podfile b/examples/demo-app/ios/Podfile index 4af817f71..0c901b0a6 100644 --- a/examples/demo-app/ios/Podfile +++ b/examples/demo-app/ios/Podfile @@ -5,6 +5,8 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip +require File.expand_path('../../shared/ios/pod_fmt_consteval_patch.rb', __dir__) + platform :ios, min_ios_version_supported prepare_react_native_project! @@ -31,25 +33,7 @@ target 'OfflineDemo' do # :ccache_enabled => true ) - # fmt 11.0.2 (hard-pinned by RCT-Folly -> React Native 0.82) does not compile - # under Xcode 26.5 / clang 21: clang defines __cpp_consteval, so fmt's base.h - # selects FMT_USE_CONSTEVAL=1 and its consteval format-string check fails with - # "call to consteval function ... is not a constant expression". base.h sets - # FMT_USE_CONSTEVAL via a raw #if/#elif chain with NO #ifndef guard, so a - # -DFMT_USE_CONSTEVAL=0 compile flag is silently overridden — the value must be - # forced in the header. Rewrite the two `#define FMT_USE_CONSTEVAL 1` lines to - # 0. Idempotent; re-applied on every `pod install`. Demo-app only — the SDK - # sources are untouched. - fmt_base = File.join(installer.sandbox.root, 'fmt', 'include', 'fmt', 'base.h') - if File.exist?(fmt_base) - original = File.read(fmt_base) - patched = original.gsub('# define FMT_USE_CONSTEVAL 1', - '# define FMT_USE_CONSTEVAL 0') - if patched != original - File.write(fmt_base, patched) - Pod::UI.puts 'Patched fmt/base.h: forced FMT_USE_CONSTEVAL=0 (Xcode 26.5 / clang 21)'.yellow - end - end + apply_fmt_consteval_patch_for_xcode26(installer) installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| diff --git a/examples/demo-app/ios/Podfile.lock b/examples/demo-app/ios/Podfile.lock index a3e2899da..a508fe60a 100644 --- a/examples/demo-app/ios/Podfile.lock +++ b/examples/demo-app/ios/Podfile.lock @@ -2727,6 +2727,6 @@ SPEC CHECKSUMS: SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb -PODFILE CHECKSUM: 98555054fbebb78edd782446f9f6ca214c995483 +PODFILE CHECKSUM: 5526e8df1b28bf6d6fac655e3c3ed2e763814a03 COCOAPODS: 1.16.2 diff --git a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift index eb6addf5f..17eaecfba 100644 --- a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift +++ b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift @@ -37,6 +37,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } + + func reactNativeLaunchOptions( + sceneConnectionOptions: UIScene.ConnectionOptions + ) -> [UIApplication.LaunchOptionsKey: Any]? { + var options = launchOptions ?? [:] + + if let url = sceneConnectionOptions.urlContexts.first?.url { + options[.url] = url + } + + if let response = sceneConnectionOptions.notificationResponse { + options[.remoteNotification] = response.notification.request.content.userInfo + } + + if let userActivity = sceneConnectionOptions.userActivities.first { + options[.userActivityType] = userActivity.activityType + options[.userActivityDictionary] = [ + "UIApplicationLaunchOptionsUserActivityKey": userActivity, + ] + } + + return options.isEmpty ? nil : options + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift index d1f7dda3c..a16531ac4 100644 --- a/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift +++ b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift @@ -24,7 +24,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "MeshWiki", in: window, - launchOptions: appDelegate.launchOptions + launchOptions: appDelegate.reactNativeLaunchOptions( + sceneConnectionOptions: connectionOptions + ) ) } } diff --git a/examples/mesh-wiki/ios/Podfile b/examples/mesh-wiki/ios/Podfile index aebcf2a06..74b54d66e 100644 --- a/examples/mesh-wiki/ios/Podfile +++ b/examples/mesh-wiki/ios/Podfile @@ -5,6 +5,8 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip +require File.expand_path('../../shared/ios/pod_fmt_consteval_patch.rb', __dir__) + platform :ios, min_ios_version_supported prepare_react_native_project! @@ -31,6 +33,8 @@ target 'MeshWiki' do # :ccache_enabled => true ) + apply_fmt_consteval_patch_for_xcode26(installer) + installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' diff --git a/examples/nostr-example/ios/NostrExample/AppDelegate.swift b/examples/nostr-example/ios/NostrExample/AppDelegate.swift index eb6addf5f..17eaecfba 100644 --- a/examples/nostr-example/ios/NostrExample/AppDelegate.swift +++ b/examples/nostr-example/ios/NostrExample/AppDelegate.swift @@ -37,6 +37,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } + + func reactNativeLaunchOptions( + sceneConnectionOptions: UIScene.ConnectionOptions + ) -> [UIApplication.LaunchOptionsKey: Any]? { + var options = launchOptions ?? [:] + + if let url = sceneConnectionOptions.urlContexts.first?.url { + options[.url] = url + } + + if let response = sceneConnectionOptions.notificationResponse { + options[.remoteNotification] = response.notification.request.content.userInfo + } + + if let userActivity = sceneConnectionOptions.userActivities.first { + options[.userActivityType] = userActivity.activityType + options[.userActivityDictionary] = [ + "UIApplicationLaunchOptionsUserActivityKey": userActivity, + ] + } + + return options.isEmpty ? nil : options + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/nostr-example/ios/NostrExample/SceneDelegate.swift b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift index 28e2bc093..0bbc119bf 100644 --- a/examples/nostr-example/ios/NostrExample/SceneDelegate.swift +++ b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift @@ -24,7 +24,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "NostrExample", in: window, - launchOptions: appDelegate.launchOptions + launchOptions: appDelegate.reactNativeLaunchOptions( + sceneConnectionOptions: connectionOptions + ) ) } } diff --git a/examples/nostr-example/ios/Podfile b/examples/nostr-example/ios/Podfile index e27950879..d22d71056 100644 --- a/examples/nostr-example/ios/Podfile +++ b/examples/nostr-example/ios/Podfile @@ -5,6 +5,8 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip +require File.expand_path('../../shared/ios/pod_fmt_consteval_patch.rb', __dir__) + platform :ios, min_ios_version_supported prepare_react_native_project! @@ -31,6 +33,8 @@ target 'NostrExample' do # :ccache_enabled => true ) + apply_fmt_consteval_patch_for_xcode26(installer) + installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' diff --git a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift index eb6addf5f..17eaecfba 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift +++ b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift @@ -37,6 +37,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } + + func reactNativeLaunchOptions( + sceneConnectionOptions: UIScene.ConnectionOptions + ) -> [UIApplication.LaunchOptionsKey: Any]? { + var options = launchOptions ?? [:] + + if let url = sceneConnectionOptions.urlContexts.first?.url { + options[.url] = url + } + + if let response = sceneConnectionOptions.notificationResponse { + options[.remoteNotification] = response.notification.request.content.userInfo + } + + if let userActivity = sceneConnectionOptions.userActivities.first { + options[.userActivityType] = userActivity.activityType + options[.userActivityDictionary] = [ + "UIApplicationLaunchOptionsUserActivityKey": userActivity, + ] + } + + return options.isEmpty ? nil : options + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift index e47b6eebf..40999f149 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift +++ b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift @@ -24,7 +24,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "OfflineProtocolExample", in: window, - launchOptions: appDelegate.launchOptions + launchOptions: appDelegate.reactNativeLaunchOptions( + sceneConnectionOptions: connectionOptions + ) ) } } diff --git a/examples/react-native-app/ios/Podfile b/examples/react-native-app/ios/Podfile index 8d85e27cb..aae80e210 100644 --- a/examples/react-native-app/ios/Podfile +++ b/examples/react-native-app/ios/Podfile @@ -5,6 +5,8 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip +require File.expand_path('../../shared/ios/pod_fmt_consteval_patch.rb', __dir__) + platform :ios, min_ios_version_supported prepare_react_native_project! @@ -31,6 +33,8 @@ target 'OfflineProtocolExample' do # :ccache_enabled => true ) + apply_fmt_consteval_patch_for_xcode26(installer) + installer.pods_project.targets.each do |target| target.build_configurations.each do |build_config| build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' diff --git a/examples/shared/ios/pod_fmt_consteval_patch.rb b/examples/shared/ios/pod_fmt_consteval_patch.rb new file mode 100644 index 000000000..e9f4186f5 --- /dev/null +++ b/examples/shared/ios/pod_fmt_consteval_patch.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# fmt 11.0.2 (hard-pinned by RCT-Folly -> React Native 0.82) does not compile +# under Xcode 26+ / clang 21: clang defines __cpp_consteval, so fmt's base.h +# selects FMT_USE_CONSTEVAL=1 and its consteval format-string check fails with +# "call to consteval function ... is not a constant expression". base.h sets +# FMT_USE_CONSTEVAL via a raw #if/#elif chain with NO #ifndef guard, so a +# -DFMT_USE_CONSTEVAL=0 compile flag is silently overridden — the value must be +# forced in the header. Idempotent; re-applied on every `pod install`. +def apply_fmt_consteval_patch_for_xcode26(installer) + fmt_base = File.join(installer.sandbox.root, 'fmt', 'include', 'fmt', 'base.h') + return unless File.exist?(fmt_base) + + original = File.read(fmt_base) + patched = original.gsub('# define FMT_USE_CONSTEVAL 1', + '# define FMT_USE_CONSTEVAL 0') + return if patched == original + + File.write(fmt_base, patched) + Pod::UI.puts 'Patched fmt/base.h: forced FMT_USE_CONSTEVAL=0 (Xcode 26+ / clang 21)'.yellow +end From 490360b97d36e8ef1f8ea1a05f7f19ab7af45096 Mon Sep 17 00:00:00 2001 From: Ankush523 Date: Tue, 6 Oct 2026 11:24:37 -0400 Subject: [PATCH 4/5] chore(examples): polish UIScene PR per review --- examples/demo-app/README.md | 8 ++++-- .../ios/DevelopmentTeam.xcconfig.example | 5 ++-- .../ios/OfflineDemo.xcodeproj/project.pbxproj | 4 +++ .../ios/OfflineDemo/AppDelegate.swift | 24 ---------------- .../ios/OfflineDemo/SceneDelegate.swift | 3 +- examples/demo-app/ios/Podfile.lock | 2 +- .../ios/MeshWiki.xcodeproj/project.pbxproj | 4 +++ .../mesh-wiki/ios/MeshWiki/AppDelegate.swift | 23 --------------- .../ios/MeshWiki/SceneDelegate.swift | 3 +- .../NostrExample.xcodeproj/project.pbxproj | 4 +++ .../ios/NostrExample/AppDelegate.swift | 23 --------------- .../ios/NostrExample/SceneDelegate.swift | 3 +- .../project.pbxproj | 4 +++ .../OfflineProtocolExample/AppDelegate.swift | 23 --------------- .../SceneDelegate.swift | 3 +- .../ios/ReactNativeSceneLaunchOptions.swift | 28 +++++++++++++++++++ 16 files changed, 61 insertions(+), 103 deletions(-) create mode 100644 examples/shared/ios/ReactNativeSceneLaunchOptions.swift diff --git a/examples/demo-app/README.md b/examples/demo-app/README.md index 4ac35c73b..182cec2ed 100644 --- a/examples/demo-app/README.md +++ b/examples/demo-app/README.md @@ -52,9 +52,11 @@ This example ships both an `ios/` and an `android/` project. `DEVELOPMENT_TEAM` is intentionally blank in the committed Xcode project. Open `ios/OfflineDemo.xcodeproj`, select the target, and set your own team under **Signing & Capabilities** before running on a physical device. The Simulator -needs no team. Optionally, copy `ios/DevelopmentTeam.xcconfig.example` to -`ios/DevelopmentTeam.xcconfig` and set your team ID if you use a local xcconfig -workflow (that file is gitignored). +needs no team. The repo also ships `ios/DevelopmentTeam.xcconfig.example` as an +optional template for a **local** xcconfig (copy to `ios/DevelopmentTeam.xcconfig`, +gitignored). The Xcode target does **not** include that file; copying it alone +does not configure signing — set your team in **Signing & Capabilities**, or wire +the xcconfig into the project yourself if you prefer that workflow. ## iOS development (simulator vs physical device) diff --git a/examples/demo-app/ios/DevelopmentTeam.xcconfig.example b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example index 3cf4590ad..478bbe57e 100644 --- a/examples/demo-app/ios/DevelopmentTeam.xcconfig.example +++ b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example @@ -1,3 +1,4 @@ -// Optional local signing override (copy to DevelopmentTeam.xcconfig, gitignored). -// Set your Apple Team ID from Xcode → OfflineDemo → Signing & Capabilities → Team. +// Optional template only — not referenced by the OfflineDemo Xcode target. +// Copy to DevelopmentTeam.xcconfig (gitignored) if you wire xcconfig into the +// project yourself; otherwise set Team under Signing & Capabilities in Xcode. DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE diff --git a/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj b/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj index c6fdcd197..bf8e5f9fe 100644 --- a/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj +++ b/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0C80B921A6F3F58F76C31292 /* libPods-OfflineDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-OfflineDemo.a */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 7E8F9A0B1C2D3E4F5061728A /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */; }; A6527776531B6CBC53134632 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; @@ -24,6 +25,7 @@ 5709B34CF0A7D63546082F79 /* Pods-OfflineDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OfflineDemo.release.xcconfig"; path = "Target Support Files/Pods-OfflineDemo/Pods-OfflineDemo.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-OfflineDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OfflineDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OfflineDemo/AppDelegate.swift; sourceTree = ""; }; + 7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OfflineDemo/LaunchScreen.storyboard; sourceTree = ""; }; 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OfflineDemo/SceneDelegate.swift; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -47,6 +49,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -251,6 +254,7 @@ files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, 8A2F3B4C5D6E7F8091A2B3C5 /* SceneDelegate.swift in Sources */, + 7E8F9A0B1C2D3E4F5061728A /* ReactNativeSceneLaunchOptions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift index 016ca7505..594cff5be 100644 --- a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift @@ -40,30 +40,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } - - /// Merges `didFinishLaunchingWithOptions` with scene cold-start context for React Native. - func reactNativeLaunchOptions( - sceneConnectionOptions: UIScene.ConnectionOptions - ) -> [UIApplication.LaunchOptionsKey: Any]? { - var options = launchOptions ?? [:] - - if let url = sceneConnectionOptions.urlContexts.first?.url { - options[.url] = url - } - - if let response = sceneConnectionOptions.notificationResponse { - options[.remoteNotification] = response.notification.request.content.userInfo - } - - if let userActivity = sceneConnectionOptions.userActivities.first { - options[.userActivityType] = userActivity.activityType - options[.userActivityDictionary] = [ - "UIApplicationLaunchOptionsUserActivityKey": userActivity, - ] - } - - return options.isEmpty ? nil : options - } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift index fdd3dd4f7..813ccaa16 100644 --- a/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift @@ -24,7 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "OfflineDemo", in: window, - launchOptions: appDelegate.reactNativeLaunchOptions( + launchOptions: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, sceneConnectionOptions: connectionOptions ) ) diff --git a/examples/demo-app/ios/Podfile.lock b/examples/demo-app/ios/Podfile.lock index a508fe60a..acb5ef1ea 100644 --- a/examples/demo-app/ios/Podfile.lock +++ b/examples/demo-app/ios/Podfile.lock @@ -2729,4 +2729,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5526e8df1b28bf6d6fac655e3c3ed2e763814a03 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj b/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj index 3f4b1ecee..ea320d2bb 100644 --- a/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj +++ b/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */; }; + 7E8F9A0B1C2D3E4F50617290 /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; D376D8FE1677B6FD73C3EF9F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -25,6 +26,7 @@ 5DCACB8F33CDC322A6C60F78 /* libPods-MeshWiki.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MeshWiki.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = MeshWiki/AppDelegate.swift; sourceTree = ""; }; 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = MeshWiki/SceneDelegate.swift; sourceTree = ""; }; + 7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = MeshWiki/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -47,6 +49,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -251,6 +254,7 @@ files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, 9D3E4F5011228394A5B6C7D9 /* SceneDelegate.swift in Sources */, + 7E8F9A0B1C2D3E4F50617290 /* ReactNativeSceneLaunchOptions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift index 17eaecfba..eb6addf5f 100644 --- a/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift +++ b/examples/mesh-wiki/ios/MeshWiki/AppDelegate.swift @@ -37,29 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } - - func reactNativeLaunchOptions( - sceneConnectionOptions: UIScene.ConnectionOptions - ) -> [UIApplication.LaunchOptionsKey: Any]? { - var options = launchOptions ?? [:] - - if let url = sceneConnectionOptions.urlContexts.first?.url { - options[.url] = url - } - - if let response = sceneConnectionOptions.notificationResponse { - options[.remoteNotification] = response.notification.request.content.userInfo - } - - if let userActivity = sceneConnectionOptions.userActivities.first { - options[.userActivityType] = userActivity.activityType - options[.userActivityDictionary] = [ - "UIApplicationLaunchOptionsUserActivityKey": userActivity, - ] - } - - return options.isEmpty ? nil : options - } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift index a16531ac4..f646a3fca 100644 --- a/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift +++ b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift @@ -24,7 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "MeshWiki", in: window, - launchOptions: appDelegate.reactNativeLaunchOptions( + launchOptions: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, sceneConnectionOptions: connectionOptions ) ) diff --git a/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj b/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj index e9d9b578a..0a0be4fba 100644 --- a/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj +++ b/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 9C2D3E4F5011228394A5B6C8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */; }; + 7E8F9A0B1C2D3E4F5061728E /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F5061728D /* ReactNativeSceneLaunchOptions.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; A6527776531B6CBC53134632 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -25,6 +26,7 @@ 5DCACB8F33CDC322A6C60F78 /* libPods-NostrExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-NostrExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NostrExample/AppDelegate.swift; sourceTree = ""; }; 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = NostrExample/SceneDelegate.swift; sourceTree = ""; }; + 7E8F9A0B1C2D3E4F5061728D /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NostrExample/LaunchScreen.storyboard; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -47,6 +49,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728D /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -251,6 +254,7 @@ files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, 9C2D3E4F5011228394A5B6C8 /* SceneDelegate.swift in Sources */, + 7E8F9A0B1C2D3E4F5061728E /* ReactNativeSceneLaunchOptions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/nostr-example/ios/NostrExample/AppDelegate.swift b/examples/nostr-example/ios/NostrExample/AppDelegate.swift index 17eaecfba..eb6addf5f 100644 --- a/examples/nostr-example/ios/NostrExample/AppDelegate.swift +++ b/examples/nostr-example/ios/NostrExample/AppDelegate.swift @@ -37,29 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } - - func reactNativeLaunchOptions( - sceneConnectionOptions: UIScene.ConnectionOptions - ) -> [UIApplication.LaunchOptionsKey: Any]? { - var options = launchOptions ?? [:] - - if let url = sceneConnectionOptions.urlContexts.first?.url { - options[.url] = url - } - - if let response = sceneConnectionOptions.notificationResponse { - options[.remoteNotification] = response.notification.request.content.userInfo - } - - if let userActivity = sceneConnectionOptions.userActivities.first { - options[.userActivityType] = userActivity.activityType - options[.userActivityDictionary] = [ - "UIApplicationLaunchOptionsUserActivityKey": userActivity, - ] - } - - return options.isEmpty ? nil : options - } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/nostr-example/ios/NostrExample/SceneDelegate.swift b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift index 0bbc119bf..529670c0f 100644 --- a/examples/nostr-example/ios/NostrExample/SceneDelegate.swift +++ b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift @@ -24,7 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "NostrExample", in: window, - launchOptions: appDelegate.reactNativeLaunchOptions( + launchOptions: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, sceneConnectionOptions: connectionOptions ) ) diff --git a/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj b/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj index 4b84036ee..6bf5a1978 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj +++ b/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 9B1C2D3E4F5011228394A5B7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */; }; + 7E8F9A0B1C2D3E4F5061728C /* ReactNativeSceneLaunchOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E8F9A0B1C2D3E4F5061728B /* ReactNativeSceneLaunchOptions.swift */; }; CF91EE88E87D637E5CF013BC /* libPods-OfflineProtocolExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADD34094B13BB66AFA6B2B9F /* libPods-OfflineProtocolExample.a */; }; /* End PBXBuildFile section */ @@ -24,6 +25,7 @@ 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OfflineProtocolExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OfflineProtocolExample/LaunchScreen.storyboard; sourceTree = ""; }; 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OfflineProtocolExample/SceneDelegate.swift; sourceTree = ""; }; + 7E8F9A0B1C2D3E4F5061728B /* ReactNativeSceneLaunchOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactNativeSceneLaunchOptions.swift; path = ../../shared/ios/ReactNativeSceneLaunchOptions.swift; sourceTree = ""; }; ADD34094B13BB66AFA6B2B9F /* libPods-OfflineProtocolExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OfflineProtocolExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DF196923822DDC30DE44E8BA /* Pods-OfflineProtocolExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OfflineProtocolExample.release.xcconfig"; path = "Target Support Files/Pods-OfflineProtocolExample/Pods-OfflineProtocolExample.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -47,6 +49,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728B /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -252,6 +255,7 @@ files = ( 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, 9B1C2D3E4F5011228394A5B7 /* SceneDelegate.swift in Sources */, + 7E8F9A0B1C2D3E4F5061728C /* ReactNativeSceneLaunchOptions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift index 17eaecfba..eb6addf5f 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift +++ b/examples/react-native-app/ios/OfflineProtocolExample/AppDelegate.swift @@ -37,29 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { configuration.delegateClass = SceneDelegate.self return configuration } - - func reactNativeLaunchOptions( - sceneConnectionOptions: UIScene.ConnectionOptions - ) -> [UIApplication.LaunchOptionsKey: Any]? { - var options = launchOptions ?? [:] - - if let url = sceneConnectionOptions.urlContexts.first?.url { - options[.url] = url - } - - if let response = sceneConnectionOptions.notificationResponse { - options[.remoteNotification] = response.notification.request.content.userInfo - } - - if let userActivity = sceneConnectionOptions.userActivities.first { - options[.userActivityType] = userActivity.activityType - options[.userActivityDictionary] = [ - "UIApplicationLaunchOptionsUserActivityKey": userActivity, - ] - } - - return options.isEmpty ? nil : options - } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { diff --git a/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift index 40999f149..beae50aa9 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift +++ b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift @@ -24,7 +24,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { factory.startReactNative( withModuleName: "OfflineProtocolExample", in: window, - launchOptions: appDelegate.reactNativeLaunchOptions( + launchOptions: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, sceneConnectionOptions: connectionOptions ) ) diff --git a/examples/shared/ios/ReactNativeSceneLaunchOptions.swift b/examples/shared/ios/ReactNativeSceneLaunchOptions.swift new file mode 100644 index 000000000..d53a82761 --- /dev/null +++ b/examples/shared/ios/ReactNativeSceneLaunchOptions.swift @@ -0,0 +1,28 @@ +import UIKit + +/// Maps UIScene cold-start context into `UIApplication.LaunchOptionsKey` values for React Native. +enum ReactNativeSceneLaunchOptions { + static func merging( + _ launchOptions: [UIApplication.LaunchOptionsKey: Any]?, + sceneConnectionOptions: UIScene.ConnectionOptions + ) -> [UIApplication.LaunchOptionsKey: Any]? { + var options = launchOptions ?? [:] + + if let url = sceneConnectionOptions.urlContexts.first?.url { + options[.url] = url + } + + if let response = sceneConnectionOptions.notificationResponse { + options[.remoteNotification] = response.notification.request.content.userInfo + } + + if let userActivity = sceneConnectionOptions.userActivities.first { + options[.userActivityType] = userActivity.activityType + options[.userActivityDictionary] = [ + "UIApplicationLaunchOptionsUserActivityKey": userActivity, + ] + } + + return options.isEmpty ? nil : options + } +} From 20ceda9b47b3a98b4d8fd540562690101beecc4c Mon Sep 17 00:00:00 2001 From: Ankush523 Date: Tue, 6 Oct 2026 11:27:01 -0400 Subject: [PATCH 5/5] chore(examples): revert demo PrivacyInfo pod aggregation noise --- examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy b/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy index bad327615..41b8317f0 100644 --- a/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy +++ b/examples/demo-app/ios/OfflineDemo/PrivacyInfo.xcprivacy @@ -6,18 +6,18 @@ NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons - CA92.1 + C617.1 NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - C617.1 + CA92.1