[Reland2] Implements UISceneDelegate dynamically w/ FlutterLaunchEngine (#169276)

## **BREAKING CHANGE**

Adopting Apple's UISceneDelegate protocol shifts the initialization
order of apps. For the common cases we've made sure they will work
without change. The one case that will require a change is any app that
in `-[UIApplicateDelegate didFinishLaunchingWithOptions:]` assumes that
`UIApplicationDelegate.window.rootViewController` is a
`FlutterViewController` instance. This is sometimes done to register
platform channels directly on the `FlutterViewController`. Instead users
should use the `FlutterPluginRegistry` API's to create platform channels
in `-[UIApplicateDelegate didFinishLaunchingWithOptions:]`, like
`FlutterPlugin`s do.

An example can be seen here:

https://github.com/flutter/flutter/pull/168914/files#diff-9f59c5248b58124beca7e290a57646023cda3ca024607092c6c6932606ce16ee

In extreme cases, like bespoke test harnesses, the startup logic can be
moved to `-[FlutterViewController awakeFromNib]` in a
FlutterViewController subclass.

An example can be seen here:

https://github.com/flutter/flutter/pull/169276/files#diff-dbe39c23a0a380447b90b7559a878dae8564616e0875c4ef0d9e99e02b91adac

## Changes since revert
I changed the init in `//dev/integration_tests/external_textures` from
the UIApplicationDelegate to the FlutterViewController's awakeFromNib.
This is a more appropriate place for initialization post-UISceneDelegate
since it avoids the launch engine altogether. I tried avoiding to make
the big change to prove we could do a small change to migrate that
project. I don't think this big refactor is indicative of what users
will experience. There was a timing assumption in the integration test
that required not using the launch engine at all.

## Description
fixes: https://github.com/flutter/flutter/issues/167267

design doc:


https://docs.google.com/document/d/1ZfcQOs-UKRa9jsFG84-MTFeibZTLKCvPQLxF2eskx44/edit?tab=t.0

relands https://github.com/flutter/flutter/pull/168396
relands https://github.com/flutter/flutter/pull/168914

## 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.
This commit is contained in:
gaaclarke 2025-05-22 17:44:55 -07:00 committed by GitHub
parent c104ba2a4b
commit 4226470eb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 504 additions and 111 deletions

View File

@ -86,38 +86,36 @@ const UInt8 PAIR = 129;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController;
id<FlutterPluginRegistrar> registrar = [self registrarForPlugin:@"platform-channel-test"];
ExtendedReaderWriter* extendedReaderWriter = [ExtendedReaderWriter new];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterBinaryCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"string-msg"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterStringCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"json-msg"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterJSONMessageCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterJSONMethodCodec sharedInstance]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
[[FlutterBasicMessageChannel
messageChannelWithName:@"std-echo"
binaryMessenger:flutterController
binaryMessenger:registrar.messenger
codec:[FlutterStandardMessageCodec
codecWithReaderWriter:extendedReaderWriter]]
setMessageHandler:^(id message, FlutterReply reply) {

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
0D569A532DDF895D00F24F69 /* TextureViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D569A522DDF895D00F24F69 /* TextureViewController.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
@ -28,6 +29,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0D569A522DDF895D00F24F69 /* TextureViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextureViewController.m; sourceTree = "<group>"; };
0D569A542DDF896B00F24F69 /* TextureViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextureViewController.h; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@ -89,6 +92,8 @@
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
0D569A522DDF895D00F24F69 /* TextureViewController.m */,
0D569A542DDF896B00F24F69 /* TextureViewController.h */,
);
path = Runner;
sourceTree = "<group>";
@ -210,6 +215,7 @@
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
0D569A532DDF895D00F24F69 /* TextureViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -281,9 +287,13 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.externalUi;
PRODUCT_NAME = "$(TARGET_NAME)";
};
@ -382,9 +392,13 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.externalUi;
PRODUCT_NAME = "$(TARGET_NAME)";
};
@ -395,9 +409,13 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = io.flutter.externalUi;
PRODUCT_NAME = "$(TARGET_NAME)";
};

View File

@ -5,6 +5,5 @@
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate<FlutterTexture>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -4,73 +4,5 @@
#import "AppDelegate.h"
@interface AppDelegate ()
@property (atomic) uint64_t textureId;
@property (atomic) int framesProduced;
@property (atomic) int framesConsumed;
@property (atomic) int lastFrameConsumed;
@property (atomic) double startTime;
@property (atomic) double endTime;
@property (atomic) double frameRate;
@property (atomic) double frameStartTime;
@property (atomic) NSTimer* timer;
- (void)tick:(NSTimer*)timer;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
FlutterViewController* flutterController =
(FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* channel =
[FlutterMethodChannel methodChannelWithName:@"texture"
binaryMessenger:flutterController];
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"start" isEqualToString:call.method]) {
_framesProduced = 0;
_framesConsumed = 0;
_frameRate = 1.0 / [(NSNumber*) call.arguments intValue];
_timer = [NSTimer scheduledTimerWithTimeInterval:_frameRate
target:self
selector:@selector(tick:)
userInfo:nil
repeats:YES];
_startTime = [[NSDate date] timeIntervalSince1970];
result(nil);
} else if ([@"stop" isEqualToString:call.method]) {
[_timer invalidate];
_endTime = [[NSDate date] timeIntervalSince1970];
result(nil);
} else if ([@"getProducedFrameRate" isEqualToString:call.method]) {
result(@(_framesProduced / (_endTime - _startTime)));
} else if ([@"getConsumedFrameRate" isEqualToString:call.method]) {
result(@(_framesConsumed / (_endTime - _startTime)));
} else {
result(FlutterMethodNotImplemented);
}
}];
_textureId = [flutterController registerTexture:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)tick:(NSTimer*)timer {
FlutterViewController* flutterController =
(FlutterViewController*)self.window.rootViewController;
[flutterController textureFrameAvailable:_textureId];
_frameStartTime = [[NSDate date] timeIntervalSince1970];
// We just pretend to be producing a frame.
_framesProduced++;
}
- (CVPixelBufferRef)copyPixelBuffer {
double now = [[NSDate date] timeIntervalSince1970];
if (now < _frameStartTime
|| _frameStartTime + _frameRate < now
|| _framesProduced == _lastFrameConsumed) return nil;
_framesConsumed++;
_lastFrameConsumed = _framesProduced;
// We just pretend to be handing over the produced frame to the consumer.
return nil;
}
@end

View File

@ -1,26 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<!--Texture View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="TextureViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="132" y="-34"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,8 @@
// 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/Flutter.h>
@interface TextureViewController : FlutterViewController
@end

View File

@ -0,0 +1,75 @@
// 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 "TextureViewController.h"
#import "AppDelegate.h"
@interface TextureViewController () <FlutterTexture>
@property (nonatomic, assign) uint64_t textureId;
@property (nonatomic, assign) int framesProduced;
@property (nonatomic, assign) int framesConsumed;
@property (nonatomic, assign) int lastFrameConsumed;
@property (nonatomic, assign) double startTime;
@property (nonatomic, assign) double endTime;
@property (nonatomic, assign) double frameRate;
@property (nonatomic, assign) double frameStartTime;
@property (nonatomic, strong) NSTimer* timer;
- (void)tick:(NSTimer*)timer;
@end
@implementation TextureViewController
- (void)awakeFromNib {
[super awakeFromNib];
FlutterMethodChannel* channel =
[FlutterMethodChannel methodChannelWithName:@"texture"
binaryMessenger:self.binaryMessenger];
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"start" isEqualToString:call.method]) {
_framesProduced = 0;
_framesConsumed = 0;
_frameRate = 1.0 / [(NSNumber*) call.arguments intValue];
_timer = [NSTimer scheduledTimerWithTimeInterval:_frameRate
target:self
selector:@selector(tick:)
userInfo:nil
repeats:YES];
_startTime = [[NSDate date] timeIntervalSince1970];
result(nil);
} else if ([@"stop" isEqualToString:call.method]) {
[_timer invalidate];
_endTime = [[NSDate date] timeIntervalSince1970];
result(nil);
} else if ([@"getProducedFrameRate" isEqualToString:call.method]) {
result(@(_framesProduced / (_endTime - _startTime)));
} else if ([@"getConsumedFrameRate" isEqualToString:call.method]) {
result(@(_framesConsumed / (_endTime - _startTime)));
} else {
result(FlutterMethodNotImplemented);
}
}];
_textureId = [self registerTexture:self];
}
- (void)tick:(NSTimer*)timer {
[self textureFrameAvailable:_textureId];
_frameStartTime = [[NSDate date] timeIntervalSince1970];
// We just pretend to be producing a frame.
_framesProduced++;
}
- (CVPixelBufferRef)copyPixelBuffer {
double now = [[NSDate date] timeIntervalSince1970];
if (now < _frameStartTime
|| _frameStartTime + _frameRate < now
|| _framesProduced == _lastFrameConsumed) return nil;
_framesConsumed++;
_lastFrameConsumed = _framesProduced;
// We just pretend to be handing over the produced frame to the consumer.
return nil;
}
@end

View File

@ -52778,6 +52778,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/ConnectionCo
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FakeUIPressProxy.swift + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h + ../../../flutter/LICENSE
@ -52808,6 +52809,9 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySe
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.m + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngineTest.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm + ../../../flutter/LICENSE
@ -55804,6 +55808,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/ConnectionColl
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FakeUIPressProxy.swift
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h
@ -55834,6 +55839,9 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySeco
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManagerTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.m
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngineTest.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayerTest.mm

View File

@ -425,6 +425,37 @@
"ios_debug_unopt"
]
},
{
"type": "shell",
"command": "./flutter/bin/et",
"options": {
"cwd": "${workspaceFolder}/.."
},
"problemMatcher": [
"$gcc"
],
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"clear": true
},
"group": {
"kind": "build"
},
"label": "ios_debug_sim_unopt_arm64",
"args": [
"build",
"-c",
"host_debug_unopt_arm64",
"&&",
"./flutter/bin/et",
"build",
"-c",
"ios_debug_sim_unopt_arm64"
]
},
{
"type": "shell",
"command": "./flutter/bin/et",

View File

@ -75,6 +75,7 @@ source_set("flutter_framework_source") {
sources = [
"framework/Source/FlutterAppDelegate.mm",
"framework/Source/FlutterAppDelegate_Internal.h",
"framework/Source/FlutterCallbackCache.mm",
"framework/Source/FlutterCallbackCache_Internal.h",
"framework/Source/FlutterChannelKeyResponder.h",
@ -93,6 +94,8 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterKeySecondaryResponder.h",
"framework/Source/FlutterKeyboardManager.h",
"framework/Source/FlutterKeyboardManager.mm",
"framework/Source/FlutterLaunchEngine.h",
"framework/Source/FlutterLaunchEngine.m",
"framework/Source/FlutterMetalLayer.h",
"framework/Source/FlutterMetalLayer.mm",
"framework/Source/FlutterOverlayView.h",
@ -250,6 +253,7 @@ if (enable_ios_unittests) {
"framework/Source/FlutterFakeKeyEvents.h",
"framework/Source/FlutterFakeKeyEvents.mm",
"framework/Source/FlutterKeyboardManagerTest.mm",
"framework/Source/FlutterLaunchEngineTest.mm",
"framework/Source/FlutterMetalLayerTest.mm",
"framework/Source/FlutterPlatformPluginTest.mm",
"framework/Source/FlutterPlatformViewsTest.mm",

View File

@ -9,6 +9,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
@ -20,9 +21,33 @@ static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* const kBackgroundFetchCapatibility = @"fetch";
static NSString* const kRestorationStateAppModificationKey = @"mod-date";
@interface FlutterSceneDelegate : NSObject <UIWindowSceneDelegate>
@property(nonatomic, strong, nullable) UIWindow* window;
@end
@implementation FlutterSceneDelegate
- (void)scene:(UIScene*)scene
willConnectToSession:(UISceneSession*)session
options:(UISceneConnectionOptions*)connectionOptions {
NSObject<UIApplicationDelegate>* appDelegate = FlutterSharedApplication.application.delegate;
if (appDelegate.window.rootViewController) {
// If this is not nil we are running into a case where someone is manually
// performing root view controller setup in the UIApplicationDelegate.
UIWindowScene* windowScene = (UIWindowScene*)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
self.window.rootViewController = appDelegate.window.rootViewController;
appDelegate.window = self.window;
[self.window makeKeyAndVisible];
}
}
@end
@interface FlutterAppDelegate ()
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
@property(nonatomic, strong) FlutterPluginAppLifeCycleDelegate* lifeCycleDelegate;
@property(nonatomic, strong) FlutterLaunchEngine* launchEngine;
@end
@implementation FlutterAppDelegate
@ -30,10 +55,15 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
_launchEngine = [[FlutterLaunchEngine alloc] init];
}
return self;
}
- (nullable FlutterEngine*)takeLaunchEngine {
return [self.launchEngine takeEngine];
}
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [self.lifeCycleDelegate application:application
@ -233,7 +263,7 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
}
return nil;
return [self.launchEngine.engine registrarForPlugin:pluginKey];
}
- (BOOL)hasPlugin:(NSString*)pluginKey {
@ -241,7 +271,7 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
}
return false;
return [self.launchEngine.engine hasPlugin:pluginKey];
}
- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
@ -249,7 +279,7 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
}
return nil;
return [self.launchEngine.engine valuePublishedByPlugin:pluginKey];
}
#pragma mark - Selectors handling
@ -341,4 +371,31 @@ static NSString* const kRestorationStateAppModificationKey = @"mod-date";
return [fileDate timeIntervalSince1970];
}
- (UISceneConfiguration*)application:(UIApplication*)application
configurationForConnectingSceneSession:(UISceneSession*)connectingSceneSession
options:(UISceneConnectionOptions*)options {
NSDictionary* sceneManifest =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationSceneManifest"];
NSDictionary* sceneConfigs = sceneManifest[@"UISceneConfigurations"];
if (sceneConfigs.count > 0) {
return connectingSceneSession.configuration;
} else {
UISceneConfiguration* config =
[UISceneConfiguration configurationWithName:@"flutter"
sessionRole:connectingSceneSession.role];
config.delegateClass = [FlutterSceneDelegate class];
NSString* mainStoryboard =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIMainStoryboardFile"];
if (mainStoryboard) {
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:mainStoryboard
bundle:[NSBundle mainBundle]];
config.storyboard = storyboard;
}
return config;
}
}
@end

View File

@ -8,6 +8,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
@ -154,6 +155,20 @@ FLUTTER_ASSERT_ARC
XCTAssertNil(weakWindow);
}
- (void)testGrabLaunchEngine {
// Clear out the mocking of the root view controller.
[self.mockMainBundle stopMocking];
self.appDelegate.rootFlutterViewControllerGetter = nil;
// Working with plugins forces the creation of an engine.
XCTAssertFalse([self.appDelegate hasPlugin:@"hello"]);
XCTAssertNotNil([self.appDelegate takeLaunchEngine]);
XCTAssertNil([self.appDelegate takeLaunchEngine]);
}
- (void)testGrabLaunchEngineWithoutPlugins {
XCTAssertNil([self.appDelegate takeLaunchEngine]);
}
#pragma mark - Deep linking
- (void)testUniversalLinkPushRouteInformation {

View File

@ -0,0 +1,16 @@
// Copyright 2013 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.
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERAPPDELEGATE_INTERNAL_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERAPPDELEGATE_INTERNAL_H_
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
@interface FlutterAppDelegate ()
- (nullable FlutterEngine*)takeLaunchEngine;
@end
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERAPPDELEGATE_INTERNAL_H_

View File

@ -0,0 +1,40 @@
// Copyright 2013 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.
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERLAUNCHENGINE_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERLAUNCHENGINE_H_
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
/**
* A lazy container for an engine that will only dispense one engine.
*
* This is used to hold an engine for plugin registration when the
* GeneratedPluginRegistrant is called on a FlutterAppDelegate before the first
* FlutterViewController is set up. This is the typical flow after the
* UISceneDelegate migration.
*
* The launch engine is intended to work only with first FlutterViewController
* instantiated with a NIB since that is the only FlutterEngine that registers
* plugins through the FlutterAppDelegate.
*/
@interface FlutterLaunchEngine : NSObject
/**
* Accessor for the launch engine.
*
* Getting this may allocate an engine.
*/
@property(nonatomic, strong, nullable, readonly) FlutterEngine* engine;
/**
* Take ownership of the launch engine.
*
* After this is called `self.engine` and `takeEngine` will always return nil.
*/
- (nullable FlutterEngine*)takeEngine;
@end
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERLAUNCHENGINE_H_

View File

@ -0,0 +1,52 @@
// Copyright 2013 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/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h"
@interface FlutterLaunchEngine () {
BOOL _didTakeEngine;
FlutterEngine* _engine;
}
@end
@implementation FlutterLaunchEngine
- (instancetype)init {
self = [super init];
if (self) {
self->_didTakeEngine = NO;
}
return self;
}
- (FlutterEngine*)engine {
if (!_didTakeEngine && !_engine) {
// `allowHeadlessExecution` is set to `YES` since that has always been the
// default behavior. Technically, someone could have set it to `NO` in their
// nib and it would be ignored here. There is no documented usage of this
// though.
// `restorationEnabled` is set to `YES` since a FlutterViewController
// without restoration will have a nil restorationIdentifier leading no
// restoration data being saved. So, it is safe to turn this on in the event
// that someone does not want it.
_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
project:[[FlutterDartProject alloc] init]
allowHeadlessExecution:YES
restorationEnabled:YES];
// Run engine with default values like initialRoute. Specifying these in
// the FlutterViewController was not supported so it's safe to use the
// defaults.
[_engine run];
}
return _engine;
}
- (nullable FlutterEngine*)takeEngine {
FlutterEngine* result = _engine;
_engine = nil;
_didTakeEngine = YES;
return result;
}
@end

