From 95addc4e313e7a83a85f8a1cf327d18c535b35d5 Mon Sep 17 00:00:00 2001 From: Sunita Prajapati Date: Tue, 13 Oct 2026 11:39:42 +0530 Subject: [PATCH] feat: add iOS UIScene life cycle support Register the core plugin as a scene delegate so deep links are received via scene(_:openURLContexts:) in apps that adopt the UIScene life cycle. The UIApplicationDelegate path is kept for apps that have not migrated. - Require Flutter >=3.38.0 (first release with the scene plugin API) - Bump segment_analytics to 1.2.0 - Migrate the example app to UIScene Fixes #192 Co-Authored-By: Claude Sonnet 5.5 --- example/ios/Runner.xcodeproj/project.pbxproj | 4 +++ example/ios/Runner/AppDelegate.swift | 9 ++++-- example/ios/Runner/Info.plist | 32 +++++++++++++++++++ example/ios/Runner/SceneDelegate.swift | 5 +++ packages/core/CHANGELOG.md | 5 +++ packages/core/README.md | 2 ++ .../core/ios/Classes/AnalyticsPlugin.swift | 24 ++++++++++---- packages/core/pubspec.yaml | 4 +-- 8 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 example/ios/Runner/SceneDelegate.swift diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 0b159f7..42f66d6 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B5C067C318D8DD07D23BA94 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98E2D8A73DE7B28F12DCB802 /* Pods_Runner.framework */; }; + A1B2C3D41F00000100000001 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D41F00000100000002 /* SceneDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -36,6 +37,7 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 5D511AE6D5667D367382F8FB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + A1B2C3D41F00000100000002 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -115,6 +117,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + A1B2C3D41F00000100000002 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; @@ -280,6 +283,7 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + A1B2C3D41F00000100000001 /* SceneDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..6bea110 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ import UIKit import Flutter -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 4698a4a..830a318 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -47,5 +47,37 @@ UIApplicationSupportsIndirectInputEvents + CFBundleURLTypes + + + CFBundleURLName + segment.analytics.example + CFBundleURLSchemes + + segmentexample + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + diff --git a/example/ios/Runner/SceneDelegate.swift b/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..de2a22f --- /dev/null +++ b/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,5 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { +} diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 404071c..5e5de52 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.2.0 + +- Added iOS UIScene life cycle support (deep links are now received via `scene(_:openURLContexts:)`) +- **Breaking:** requires Flutter 3.38 or newer. Apps on an older Flutter version should stay on `segment_analytics: 1.1.12` + ## 1.1.12 - Added Swift Package Manager support for iOS and macOS diff --git a/packages/core/README.md b/packages/core/README.md index 59a3fb3..f56c016 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -58,6 +58,8 @@ Run: `flutter pub add segment_analytics` +> **Flutter version compatibility:** `segment_analytics` 1.2.0 and newer require Flutter 3.38+ (iOS UIScene life cycle support). If you are on an older Flutter version, pin `segment_analytics: 1.1.12`. + ```dart import 'package:segment_analytics/client.dart'; ``` diff --git a/packages/core/ios/Classes/AnalyticsPlugin.swift b/packages/core/ios/Classes/AnalyticsPlugin.swift index 0dde117..3014b46 100644 --- a/packages/core/ios/Classes/AnalyticsPlugin.swift +++ b/packages/core/ios/Classes/AnalyticsPlugin.swift @@ -2,7 +2,7 @@ import Flutter import UIKit import Foundation -public class AnalyticsPlugin: NSObject, FlutterPlugin, NativeContextApi, FlutterStreamHandler, FlutterApplicationLifeCycleDelegate { +public class AnalyticsPlugin: NSObject, FlutterPlugin, NativeContextApi, FlutterStreamHandler, FlutterApplicationLifeCycleDelegate, FlutterSceneLifeCycleDelegate { private var pendingDeeplinkEventsQueue:[[String:String?]] = [] public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { _eventSink = events @@ -16,15 +16,26 @@ public class AnalyticsPlugin: NSObject, FlutterPlugin, NativeContextApi, Flutter } var _eventSink:FlutterEventSink?; - public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool{ - let sourceApplication = options[.sourceApplication] as? String; + private func handleDeeplink(url: URL, sourceApplication: String?) { + let event: [String:String?] = ["url": url.absoluteString, "referring_application": sourceApplication] if (_eventSink != nil) { - _eventSink?(["url": url.absoluteString, "referring_application": sourceApplication]) - }else{ - pendingDeeplinkEventsQueue.append(["url": url.absoluteString, "referring_application": sourceApplication]); + _eventSink?(event) + } else { + pendingDeeplinkEventsQueue.append(event) } + } + // UIApplicationDelegate path (apps that have not adopted the UIScene life cycle). + public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool{ + handleDeeplink(url: url, sourceApplication: options[.sourceApplication] as? String) + return false + } + // UIScene path: Flutter forwards both warm URLs and the cold-start launch URL here. + public func scene(_ scene: UIScene, openURLContexts URLContexts: Set) -> Bool { + for context in URLContexts { + handleDeeplink(url: context.url, sourceApplication: context.options.sourceApplication) + } return false } @@ -105,5 +116,6 @@ public class AnalyticsPlugin: NSObject, FlutterPlugin, NativeContextApi, Flutter let channel:FlutterEventChannel = FlutterEventChannel(name: "analytics/deep_link_events", binaryMessenger: registrar.messenger()) channel.setStreamHandler(api) registrar.addApplicationDelegate(plugin) + registrar.addSceneDelegate(plugin) } } diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index d88faa8..c8168d6 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -1,13 +1,13 @@ name: segment_analytics description: The hassle-free way to add Segment analytics to your Flutter app. -version: 1.1.12 +version: 1.2.0 homepage: https://github.com/segmentio/analytics_flutter#readme repository: https://github.com/segmentio/analytics_flutter/tree/main/packages/core#readme issue_tracker: https://github.com/segmentio/analytics_flutter/issues environment: sdk: '>=3.2.0 <4.0.0' - flutter: ">=3.16.0" + flutter: ">=3.38.0" dependencies: flutter: