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/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/README.md b/examples/demo-app/README.md index d2faba089..182cec2ed 100644 --- a/examples/demo-app/README.md +++ b/examples/demo-app/README.md @@ -52,7 +52,24 @@ 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. 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) + +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 @@ -65,5 +82,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/DevelopmentTeam.xcconfig.example b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example new file mode 100644 index 000000000..478bbe57e --- /dev/null +++ b/examples/demo-app/ios/DevelopmentTeam.xcconfig.example @@ -0,0 +1,4 @@ +// 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 2d3b2a499..bf8e5f9fe 100644 --- a/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj +++ b/examples/demo-app/ios/OfflineDemo.xcodeproj/project.pbxproj @@ -10,7 +10,9 @@ 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 */; }; /* End PBXBuildFile section */ @@ -23,7 +25,9 @@ 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; }; /* End PBXFileReference section */ @@ -44,6 +48,8 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 8A2F3B4C5D6E7F8091A2B3C4 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F50617289 /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +253,8 @@ buildActionMask = 2147483647; 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 794a886e8..594cff5be 100644 --- a/examples/demo-app/ios/OfflineDemo/AppDelegate.swift +++ b/examples/demo-app/ios/OfflineDemo/AppDelegate.swift @@ -5,10 +5,13 @@ 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? + /// Stashed from `application(_:didFinishLaunchingWithOptions:)` for the scene. + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? func application( _ application: UIApplication, @@ -20,16 +23,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate { reactNativeDelegate = delegate reactNativeFactory = factory + self.launchOptions = launchOptions - 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 +49,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..813ccaa16 --- /dev/null +++ b/examples/demo-app/ios/OfflineDemo/SceneDelegate.swift @@ -0,0 +1,33 @@ +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: "OfflineDemo", + in: window, + launchOptions: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, + sceneConnectionOptions: connectionOptions + ) + ) + } +} diff --git a/examples/demo-app/ios/Podfile b/examples/demo-app/ios/Podfile index 1993dd209..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,28 +33,12 @@ 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| + # 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..acb5ef1ea 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: 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 8a63a19de..ea320d2bb 100644 --- a/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj +++ b/examples/mesh-wiki/ios/MeshWiki.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 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 */; }; + 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 */ @@ -23,6 +25,8 @@ 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 = ""; }; + 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 */ @@ -44,6 +48,8 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9D3E4F5011228394A5B6C7D8 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728F /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +253,8 @@ buildActionMask = 2147483647; 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 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..f646a3fca --- /dev/null +++ b/examples/mesh-wiki/ios/MeshWiki/SceneDelegate.swift @@ -0,0 +1,33 @@ +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: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, + sceneConnectionOptions: connectionOptions + ) + ) + } +} diff --git a/examples/mesh-wiki/ios/Podfile b/examples/mesh-wiki/ios/Podfile index 08d8c7348..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! @@ -30,5 +32,13 @@ target 'MeshWiki' do :mac_catalyst_enabled => false, # :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' + 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..0a0be4fba 100644 --- a/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj +++ b/examples/nostr-example/ios/NostrExample.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 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 */; }; + 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 */ @@ -23,6 +25,8 @@ 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 = ""; }; + 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 */ @@ -44,6 +48,8 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9C2D3E4F5011228394A5B6C7 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728D /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -247,6 +253,8 @@ buildActionMask = 2147483647; 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 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..529670c0f --- /dev/null +++ b/examples/nostr-example/ios/NostrExample/SceneDelegate.swift @@ -0,0 +1,33 @@ +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: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, + sceneConnectionOptions: connectionOptions + ) + ) + } +} diff --git a/examples/nostr-example/ios/Podfile b/examples/nostr-example/ios/Podfile index 1bfd5f6f7..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,8 +33,11 @@ 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' 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..6bf5a1978 100644 --- a/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj +++ b/examples/react-native-app/ios/OfflineProtocolExample.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 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 */; }; + 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 */ @@ -22,6 +24,8 @@ 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 = ""; }; + 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; }; @@ -44,6 +48,8 @@ children = ( 13B07FB51A68108700A75B9A /* Images.xcassets */, 761780EC2CA45674006654EE /* AppDelegate.swift */, + 9B1C2D3E4F5011228394A5B6 /* SceneDelegate.swift */, + 7E8F9A0B1C2D3E4F5061728B /* ReactNativeSceneLaunchOptions.swift */, 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, @@ -248,6 +254,8 @@ buildActionMask = 2147483647; 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 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..beae50aa9 --- /dev/null +++ b/examples/react-native-app/ios/OfflineProtocolExample/SceneDelegate.swift @@ -0,0 +1,33 @@ +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: ReactNativeSceneLaunchOptions.merging( + appDelegate.launchOptions, + sceneConnectionOptions: connectionOptions + ) + ) + } +} diff --git a/examples/react-native-app/ios/Podfile b/examples/react-native-app/ios/Podfile index acef4f327..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,8 +33,11 @@ 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' 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/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 + } +} 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