Add SwiftUI support for UIScene migration (#176230)

When embedding Flutter in a SwiftUI app, it appears that SwiftUI wraps
the scene delegate given by the developer in an internal class called
`SwiftUI.AppSceneDelegate`. This causes the scene delegate to fail to
check if it conforms to the `FlutterSceneLifeCycleProvider` protocol -
even though it does. However, after force casting it, selectors respond
and can be used.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Victoria Ashworth 2025-10-01 09:24:37 -05:00 committed by GitHub
parent 72eec03e86
commit 80496d7662
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1119 additions and 69 deletions

View File

@ -27,9 +27,11 @@ import 'package:path/path.dart' as path;
Future<void> main(List<String> args) async {
const String kDestination = 'destination';
const String kTestName = 'name';
const String kXcodeProjecType = 'type';
final ArgParser argParser = ArgParser()
..addOption(kDestination)
..addOption(kTestName);
..addOption(kTestName)
..addOption(kXcodeProjecType);
await task(() async {
final ArgResults argResults = argParser.parse(args);
@ -49,6 +51,18 @@ Future<void> main(List<String> args) async {
final String? testName = argResults.option(kTestName);
XcodeProjectType? projectType;
final String? projectTypeName = argResults.option(kXcodeProjecType);
if (projectTypeName?.toLowerCase() == 'swiftui') {
projectType = XcodeProjectType.SwiftUI;
} else if (projectTypeName?.toLowerCase() == 'uikit-swift') {
projectType = XcodeProjectType.UIKitSwift;
}
List<XcodeProjectType> projectTypesToTest = XcodeProjectType.values;
if (projectType != null) {
projectTypesToTest = <XcodeProjectType>[projectType];
}
String? simulatorDeviceId;
final Directory templatesDir = Directory(
path.join(flutterDirectory.path, 'dev', 'integration_tests', 'ios_add2app_uiscene'),
@ -63,50 +77,56 @@ Future<void> main(List<String> args) async {
destinationDir: destinationDir,
templatesDir: templatesDir,
);
final Directory xcodeProjectDir = await _createNativeApp(
destinationDir: destinationDir,
templatesDir: templatesDir,
xcodeProjectType: XcodeProjectType.UIKitSwift,
);
bool testFailed = false;
await testWithNewIOSSimulator('TestAdd2AppSim', (String deviceId) async {
simulatorDeviceId = deviceId;
final Scenarios scenarios = Scenarios();
for (final String scenarioName in scenarios.scenarios.keys) {
if (testName != null && scenarioName != testName) {
continue;
}
final List<FileReplacements> replacements = FileReplacements.fromScenario(
scenarios.scenarios[scenarioName]!,
for (final XcodeProjectType xcodeProjectType in projectTypesToTest) {
final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp(
destinationDir: destinationDir,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
pluginDir: pluginDir,
appDir: appDir,
xcodeProjectType: xcodeProjectType,
);
for (final FileReplacements replacement in replacements) {
replacement.replace();
}
section('Test Scenario $scenarioName');
await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir);
final int result = await _testNativeApp(
deviceId: simulatorDeviceId!,
scenarioName: scenarioName,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
simulatorDeviceId = deviceId;
final Scenarios scenarios = Scenarios();
final Map<String, Map<String, String>> scenariosMap = scenarios.scenarios(
xcodeProjectType,
);
if (result != 0) {
testFailed = true;
}
for (final String scenarioName in scenariosMap.keys) {
if (testName != null && scenarioName != testName) {
continue;
}
final List<FileReplacements> replacements = FileReplacements.fromScenario(
scenariosMap[scenarioName]!,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
pluginDir: pluginDir,
appDir: appDir,
);
// Reset files to original between scenarios unless we're targetting a specific test.
if (testName == null) {
for (final FileReplacements replacement in replacements) {
replacement.reset();
replacement.replace();
}
section('Test Scenario $scenarioName');
await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir);
final int result = await _testNativeApp(
deviceId: simulatorDeviceId!,
scenarioName: scenarioName,
templatesDir: templatesDir,
xcodeProjectDir: xcodeProjectDir,
xcodeProjectName: xcodeProjectName,
);
if (result != 0) {
testFailed = true;
}
// Reset files to original between scenarios unless we're targetting a specific test.
if (testName == null) {
for (final FileReplacements replacement in replacements) {
replacement.reset();
}
}
}
}
@ -167,7 +187,7 @@ Future<Directory> _createFlutterPlugin({
return Directory(path.join(destinationDir.path, pluginName));
}
Future<Directory> _createNativeApp({
Future<(String, Directory)> _createNativeApp({
required Directory templatesDir,
required Directory destinationDir,
required XcodeProjectType xcodeProjectType,
@ -179,12 +199,8 @@ Future<Directory> _createNativeApp({
switch (xcodeProjectType) {
case XcodeProjectType.UIKitSwift:
xcodeProjectName = 'xcode_uikit_swift';
case XcodeProjectType.UIKitObjC:
// TODO(vashworth): add Objective C integration test
throw UnimplementedError();
case XcodeProjectType.SwiftUI:
// TODO(vashworth): add SwiftUI integration test
throw UnimplementedError();
xcodeProjectName = 'xcode_swiftui';
}
// Copy Xcode project
final Directory xcodeProjectDir = Directory(path.join(destinationDir.path, xcodeProjectName));
@ -192,7 +208,7 @@ Future<Directory> _createNativeApp({
final Directory xcodeProjectTemplate = Directory(path.join(templatesDir.path, xcodeProjectName));
recursiveCopy(xcodeProjectTemplate, xcodeProjectDir);
return xcodeProjectDir;
return (xcodeProjectName, xcodeProjectDir);
}
Future<void> _installPlugins({
@ -279,6 +295,7 @@ Future<int> _testNativeApp({
required String scenarioName,
required Directory xcodeProjectDir,
required String deviceId,
required String xcodeProjectName,
}) async {
final String resultBundleTemp = Directory.systemTemp
.createTempSync('flutter_module_test_ios_xcresult.')
@ -288,9 +305,9 @@ Future<int> _testNativeApp({
'xcodebuild',
<String>[
'-workspace',
'xcode_uikit_swift.xcworkspace',
'$xcodeProjectName.xcworkspace',
'-scheme',
'xcode_uikit_swift',
xcodeProjectName,
'-configuration',
'Debug',
'-destination',
@ -332,17 +349,26 @@ Future<void> _uploadTestResults({
}
}
enum XcodeProjectType { UIKitSwift, UIKitObjC, SwiftUI }
enum XcodeProjectType { UIKitSwift, SwiftUI }
class Scenarios {
Scenarios();
Map<String, Map<String, String>> scenarios(XcodeProjectType projectType) {
switch (projectType) {
case XcodeProjectType.UIKitSwift:
return uiKitSwiftScenarios;
case XcodeProjectType.SwiftUI:
return swiftUIScenarios;
}
}
/// A map of scenario names to a map of file replacements.
///
/// Each scenario is a different configuration for testing the Flutter module
/// in a native iOS app. The file replacements are used to set up the
/// specific configuration for each scenario.
late Map<String, Map<String, String>> scenarios = <String, Map<String, String>>{
late Map<String, Map<String, String>> uiKitSwiftScenarios = <String, Map<String, String>>{
// When both the app and the plugin have migrated to scenes, we expect scene events.
'AppMigrated-FlutterSceneDelegate-PluginMigrated': <String, String>{
...sharedLifecycleFiles,
@ -412,6 +438,37 @@ class Scenarios {
},
};
late Map<String, Map<String, String>> swiftUIScenarios = <String, Map<String, String>>{
'SwiftUI-FlutterSceneDelegate': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist':
r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist',
r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneDelegate.swift':
r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift',
r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift':
r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift',
},
'SwiftUI-FlutterSceneLifeCycleProvider': <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,
r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist':
r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist',
r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift':
r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift',
r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift':
r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift',
r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift':
r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift',
r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift':
r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift',
},
};
late Map<String, String> sharedLifecycleFiles = <String, String>{
...sharedAppLifecycleFiles,
...sharedPluginLifecycleFiles,

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,29 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import SwiftUI
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
@Environment(AppDelegate.self) var appDelegate
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(
engine: appDelegate.flutterEngine,
nibName: nil,
bundle: nil
)
}
func updateUIViewController(
_ uiViewController: UIViewControllerType,
context: Context
) {}
}
struct ContentView: View {
var body: some View {
FlutterViewControllerRepresentable()
}
}

View File

@ -0,0 +1,46 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import FlutterPluginRegistrant
import SwiftUI
@Observable
class AppDelegate: FlutterAppDelegate {
let flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication
.LaunchOptionsKey: Any]?
) -> Bool {
flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine)
return true
}
override func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: nil,
sessionRole: connectingSceneSession.role
)
configuration.delegateClass = FlutterSceneDelegate.self
return configuration
}
}
@main
struct xcode_swiftuiApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import FlutterPluginRegistrant
import SwiftUI
@Observable
class AppDelegate: FlutterAppDelegate {
let flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication
.LaunchOptionsKey: Any]?
) -> Bool {
flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine)
return true
}
override func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: nil,
sessionRole: connectingSceneSession.role
)
configuration.delegateClass = SceneDelegate.self
return configuration
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate,
FlutterSceneLifeCycleProvider
{
var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate =
FlutterPluginSceneLifeCycleDelegate()
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard (scene as? UIWindowScene) != nil else { return }
sceneLifeCycleDelegate.scene(
scene,
willConnectTo: session,
options: connectionOptions
)
}
func sceneDidDisconnect(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneDidDisconnect(scene)
}
func sceneWillEnterForeground(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneWillEnterForeground(scene)
}
func sceneDidBecomeActive(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneDidBecomeActive(scene)
}
func sceneWillResignActive(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneWillResignActive(scene)
}
func sceneDidEnterBackground(_ scene: UIScene) {
sceneLifeCycleDelegate.sceneDidEnterBackground(scene)
}
func scene(
_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>
) {
sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
sceneLifeCycleDelegate.scene(scene, continue: userActivity)
}
func windowScene(
_ windowScene: UIWindowScene,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
sceneLifeCycleDelegate.windowScene(
windowScene,
performActionFor: shortcutItem,
completionHandler: completionHandler
)
}
}
@main
struct xcode_swiftuiApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import XCTest
final class xcode_uikit_swiftUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLifecycleEvents() throws {
let app = XCUIApplication()
app.launch()
let button = app.buttons["Get Lifecycle Events"].firstMatch
XCTAssertTrue(button.waitForExistence(timeout: 5))
button.tap()
let expectedStartEvents = [
"sceneWillConnect",
"sceneWillEnterForeground", "sceneDidBecomeActive",
]
let startEventsPredicate = NSPredicate(
format: "label == %@",
expectedStartEvents.joined(separator: "\n")
)
let startEventsElement = app.staticTexts.element(
matching: startEventsPredicate
)
XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5))
// Background the app, then reactivate it and check the events again
XCUIDevice.shared.press(.home)
app.activate()
XCTAssertTrue(button.waitForExistence(timeout: 5))
button.tap()
let expectedEventsAfterBackgroundAndReactivate = [
"sceneWillConnect",
"sceneWillEnterForeground", "sceneDidBecomeActive",
"sceneWillResignActive", "sceneDidEnterBackground",
"sceneWillEnterForeground", "sceneDidBecomeActive",
]
let backgroundEventsPredicate = NSPredicate(
format: "label == %@",
expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n")
)
let backgroundEventsElement = app.staticTexts.element(
matching: backgroundEventsPredicate
)
XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5))
}
}

View File

@ -0,0 +1,22 @@
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
flutter_application_path = '../my_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'xcode_swiftui' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for xcode_swiftui
install_all_flutter_pods(flutter_application_path)
target 'xcode_swiftuiUITests' do
# Pods for testing
end
end
post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
end

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@ -0,0 +1,553 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */; };
1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
787D51772E8A1A61002EB011 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 787D51572E8A1A60002EB011 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 787D515E2E8A1A60002EB011;
remoteInfo = xcode_swiftui;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; sourceTree = "<group>"; };
149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui.framework; sourceTree = BUILT_PRODUCTS_DIR; };
36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.release.xcconfig"; sourceTree = "<group>"; };
5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.debug.xcconfig"; sourceTree = "<group>"; };
714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.release.xcconfig"; sourceTree = "<group>"; };
787D515F2E8A1A60002EB011 /* xcode_swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xcode_swiftui.app; sourceTree = BUILT_PRODUCTS_DIR; };
787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xcode_swiftuiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui_xcode_swiftuiUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.debug.xcconfig"; sourceTree = "<group>"; };
BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; sourceTree = "<group>"; };
EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftuiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
787D51612E8A1A60002EB011 /* xcode_swiftui */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = xcode_swiftui;
sourceTree = "<group>";
};
787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = xcode_swiftuiUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
787D515C2E8A1A60002EB011 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
787D51732E8A1A61002EB011 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
3FAB8759C7E0FBF58FE109A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */,
8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */,
EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
787D51562E8A1A60002EB011 = {
isa = PBXGroup;
children = (
787D51612E8A1A60002EB011 /* xcode_swiftui */,
787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */,
787D51602E8A1A60002EB011 /* Products */,
E283866923164D07FF92228F /* Pods */,
3FAB8759C7E0FBF58FE109A5 /* Frameworks */,
);
sourceTree = "<group>";
};
787D51602E8A1A60002EB011 /* Products */ = {
isa = PBXGroup;
children = (
787D515F2E8A1A60002EB011 /* xcode_swiftui.app */,
787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
E283866923164D07FF92228F /* Pods */ = {
isa = PBXGroup;
children = (
5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */,
714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */,
0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */,
BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */,
8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */,
36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
787D515E2E8A1A60002EB011 /* xcode_swiftui */ = {
isa = PBXNativeTarget;
buildConfigurationList = 787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */;
buildPhases = (
6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */,
787D515B2E8A1A60002EB011 /* Sources */,
787D515C2E8A1A60002EB011 /* Frameworks */,
787D515D2E8A1A60002EB011 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
787D51612E8A1A60002EB011 /* xcode_swiftui */,
);
name = xcode_swiftui;
productName = xcode_swiftui;
productReference = 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */;
productType = "com.apple.product-type.application";
};
787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */;
buildPhases = (
B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */,
787D51722E8A1A61002EB011 /* Sources */,
787D51732E8A1A61002EB011 /* Frameworks */,
787D51742E8A1A61002EB011 /* Resources */,
);
buildRules = (
);
dependencies = (
787D51782E8A1A61002EB011 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */,
);
name = xcode_swiftuiUITests;
productName = xcode_swiftuiUITests;
productReference = 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
787D51572E8A1A60002EB011 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 2600;
TargetAttributes = {
787D515E2E8A1A60002EB011 = {
CreatedOnToolsVersion = 26.0;
};
787D51752E8A1A61002EB011 = {
CreatedOnToolsVersion = 26.0;
TestTargetID = 787D515E2E8A1A60002EB011;
};
};
};
buildConfigurationList = 787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 787D51562E8A1A60002EB011;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 787D51602E8A1A60002EB011 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
787D515E2E8A1A60002EB011 /* xcode_swiftui */,
787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
787D515D2E8A1A60002EB011 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
787D51742E8A1A61002EB011 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-xcode_swiftuiUITests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
787D515B2E8A1A60002EB011 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
787D51722E8A1A61002EB011 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
787D51782E8A1A61002EB011 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 787D515E2E8A1A60002EB011 /* xcode_swiftui */;
targetProxy = 787D51772E8A1A61002EB011 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
787D517E2E8A1A61002EB011 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
787D517F2E8A1A61002EB011 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
787D51812E8A1A61002EB011 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "xcode-swiftui-Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
787D51822E8A1A61002EB011 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "xcode-swiftui-Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
787D51872E8A1A61002EB011 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = S8QB4VV633;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = xcode_swiftui;
};
name = Debug;
};
787D51882E8A1A61002EB011 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = S8QB4VV633;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = xcode_swiftui;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */ = {
isa = XCConfigurationList;
buildConfigurations = (
787D517E2E8A1A61002EB011 /* Debug */,
787D517F2E8A1A61002EB011 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */ = {
isa = XCConfigurationList;
buildConfigurations = (
787D51812E8A1A61002EB011 /* Debug */,
787D51822E8A1A61002EB011 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
787D51872E8A1A61002EB011 /* Debug */,
787D51882E8A1A61002EB011 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 787D51572E8A1A60002EB011 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:xcode_swiftui.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}

View File

@ -0,0 +1,14 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import SwiftUI
@main
struct xcode_swiftuiApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@ -284,10 +284,10 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
// plugins to receive the `scene:willConnectToSession:options` event.
// If we want to support multi-window on iPad later, we may need to add a way for deveopers to
// register their FlutterEngine to the scene manually during this event.
if ([scene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
NSObject<FlutterSceneLifeCycleProvider>* sceneProvider =
(NSObject<FlutterSceneLifeCycleProvider>*)scene.delegate;
[sceneProvider.sceneLifeCycleDelegate engine:self receivedConnectNotificationFor:scene];
FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate =
[FlutterPluginSceneLifeCycleDelegate fromScene:scene];
if (sceneLifeCycleDelegate != nil) {
return [sceneLifeCycleDelegate engine:self receivedConnectNotificationFor:scene];
}
}
}

View File

@ -252,6 +252,28 @@ FLUTTER_ASSERT_ARC
}
return consumedByPlugin;
}
+ (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene {
if ([scene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
NSObject<FlutterSceneLifeCycleProvider>* sceneProvider =
(NSObject<FlutterSceneLifeCycleProvider>*)scene.delegate;
return sceneProvider.sceneLifeCycleDelegate;
}
// When embedded in a SwiftUI app, the scene delegate does not conform to
// FlutterSceneLifeCycleProvider even if it does. However, after force casting it,
// selectors respond and can be used.
NSObject<FlutterSceneLifeCycleProvider>* sceneProvider =
(NSObject<FlutterSceneLifeCycleProvider>*)scene.delegate;
if ([sceneProvider respondsToSelector:@selector(sceneLifeCycleDelegate)]) {
id sceneLifeCycleDelegate = sceneProvider.sceneLifeCycleDelegate;
// Double check that the selector is the expected class.
if ([sceneLifeCycleDelegate isKindOfClass:[FlutterPluginSceneLifeCycleDelegate class]]) {
return (FlutterPluginSceneLifeCycleDelegate*)sceneLifeCycleDelegate;
}
}
return nil;
}
@end
@implementation FlutterEnginePluginSceneLifeCycleDelegate {
@ -286,11 +308,10 @@ FLUTTER_ASSERT_ARC
for (NSObject<FlutterSceneLifeCycleDelegate>* delegate in _delegates.allObjects) {
if ([delegate respondsToSelector:_cmd]) {
// If this event has already been consumed by a plugin, send the event with nil options.
if (consumedByPlugin) {
[delegate scene:scene willConnectToSession:session options:nil];
continue;
} else if ([delegate scene:scene willConnectToSession:session options:connectionOptions]) {
// Only allow one plugin to process this event.
// Only allow one plugin to process the connection options.
if ([delegate scene:scene
willConnectToSession:session
options:(consumedByPlugin ? nil : connectionOptions)]) {
consumedByPlugin = YES;
}
}

View File

@ -5,6 +5,7 @@
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#include <objc/NSObject.h>
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
@ -13,6 +14,24 @@
FLUTTER_ASSERT_ARC
// This class is meant to mimic the SceneDelegate class generated by SwiftUI that does not conform
// FlutterSceneLifeCycleProvider but actually does.
@interface FlutterSwiftUIAppSceneDelegate : NSObject
@property(nonatomic, strong) FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate;
@end
@implementation FlutterSwiftUIAppSceneDelegate
@synthesize sceneLifeCycleDelegate = _sceneLifeCycleDelegate;
- (instancetype)init {
if (self = [super init]) {
_sceneLifeCycleDelegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init];
}
return self;
}
@end
@interface FlutterSceneLifecycleTest : XCTestCase
@end
@ -799,4 +818,15 @@ FLUTTER_ASSERT_ARC
completionHandler:handler]);
}
- (void)testFlutterPluginSceneLifeCycleDelegateFromScene {
id mockScene = OCMClassMock([UIWindowScene class]);
id mockSceneDelegate = OCMClassMock([FlutterSwiftUIAppSceneDelegate class]);
id mockSceneLifeCycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]);
OCMStub([mockScene delegate]).andReturn(mockSceneDelegate);
OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockSceneLifeCycleDelegate);
XCTAssertEqual([FlutterPluginSceneLifeCycleDelegate fromScene:mockScene],
mockSceneLifeCycleDelegate);
}
@end

View File

@ -20,6 +20,8 @@
- (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene;
+ (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene;
@end
/**

View File

@ -250,21 +250,21 @@ static void PrintWideGamutWarningOnce() {
if (newScene == previousScene) {
return;
}
if ([newScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
(id<FlutterSceneLifeCycleProvider>)newScene.delegate;
[lifeCycleProvider.sceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate];
FlutterPluginSceneLifeCycleDelegate* newSceneLifeCycleDelegate =
[FlutterPluginSceneLifeCycleDelegate fromScene:newScene];
if (newSceneLifeCycleDelegate != nil) {
return [newSceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate];
}
if ([previousScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) {
// The window, and therefore windowScene, property may be nil if the receiver does not currently
// reside in any window. This occurs when the receiver has just been removed from its superview
// or when the receiver has just been added to a superview that is not attached to a window.
// Remove the engine from the previous scene if set since it is no longer in that window and
// scene.
id<FlutterSceneLifeCycleProvider> lifeCycleProvider =
(id<FlutterSceneLifeCycleProvider>)previousScene.delegate;
[lifeCycleProvider.sceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate];
// The window, and therefore windowScene, property may be nil if the receiver does not currently
// reside in any window. This occurs when the receiver has just been removed from its superview
// or when the receiver has just been added to a superview that is not attached to a window.
// Remove the engine from the previous scene if set since it is no longer in that window and
// scene.
FlutterPluginSceneLifeCycleDelegate* previousSceneLifeCycleDelegate =
[FlutterPluginSceneLifeCycleDelegate fromScene:previousScene];
if (previousSceneLifeCycleDelegate != nil) {
return [previousSceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate];
}
}