diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index 7a5219fb71927..c0950360546ba 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -11,6 +11,7 @@ #import "FlutterBinaryMessenger.h" #import "FlutterDartProject.h" #import "FlutterEngine.h" +#import "FlutterEngineGroup.h" #import "FlutterMacros.h" #import "FlutterPlugin.h" #import "FlutterTexture.h" @@ -68,6 +69,23 @@ FLUTTER_DARWIN_EXPORT nibName:(nullable NSString*)nibName bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER; +/** + * Initializes this FlutterViewController with the specified `FlutterEngineGroup` + * + * FlutterViewController will create an new `FlutterEngine` with `options` and use this engine do + * the same thing like initWithEngine:nibName:bundle:, and when this `FlutterViewController` + * pop,the engine will be gone. + * + * @param engineGroup The specified `FlutterEngineGroup` to use to create new `FlutterEngine`. + * @param options The specified options to give FlutterEngineGroup to create new `FlutterEngine`. + * @param nibName The NIB name to initialize this UIViewController with. + * @param nibBundle The NIB bundle. + */ +- (instancetype)initWithEngineGroup:(FlutterEngineGroup*)engineGroup + options:(nullable FlutterEngineGroupOptions*)options + nibName:(nullable NSString*)nibName + bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER; + /** * Initializes a new FlutterViewController and `FlutterEngine` with the specified * `FlutterDartProject`. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 01695e8e2f1b9..9a543f3ba52a6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -133,25 +133,21 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine NSAssert(engine != nil, @"Engine is required"); self = [super initWithNibName:nibName bundle:nibBundle]; if (self) { - _viewOpaque = YES; - if (engine.viewController) { - FML_LOG(ERROR) << "The supplied FlutterEngine " << [[engine description] UTF8String] - << " is already used with FlutterViewController instance " - << [[engine.viewController description] UTF8String] - << ". One instance of the FlutterEngine can only be attached to one " - "FlutterViewController at a time. Set FlutterEngine.viewController " - "to nil before attaching it to another FlutterViewController."; - } - _engine.reset([engine retain]); - _engineNeedsLaunch = NO; - _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]); - _weakFactory = std::make_unique>(self); - _ongoingTouches.reset([[NSMutableSet alloc] init]); - - [self performCommonViewControllerInitialization]; - [engine setViewController:self]; + [self setupEngine:engine]; } + return self; +} +- (instancetype)initWithEngineGroup:(FlutterEngineGroup*)engineGroup + options:(nullable FlutterEngineGroupOptions*)options + nibName:(nullable NSString*)nibName + bundle:(nullable NSBundle*)nibBundle { + NSAssert(engineGroup != nil, @"FlutterEngineGroup is required"); + self = [super initWithNibName:nibName bundle:nibBundle]; + if (self) { + FlutterEngine* engine = [engineGroup makeEngineWithOptions:options]; + [self setupEngine:engine]; + } return self; } @@ -198,6 +194,26 @@ - (instancetype)init { return [self initWithProject:nil nibName:nil bundle:nil]; } +- (void)setupEngine:(FlutterEngine*)engine { + _viewOpaque = YES; + if (engine.viewController) { + FML_LOG(ERROR) << "The supplied FlutterEngine " << [[engine description] UTF8String] + << " is already used with FlutterViewController instance " + << [[engine.viewController description] UTF8String] + << ". One instance of the FlutterEngine can only be attached to one " + "FlutterViewController at a time. Set FlutterEngine.viewController " + "to nil before attaching it to another FlutterViewController."; + } + _engine.reset([engine retain]); + _engineNeedsLaunch = NO; + _flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]); + _weakFactory = std::make_unique>(self); + _ongoingTouches.reset([[NSMutableSet alloc] init]); + + [self performCommonViewControllerInitialization]; + [engine setViewController:self]; +} + - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project initialRoute:(nullable NSString*)initialRoute { // Need the project to get settings for the view. Initializing it here means diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 2c462e479c56d..f4772c69811b5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -80,6 +80,11 @@ @interface FlutterEngine (TestLowMemory) - (void)notifyLowMemory; @end +@interface FlutterEngineGroup () +@property(nonatomic, strong) NSMutableArray* engines; +- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options; +@end + extern NSNotificationName const FlutterViewControllerWillDealloc; /// A simple mock class for FlutterEngine. @@ -155,6 +160,19 @@ - (void)tearDown { self.messageSent = nil; } +- (void)testViewControllerInitWithEngineGroupWithEngineGroupOptions { + FlutterEngineGroup* engineGroup = + OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"io.flutter" project:nil]); + FlutterEngineGroupOptions* options = [[FlutterEngineGroupOptions alloc] init]; + + FlutterViewController* controller = [[FlutterViewController alloc] initWithEngineGroup:engineGroup + options:options + nibName:nil + bundle:nil]; + OCMStub([[engineGroup engines] firstObject]).andReturn(controller.engine); + OCMVerify([engineGroup makeEngineWithOptions:options]); +} + - (void)testkeyboardWillChangeFrameWillStartKeyboardAnimation { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];