View File

@ -0,0 +1,26 @@
// Copyright 2013 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 <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h"
FLUTTER_ASSERT_ARC;
@interface FlutterLaunchEngineTest : XCTestCase
@end
@implementation FlutterLaunchEngineTest
- (void)testSimple {
FlutterLaunchEngine* launchEngine = [[FlutterLaunchEngine alloc] init];
XCTAssertTrue(launchEngine.engine);
XCTAssertTrue([launchEngine takeEngine]);
XCTAssertFalse(launchEngine.engine);
}
@end

View File

@ -17,11 +17,13 @@
#include "flutter/shell/common/thread_host.h"
#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
#import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterChannelKeyResponder.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyPrimaryResponder.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
@ -255,15 +257,33 @@ typedef struct MouseState {
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
initialRoute:(nullable NSString*)initialRoute {
// Need the project to get settings for the view. Initializing it here means
// the Engine class won't initialize it later.
if (!project) {
project = [[FlutterDartProject alloc] init];
id appDelegate = FlutterSharedApplication.application.delegate;
FlutterEngine* engine;
if ([appDelegate respondsToSelector:@selector(takeLaunchEngine)]) {
if (self.nibName) {
// Only grab the launch engine if it was created with a nib.
// FlutterViewControllers created from nibs can't specify their initial
// routes so it's safe to take it.
engine = [appDelegate takeLaunchEngine];
} else {
// If we registered plugins with a FlutterAppDelegate without a xib, throw
// away the engine that was registered through the FlutterAppDelegate.
// That's not a valid usage of the API.
[appDelegate takeLaunchEngine];
}
}
if (!engine) {
// Need the project to get settings for the view. Initializing it here means
// the Engine class won't initialize it later.
if (!project) {
project = [[FlutterDartProject alloc] init];
}
engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution
restorationEnabled:self.restorationIdentifier != nil];
}
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution
restorationEnabled:self.restorationIdentifier != nil];
if (!engine) {
return;
}
@ -272,7 +292,7 @@ typedef struct MouseState {
_engine = engine;
_flutterView = [[FlutterView alloc] initWithDelegate:_engine
opaque:_viewOpaque
enableWideGamut:project.isWideGamutEnabled];
enableWideGamut:engine.project.isWideGamutEnabled];
[_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
_engineNeedsLaunch = YES;
_ongoingTouches = [[NSMutableSet alloc] init];

View File

@ -13,6 +13,7 @@
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterHourFormat.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h"
@ -118,6 +119,10 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
@property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent;
@end
@interface NSObject (Tests)
@property(nonatomic, strong) FlutterEngine* mockLaunchEngine;
@end
@interface FlutterViewController (Tests)
@property(nonatomic, assign) double targetViewInsetBottom;
@ -2471,4 +2476,28 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
[mockVC stopMocking];
}
- (void)testGrabLaunchEngine {
id appDelegate = [[UIApplication sharedApplication] delegate];
XCTAssertTrue([appDelegate respondsToSelector:@selector(setMockLaunchEngine:)]);
[appDelegate setMockLaunchEngine:self.mockEngine];
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Flutter" bundle:nil];
XCTAssertTrue(storyboard);
FlutterViewController* viewController =
(FlutterViewController*)[storyboard instantiateInitialViewController];
XCTAssertTrue(viewController);
XCTAssertTrue([viewController isKindOfClass:[FlutterViewController class]]);
XCTAssertEqual(viewController.engine, self.mockEngine);
[appDelegate setMockLaunchEngine:nil];
}
- (void)testDoesntGrabLaunchEngine {
id appDelegate = [[UIApplication sharedApplication] delegate];
XCTAssertTrue([appDelegate respondsToSelector:@selector(setMockLaunchEngine:)]);
[appDelegate setMockLaunchEngine:self.mockEngine];
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
XCTAssertNotNil(flutterViewController.engine);
XCTAssertNotEqual(flutterViewController.engine, self.mockEngine);
[appDelegate setMockLaunchEngine:nil];
}
@end

View File

@ -7,9 +7,15 @@
#import <UIKit/UIKit.h>
@class FlutterEngine;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property(strong, nonatomic) UIWindow* window;
/** The FlutterEngine that will be served by `takeLaunchEngine`. */
@property(strong, nonatomic) FlutterEngine* mockLaunchEngine;
- (FlutterEngine*)takeLaunchEngine;
@end

View File

@ -30,4 +30,9 @@
- (void)applicationWillTerminate:(UIApplication*)application {
}
- (FlutterEngine*)takeLaunchEngine {
// This is just served up for tests and doesn't actually take ownership.
return _mockLaunchEngine;
}
@end

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="s0d-6b-0kx">
<objects>
<viewController id="Y6W-OH-hqX" customClass="FlutterViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="132" y="-27"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
0D1CE5D8233430F400E5D880 /* FlutterChannelsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1CE5D7233430F400E5D880 /* FlutterChannelsTest.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
0D48E9ED2DCE7B16005474A1 /* Flutter.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D48E9EC2DCE7B16005474A1 /* Flutter.storyboard */; };
0D48E9EE2DCE7B16005474A1 /* Flutter.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D48E9EC2DCE7B16005474A1 /* Flutter.storyboard */; };
0D6AB6B622BB05E100EEE540 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6AB6B522BB05E100EEE540 /* AppDelegate.m */; };
0D6AB6B922BB05E100EEE540 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6AB6B822BB05E100EEE540 /* ViewController.m */; };
0D6AB6BC22BB05E100EEE540 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D6AB6BA22BB05E100EEE540 /* Main.storyboard */; };
@ -55,6 +57,7 @@
0AC2331924BA71D300A85907 /* FlutterPluginAppLifeCycleDelegateTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterPluginAppLifeCycleDelegateTest.mm; sourceTree = "<group>"; };
0AC2332124BA71D300A85907 /* FlutterViewControllerTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterViewControllerTest.mm; sourceTree = "<group>"; };
0D1CE5D7233430F400E5D880 /* FlutterChannelsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FlutterChannelsTest.m; sourceTree = "<group>"; };
0D48E9EC2DCE7B16005474A1 /* Flutter.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Flutter.storyboard; sourceTree = "<group>"; };
0D6AB6B122BB05E100EEE540 /* IosUnitTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IosUnitTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
0D6AB6B422BB05E100EEE540 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
0D6AB6B522BB05E100EEE540 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@ -162,6 +165,7 @@
0D6AB6BF22BB05E200EEE540 /* LaunchScreen.storyboard */,
0D6AB6C222BB05E200EEE540 /* Info.plist */,
0D6AB6C322BB05E200EEE540 /* main.m */,
0D48E9EC2DCE7B16005474A1 /* Flutter.storyboard */,
);
path = App;
sourceTree = "<group>";
@ -270,6 +274,7 @@
0D6AB6C122BB05E200EEE540 /* LaunchScreen.storyboard in Resources */,
0D6AB6BE22BB05E200EEE540 /* Assets.xcassets in Resources */,
0D6AB6BC22BB05E100EEE540 /* Main.storyboard in Resources */,
0D48E9ED2DCE7B16005474A1 /* Flutter.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -277,6 +282,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0D48E9EE2DCE7B16005474A1 /* Flutter.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -118,6 +118,10 @@ settings:
'*.ipp': cpp
csetjmp: cpp
cfenv: cpp
execution: cpp
print: cpp
source_location: cpp
syncstream: cpp
C_Cpp.default.includePath:
- ${default}
- ${workspaceFolder}/..
@ -252,6 +256,17 @@ tasks:
- build
- -c
- ios_debug_unopt
- <<: *et-task
label: ios_debug_sim_unopt_arm64
args:
- build
- -c
- host_debug_unopt_arm64
- "&&"
- *et-cmd
- build
- -c
- ios_debug_sim_unopt_arm64
- <<: *et-task
label: android_debug_unopt_arm64
args:

View File

@ -13,12 +13,11 @@
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewController;
NSObject<FlutterPluginRegistrar>* registrar = [self registrarForPlugin:@"battery"];
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.io/battery"
binaryMessenger:controller];
binaryMessenger:registrar.messenger];
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call,
FlutterResult result) {
@ -38,7 +37,7 @@
FlutterEventChannel* chargingChannel = [FlutterEventChannel
eventChannelWithName:@"samples.flutter.io/charging"
binaryMessenger:controller];
binaryMessenger:registrar.messenger];
[chargingChannel setStreamHandler:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

View File

@ -27,11 +27,9 @@ enum MyFlutterErrorCode {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self)
guard let controller = window?.rootViewController as? FlutterViewController else {
fatalError("rootViewController is not type FlutterViewController")
}
let registry = self.registrar(forPlugin: "battery")
let batteryChannel = FlutterMethodChannel(name: ChannelName.battery,
binaryMessenger: controller.binaryMessenger)
binaryMessenger: registry!.messenger())
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getBatteryLevel" else {
@ -42,7 +40,7 @@ enum MyFlutterErrorCode {
})
let chargingChannel = FlutterEventChannel(name: ChannelName.charging,
binaryMessenger: controller.binaryMessenger)
binaryMessenger: registry!.messenger())
chargingChannel.setStreamHandler(self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}