5#define FML_USED_ON_EMBEDDER
18#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
51 @"FlutterViewControllerHideHomeIndicator";
53 @"FlutterViewControllerShowHomeIndicator";
69 FlutterKeyboardInsetManagerDelegate>
78@property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
80@property(nonatomic, strong) FlutterSplashScreenManager* splashScreenManager;
82@property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
83@property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
84@property(nonatomic, assign)
BOOL initialized;
85@property(nonatomic, assign)
BOOL engineNeedsLaunch;
86@property(nonatomic, assign)
BOOL awokenFromNib;
89@property(nonatomic, assign)
BOOL isHomeIndicatorHidden;
90@property(nonatomic, assign)
BOOL isPresentingViewControllerAnimating;
93@property(nonatomic, assign)
BOOL flutterPrefersStatusBarHidden;
95@property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
100@property(nonatomic, strong) UIScrollView* scrollView;
105@property(nonatomic, assign)
BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
111@property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
119@property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
127@property(nonatomic, strong) FlutterVSyncClient* touchRateCorrectionVSyncClient;
131@property(nonatomic, assign) CGSize sizeBeforeAutoResized;
137@property(nonatomic, strong)
140@property(nonatomic, strong)
141 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
143@property(nonatomic, strong)
144 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
146@property(nonatomic, strong)
149@property(nonatomic, strong)
150 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
153- (void)addInternalPlugins;
154- (void)deregisterNotifications;
157- (void)onFirstFrameRendered;
170@synthesize viewOpaque = _viewOpaque;
171@synthesize displayingFlutterUI = _displayingFlutterUI;
173- (FlutterSplashScreenManager*)splashScreenManager {
183@dynamic viewIdentifier;
185#pragma mark - Manage and override all designated initializers
188 nibName:(nullable NSString*)nibName
189 bundle:(nullable NSBundle*)nibBundle {
190 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
191 self = [
super initWithNibName:nibName bundle:nibBundle];
195 NSString* errorMessage =
196 [NSString stringWithFormat:
197 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
198 "instance %@. One instance of the FlutterEngine can only be attached to "
199 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
200 "nil before attaching it to another FlutterViewController.",
201 engine.description, engine.viewController.description];
202 [FlutterLogger logError:errorMessage];
205 _engineNeedsLaunch = NO;
206 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
207 opaque:self.isViewOpaque
208 enableWideGamut:engine.project.isWideGamutEnabled];
209 _ongoingTouches = [[NSMutableSet alloc] init];
213 [
self performCommonViewControllerInitialization];
214 [engine setViewController:self];
221 nibName:(NSString*)nibName
222 bundle:(NSBundle*)nibBundle {
223 self = [
super initWithNibName:nibName bundle:nibBundle];
227 [
self sharedSetupWithProject:project initialRoute:nil];
234 initialRoute:(NSString*)initialRoute
235 nibName:(NSString*)nibName
236 bundle:(NSBundle*)nibBundle {
237 self = [
super initWithNibName:nibName bundle:nibBundle];
241 [
self sharedSetupWithProject:project initialRoute:initialRoute];
247- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
248 return [
self initWithProject:nil nibName:nil bundle:nil];
252 self = [
super initWithCoder:aDecoder];
256- (void)awakeFromNib {
257 [
super awakeFromNib];
258 self.awokenFromNib = YES;
260 [
self sharedSetupWithProject:nil initialRoute:nil];
264- (instancetype)init {
265 return [
self initWithProject:nil nibName:nil bundle:nil];
269 initialRoute:(nullable NSString*)initialRoute {
272 if ([appDelegate respondsToSelector:@selector(takeLaunchEngine)]) {
277 engine = [appDelegate takeLaunchEngine];
282 [appDelegate takeLaunchEngine];
294 allowHeadlessExecution:self.engineAllowHeadlessExecution
295 restorationEnabled:self.restorationIdentifier != nil];
303 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
305 enableWideGamut:engine.project.isWideGamutEnabled];
306 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
312 BOOL performedCallback = [_engine performImplicitEngineCallback];
316 respondsToSelector:@selector(pluginRegistrant)]) {
317 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
319 [pluginRegistrant registerWithRegistry:self];
320 performedCallback = YES;
328 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
329 [applicationLifeCycleDelegate
330 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
331 [applicationLifeCycleDelegate
332 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
335 _engineNeedsLaunch = YES;
336 _ongoingTouches = [[NSMutableSet alloc] init];
340 [
self.splashScreenManager loadDefaultSplashScreenView];
341 [
self performCommonViewControllerInitialization];
344- (
BOOL)isViewOpaque {
348- (void)setViewOpaque:(
BOOL)value {
350 if (
self.flutterView.layer.opaque != value) {
352 [
self.flutterView.layer setNeedsLayout];
356#pragma mark - Common view controller initialization tasks
358- (void)performCommonViewControllerInitialization {
364 _orientationPreferences = UIInterfaceOrientationMaskAll;
365 _statusBarStyle = UIStatusBarStyleDefault;
367 _accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
368 _displayLinkManager = FlutterDisplayLinkManager.shared;
369 _keyboardInsetManager =
370 [[FlutterKeyboardInsetManager alloc] initWithDelegate:self
371 displayLinkManager:_displayLinkManager];
375 [
self setUpNotificationCenterObservers];
378- (void)setUpNotificationCenterObservers {
379 NSNotificationCenter*
center = [NSNotificationCenter defaultCenter];
380 [center addObserver:self
381 selector:@selector(onOrientationPreferencesUpdated:)
382 name:@(flutter::kOrientationUpdateNotificationName)
385 [center addObserver:self
386 selector:@selector(onPreferredStatusBarStyleUpdated:)
387 name:@(flutter::kOverlayStyleUpdateNotificationName)
391 [
self setUpApplicationLifecycleNotifications:center];
393 [
self setUpSceneLifecycleNotifications:center];
396 [center addObserver:self
397 selector:@selector(keyboardWillChangeFrame:)
398 name:UIKeyboardWillChangeFrameNotification
401 [center addObserver:self
402 selector:@selector(keyboardWillShowNotification:)
403 name:UIKeyboardWillShowNotification
406 [center addObserver:self
407 selector:@selector(keyboardWillBeHidden:)
408 name:UIKeyboardWillHideNotification
411 for (NSString* notification in [
self.accessibilityFeatures observedNotificationNames]) {
412 [center addObserver:self
413 selector:@selector(onAccessibilityStatusChanged:)
418 [center addObserver:self
419 selector:@selector(onUserSettingsChanged:)
420 name:UIContentSizeCategoryDidChangeNotification
423 [center addObserver:self
424 selector:@selector(onHideHomeIndicatorNotification:)
425 name:FlutterViewControllerHideHomeIndicator
428 [center addObserver:self
429 selector:@selector(onShowHomeIndicatorNotification:)
430 name:FlutterViewControllerShowHomeIndicator
434- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
435 [center addObserver:self
436 selector:@selector(sceneBecameActive:)
437 name:UISceneDidActivateNotification
440 [center addObserver:self
441 selector:@selector(sceneWillResignActive:)
442 name:UISceneWillDeactivateNotification
445 [center addObserver:self
446 selector:@selector(sceneWillDisconnect:)
447 name:UISceneDidDisconnectNotification
450 [center addObserver:self
451 selector:@selector(sceneDidEnterBackground:)
452 name:UISceneDidEnterBackgroundNotification
455 [center addObserver:self
456 selector:@selector(sceneWillEnterForeground:)
457 name:UISceneWillEnterForegroundNotification
461- (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
462 [center addObserver:self
463 selector:@selector(applicationBecameActive:)
464 name:UIApplicationDidBecomeActiveNotification
467 [center addObserver:self
468 selector:@selector(applicationWillResignActive:)
469 name:UIApplicationWillResignActiveNotification
472 [center addObserver:self
473 selector:@selector(applicationWillTerminate:)
474 name:UIApplicationWillTerminateNotification
477 [center addObserver:self
478 selector:@selector(applicationDidEnterBackground:)
479 name:UIApplicationDidEnterBackgroundNotification
482 [center addObserver:self
483 selector:@selector(applicationWillEnterForeground:)
484 name:UIApplicationWillEnterForegroundNotification
488- (void)setInitialRoute:(NSString*)route {
489 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
493 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
496- (void)pushRoute:(NSString*)route {
497 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
500#pragma mark - Loading the view
502static UIView* GetViewOrPlaceholder(UIView* existing_view) {
504 return existing_view;
507 auto placeholder = [[UIView alloc] init];
509 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
510 placeholder.backgroundColor = UIColor.systemBackgroundColor;
511 placeholder.autoresizesSubviews = YES;
517 auto messageLabel = [[UILabel alloc] init];
518 messageLabel.numberOfLines = 0u;
519 messageLabel.textAlignment = NSTextAlignmentCenter;
520 messageLabel.autoresizingMask =
521 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
523 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
524 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
525 @"modes to enable launching from the home screen.";
526 [placeholder addSubview:messageLabel];
533 self.view = GetViewOrPlaceholder(
self.flutterView);
534 self.view.multipleTouchEnabled = YES;
535 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
537 [
self installSplashScreenViewIfNecessary];
540 UIScrollView* scrollView = [[UIScrollView alloc] init];
541 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
543 scrollView.backgroundColor = UIColor.whiteColor;
544 scrollView.delegate =
self;
550 [
self.view addSubview:scrollView];
551 self.scrollView = scrollView;
554- (
flutter::PointerData)generatePointerDataForFake {
556 pointer_data.
Clear();
565- (
BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
569 if (
self.isViewLoaded) {
571 [
self.engine onStatusBarTap];
576#pragma mark - Managing launch views
578- (void)installSplashScreenViewIfNecessary {
584 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
586 [
self.splashScreenView removeFromSuperview];
587 self.splashScreenView = nil;
592 [
self.splashScreenManager installSplashScreenViewAsSubviewOf:self.view];
595+ (
BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
599- (void)setDisplayingFlutterUI:(
BOOL)displayingFlutterUI {
600 if (_displayingFlutterUI != displayingFlutterUI) {
601 if (displayingFlutterUI == YES) {
602 if (!
self.viewIfLoaded.window) {
606 [
self willChangeValueForKey:@"displayingFlutterUI"];
607 _displayingFlutterUI = displayingFlutterUI;
608 [
self didChangeValueForKey:@"displayingFlutterUI"];
612- (void)callViewRenderedCallback {
613 self.displayingFlutterUI = YES;
614 if (
self.flutterViewRenderedCallback) {
615 self.flutterViewRenderedCallback();
616 self.flutterViewRenderedCallback = nil;
620- (void)onFirstFrameRendered {
621 if (
self.splashScreenView) {
623 [
self.splashScreenManager removeSplashScreenWithCompletion:^{
624 [weakSelf callViewRenderedCallback];
627 [
self callViewRenderedCallback];
631- (void)installFirstFrameCallback {
636 [
self.engine installFirstFrameCallback:^{
637 [weakSelf onFirstFrameRendered];
641#pragma mark - Properties
643- (int64_t)viewIdentifier {
649- (
BOOL)loadDefaultSplashScreenView {
650 return [
self.splashScreenManager loadDefaultSplashScreenView];
653- (UIView*)splashScreenView {
657- (void)setSplashScreenView:(UIView*)view {
658 self.splashScreenManager.splashScreenView =
view;
661- (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
662 _flutterViewRenderedCallback =
callback;
665- (UISceneActivationState)activationState {
666 return self.flutterWindowSceneIfViewLoaded.activationState;
669- (
BOOL)stateIsActive {
672 BOOL isActive = flutterApplication
673 ? [
self isApplicationStateMatching:UIApplicationStateActive
674 withApplication:flutterApplication]
675 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
679- (
BOOL)stateIsBackground {
682 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
683 withApplication:flutterApplication]
684 : [
self isSceneStateMatching:UISceneActivationStateBackground];
687- (
BOOL)shouldHandleSceneNotification:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
688 if (notification.object == nil) {
691 UIWindowScene* scene =
self.flutterWindowSceneIfViewLoaded;
695 return notification.object == scene;
698- (
BOOL)isApplicationStateMatching:(UIApplicationState)match
699 withApplication:(UIApplication*)application {
700 switch (application.applicationState) {
701 case UIApplicationStateActive:
702 case UIApplicationStateInactive:
703 case UIApplicationStateBackground:
704 return application.applicationState == match;
708- (
BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
709 switch (
self.activationState) {
710 case UISceneActivationStateForegroundActive:
711 case UISceneActivationStateUnattached:
712 case UISceneActivationStateForegroundInactive:
713 case UISceneActivationStateBackground:
714 return self.activationState == match;
718#pragma mark - Surface creation and teardown updates
720- (void)surfaceUpdated:(
BOOL)appeared {
728 [
self installFirstFrameCallback];
729 self.platformViewsController.flutterView =
self.flutterView;
730 self.platformViewsController.flutterViewController =
self;
731 [
self.engine notifyViewCreated];
733 self.displayingFlutterUI = NO;
734 [
self.engine notifyViewDestroyed];
735 self.platformViewsController.flutterView = nil;
736 self.platformViewsController.flutterViewController = nil;
740#pragma mark - UIViewController lifecycle notifications
745 if (
self.engine &&
self.engineNeedsLaunch) {
746 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
747 [
self.engine setViewController:self];
748 self.engineNeedsLaunch = NO;
749 }
else if (
self.engine.viewController ==
self) {
750 [
self.engine attachView];
754 [
self addInternalPlugins];
757 [
self createTouchRateCorrectionVSyncClientIfNeeded];
759 if (@available(iOS 13.4, *)) {
760 _hoverGestureRecognizer =
761 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
762 _hoverGestureRecognizer.delegate =
self;
763 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
765 _discreteScrollingPanGestureRecognizer =
766 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
767 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
772 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
773 _discreteScrollingPanGestureRecognizer.delegate =
self;
774 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
775 _continuousScrollingPanGestureRecognizer =
776 [[UIPanGestureRecognizer alloc] initWithTarget:self
777 action:@selector(continuousScrollEvent:)];
778 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
779 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
780 _continuousScrollingPanGestureRecognizer.delegate =
self;
781 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
782 _pinchGestureRecognizer =
783 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
784 _pinchGestureRecognizer.allowedTouchTypes = @[];
785 _pinchGestureRecognizer.delegate =
self;
786 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
787 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
788 _rotationGestureRecognizer.allowedTouchTypes = @[];
789 _rotationGestureRecognizer.delegate =
self;
790 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
796- (void)addInternalPlugins {
801 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
803 [
self.keyboardManager
807 [
self.keyboardManager addPrimaryResponder:responder];
810 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
812 if (
self.engine.viewController ==
self) {
817- (void)removeInternalPlugins {
818 self.keyboardManager = nil;
821- (void)viewWillAppear:(
BOOL)animated {
823 if (
self.engine.viewController ==
self) {
825 [
self onUserSettingsChanged:nil];
829 if (_viewportMetrics.physical_width) {
830 [
self surfaceUpdated:YES];
832 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
833 [
self.engine.restorationPlugin markRestorationComplete];
836 [
super viewWillAppear:animated];
839- (void)viewDidAppear:(
BOOL)animated {
841 if (
self.engine.viewController ==
self) {
842 [
self onUserSettingsChanged:nil];
843 [
self onAccessibilityStatusChanged:nil];
845 if (
self.stateIsActive) {
846 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
849 [
super viewDidAppear:animated];
852- (void)viewWillDisappear:(
BOOL)animated {
854 if (
self.engine.viewController ==
self) {
855 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
857 [
super viewWillDisappear:animated];
860- (void)viewDidDisappear:(
BOOL)animated {
862 if (
self.engine.viewController ==
self) {
863 [
self.keyboardInsetManager hideKeyboardImmediately];
864 [
self surfaceUpdated:NO];
865 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
866 [
self flushOngoingTouches];
867 [
self.engine notifyLowMemory];
870 [
super viewDidDisappear:animated];
873- (void)viewWillTransitionToSize:(CGSize)size
874 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
875 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
885 NSTimeInterval transitionDuration = coordinator.transitionDuration;
887 if (transitionDuration == 0) {
892 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
893 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
894 static_cast<int64_t
>(transitionDuration / 2.0 *
NSEC_PER_SEC)),
895 dispatch_get_main_queue(), ^{
903 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
904 [strongSelf updateViewportMetricsIfNeeded];
908- (void)flushOngoingTouches {
909 if (
self.engine &&
self.ongoingTouches.count > 0) {
910 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
911 size_t pointer_index = 0;
914 for (NSNumber*
device in
self.ongoingTouches) {
931 packet->SetPointerData(pointer_index++, pointer_data);
934 [
self.ongoingTouches removeAllObjects];
935 [
self.engine dispatchPointerDataPacket:std::move(packet)];
939- (void)deregisterNotifications {
940 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
943 [[NSNotificationCenter defaultCenter] removeObserver:self];
949 [
self removeInternalPlugins];
950 [
self deregisterNotifications];
952 [
self.keyboardInsetManager invalidate];
953 [
self invalidateTouchRateCorrectionVSyncClient];
957 _scrollView.delegate = nil;
958 _hoverGestureRecognizer.delegate = nil;
959 _discreteScrollingPanGestureRecognizer.delegate = nil;
960 _continuousScrollingPanGestureRecognizer.delegate = nil;
961 _pinchGestureRecognizer.delegate = nil;
962 _rotationGestureRecognizer.delegate = nil;
965#pragma mark - Application lifecycle notifications
967- (void)applicationBecameActive:(NSNotification*)notification {
969 [
self appOrSceneBecameActive];
972- (void)applicationWillResignActive:(NSNotification*)notification {
974 [
self appOrSceneWillResignActive];
977- (void)applicationWillTerminate:(NSNotification*)notification {
978 [
self appOrSceneWillTerminate];
981- (void)applicationDidEnterBackground:(NSNotification*)notification {
982 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
983 [
self appOrSceneDidEnterBackground];
986- (void)applicationWillEnterForeground:(NSNotification*)notification {
987 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
988 [
self appOrSceneWillEnterForeground];
991#pragma mark - Scene lifecycle notifications
993- (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
994 if (![
self shouldHandleSceneNotification:notification]) {
998 [
self appOrSceneBecameActive];
1001- (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1002 if (![
self shouldHandleSceneNotification:notification]) {
1006 [
self appOrSceneWillResignActive];
1009- (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1010 if (![
self shouldHandleSceneNotification:notification]) {
1013 [
self appOrSceneWillTerminate];
1016- (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1017 if (![
self shouldHandleSceneNotification:notification]) {
1021 [
self appOrSceneDidEnterBackground];
1024- (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1025 if (![
self shouldHandleSceneNotification:notification]) {
1029 [
self appOrSceneWillEnterForeground];
1032#pragma mark - Lifecycle shared
1034- (void)appOrSceneBecameActive {
1035 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = NO;
1036 if (_viewportMetrics.physical_width) {
1037 [
self surfaceUpdated:YES];
1039 [
self performSelector:@selector(goToApplicationLifecycle:)
1040 withObject:@"AppLifecycleState.resumed"
1044- (void)appOrSceneWillResignActive {
1045 [NSObject cancelPreviousPerformRequestsWithTarget:self
1046 selector:@selector(goToApplicationLifecycle:)
1047 object:@"AppLifecycleState.resumed"];
1048 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1051- (void)appOrSceneWillTerminate {
1052 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1053 [
self.engine destroyContext];
1056- (void)appOrSceneDidEnterBackground {
1057 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = YES;
1058 [
self surfaceUpdated:NO];
1059 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1062- (void)appOrSceneWillEnterForeground {
1063 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1067- (void)goToApplicationLifecycle:(nonnull NSString*)state {
1070 if (
self.viewIfLoaded.window) {
1071 [
self.engine.lifecycleChannel sendMessage:state];
1075#pragma mark - Touch event handling
1079 case UITouchPhaseBegan:
1081 case UITouchPhaseMoved:
1082 case UITouchPhaseStationary:
1086 case UITouchPhaseEnded:
1088 case UITouchPhaseCancelled:
1092 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1100 switch (touch.type) {
1101 case UITouchTypeDirect:
1102 case UITouchTypeIndirect:
1104 case UITouchTypeStylus:
1106 case UITouchTypeIndirectPointer:
1109 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1120- (void)dispatchTouches:(NSSet*)touches
1121 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1122 event:(UIEvent*)event {
1147 NSUInteger touches_to_remove_count = 0;
1148 for (UITouch* touch in touches) {
1149 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1150 touches_to_remove_count++;
1155 [
self triggerTouchRateCorrectionIfNeeded:touches];
1157 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1159 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1161 size_t pointer_index = 0;
1163 for (UITouch* touch in touches) {
1164 CGPoint windowCoordinates = [touch locationInView:self.view];
1167 pointer_data.
Clear();
1172 pointer_data.
change = overridden_change !=
nullptr
1173 ? *overridden_change
1174 : PointerDataChangeFromUITouchPhase(touch.phase);
1176 pointer_data.
kind = DeviceKindFromTouchType(touch);
1178 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1185 pointer_data.
physical_x = windowCoordinates.x * scale;
1186 pointer_data.
physical_y = windowCoordinates.y * scale;
1192 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1195 switch (pointer_data.
change) {
1197 [
self.ongoingTouches addObject:deviceKey];
1201 [
self.ongoingTouches removeObject:deviceKey];
1219 pointer_data.
pressure = touch.force;
1220 pointer_data.
pressure_max = touch.maximumPossibleForce;
1222 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1223 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1238 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1258 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1260 if (@available(iOS 13.4, *)) {
1261 if (event !=
nullptr) {
1262 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1265 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1271 packet->SetPointerData(pointer_index++, pointer_data);
1273 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1276 packet->SetPointerData(pointer_index++, remove_pointer_data);
1280 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1283- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1284 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1287- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1288 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1291- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1292 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1295- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1296 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1299- (void)forceTouchesCancelled:(NSSet*)touches {
1301 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1304- (
BOOL)platformViewShouldAcceptTouchAtTouchBeganLocation:(CGPoint)location {
1306 return [
self.engine platformViewShouldAcceptTouchAtTouchBeganLocation:point
1307 viewId:self.viewIdentifier];
1310#pragma mark - Touch events rate correction
1312- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1313 if (_touchRateCorrectionVSyncClient != nil) {
1317 double displayRefreshRate =
self.displayLinkManager.displayRefreshRate;
1318 const double epsilon = 0.1;
1319 if (displayRefreshRate < 60.0 + epsilon) {
1327 void (^
callback)(CFTimeInterval, CFTimeInterval) =
1328 ^(CFTimeInterval startTime, CFTimeInterval targetTime) {
1331 _touchRateCorrectionVSyncClient = [[FlutterVSyncClient alloc]
1332 initWithTaskRunner:self.engine.platformTaskRunner
1333 isVariableRefreshRateEnabled:self.displayLinkManager.maxRefreshRateEnabledOnIPhone
1334 maxRefreshRate:self.displayLinkManager.displayRefreshRate
1336 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1339- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1340 if (_touchRateCorrectionVSyncClient == nil) {
1348 BOOL isUserInteracting = NO;
1349 for (UITouch* touch in touches) {
1350 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1351 isUserInteracting = YES;
1356 if (isUserInteracting &&
self.engine.viewController ==
self) {
1357 [_touchRateCorrectionVSyncClient await];
1359 [_touchRateCorrectionVSyncClient pause];
1363- (void)invalidateTouchRateCorrectionVSyncClient {
1364 [_touchRateCorrectionVSyncClient invalidate];
1365 _touchRateCorrectionVSyncClient = nil;
1368#pragma mark - Handle view resizing
1370- (void)updateViewportMetricsIfNeeded {
1371 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1374 if (
self.engine.viewController ==
self) {
1375 [
self.engine updateViewportMetrics:_viewportMetrics];
1379- (void)viewDidLayoutSubviews {
1380 CGRect viewBounds =
self.view.bounds;
1381 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1384 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1388 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1389 _viewportMetrics.device_pixel_ratio = scale;
1390 [
self setViewportMetricsSize];
1391 [
self checkAndUpdateAutoResizeConstraints];
1392 [
self setViewportMetricsPaddings];
1393 [
self updateViewportMetricsIfNeeded];
1400 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1401 [
self surfaceUpdated:YES];
1402#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1403 NSTimeInterval timeout = 0.2;
1405 NSTimeInterval timeout = 0.1;
1408 waitForFirstFrameSync:timeout
1409 callback:^(BOOL didTimeout) {
1411 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1412 "This may happen in unoptimized builds. If this is"
1413 "a release build, you should load a less complex "
1414 "frame to avoid the timeout."];
1420- (
BOOL)isAutoResizable {
1421 return self.flutterView.autoResizable;
1424- (void)setAutoResizable:(
BOOL)value {
1426 self.flutterView.contentMode = UIViewContentModeCenter;
1429- (void)checkAndUpdateAutoResizeConstraints {
1430 if (!
self.isAutoResizable) {
1434 [
self updateAutoResizeConstraints];
1458- (void)updateAutoResizeConstraints {
1459 BOOL hasBeenAutoResized = NO;
1460 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1462 hasBeenAutoResized = YES;
1466 if (!hasBeenAutoResized) {
1467 self.sizeBeforeAutoResized =
self.view.frame.size;
1470 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1471 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1472 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1473 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1477 if (maxWidth == 0) {
1478 maxWidth = CGFLOAT_MAX;
1481 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1482 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1483 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1484 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1485 @"constraints (e.g., using SizedBox or Container)."];
1487 if (maxHeight == 0) {
1488 maxHeight = CGFLOAT_MAX;
1491 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1492 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1493 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1494 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1495 @"constraints (e.g., using SizedBox or Container)."];
1497 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1498 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1499 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1500 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1503- (void)viewSafeAreaInsetsDidChange {
1504 [
self setViewportMetricsPaddings];
1505 [
self updateViewportMetricsIfNeeded];
1506 [
super viewSafeAreaInsetsDidChange];
1510- (void)setViewportMetricsSize {
1511 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1516 CGFloat scale = screen.scale;
1517 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1518 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1520 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1521 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1522 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1523 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1529- (void)setViewportMetricsPaddings {
1530 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1535 CGFloat scale = screen.scale;
1536 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1537 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1538 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1539 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1542#pragma mark - Keyboard events
1544- (void)keyboardWillShowNotification:(NSNotification*)notification {
1549 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1552- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1557 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1560- (void)keyboardWillBeHidden:(NSNotification*)notification {
1564 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1567- (void)handlePressEvent:(FlutterUIPressProxy*)press
1568 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1569 if (@available(iOS 13.4, *)) {
1574 [
self.keyboardManager handlePress:press nextAction:next];
1590- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1591 [
super pressesBegan:presses withEvent:event];
1594- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1595 [
super pressesChanged:presses withEvent:event];
1598- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1599 [
super pressesEnded:presses withEvent:event];
1602- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1603 [
super pressesCancelled:presses withEvent:event];
1611- (void)pressesBegan:(NSSet<UIPress*>*)presses
1612 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1613 if (@available(iOS 13.4, *)) {
1615 for (UIPress* press in presses) {
1616 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1618 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1622 [
super pressesBegan:presses withEvent:event];
1626- (void)pressesChanged:(NSSet<UIPress*>*)presses
1627 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1628 if (@available(iOS 13.4, *)) {
1630 for (UIPress* press in presses) {
1631 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1633 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1637 [
super pressesChanged:presses withEvent:event];
1641- (void)pressesEnded:(NSSet<UIPress*>*)presses
1642 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1643 if (@available(iOS 13.4, *)) {
1645 for (UIPress* press in presses) {
1646 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1648 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1652 [
super pressesEnded:presses withEvent:event];
1656- (void)pressesCancelled:(NSSet<UIPress*>*)presses
1657 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1658 if (@available(iOS 13.4, *)) {
1660 for (UIPress* press in presses) {
1661 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1663 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1667 [
super pressesCancelled:presses withEvent:event];
1671#pragma mark - Orientation updates
1673- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1676 dispatch_async(dispatch_get_main_queue(), ^{
1677 NSDictionary* info = notification.userInfo;
1678 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1679 if (update == nil) {
1682 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1686- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1687 API_AVAILABLE(ios(16.0)) {
1688 for (UIScene* windowScene in windowScenes) {
1689 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
1690 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1691 initWithInterfaceOrientations:self.orientationPreferences];
1692 [(UIWindowScene*)windowScene
1693 requestGeometryUpdateWithPreferences:preference
1694 errorHandler:^(NSError* error) {
1695 os_log_error(OS_LOG_DEFAULT,
1696 "Failed to change device orientation: %@", error);
1698 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
1702- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
1703 if (new_preferences !=
self.orientationPreferences) {
1704 self.orientationPreferences = new_preferences;
1706 if (@available(iOS 16.0, *)) {
1708 NSSet<UIScene*>* scenes = [NSSet set];
1709 if (flutterApplication) {
1710 scenes = [flutterApplication.connectedScenes
1711 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
1712 id scene, NSDictionary* bindings) {
1713 return [scene isKindOfClass:[UIWindowScene class]];
1715 }
else if (
self.flutterWindowSceneIfViewLoaded) {
1716 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
1718 [
self requestGeometryUpdateForWindowScenes:scenes];
1720 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
1721 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
1725 @"Accessing the interface orientation when the window scene is unavailable."];
1728 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
1729 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
1730 [UIViewController attemptRotationToDeviceOrientation];
1732 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
1736 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
1737 forKey:@"orientation"];
1738 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
1739 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
1740 forKey:@"orientation"];
1741 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
1742 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
1743 forKey:@"orientation"];
1744 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
1745 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
1746 forKey:@"orientation"];
1753- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
1754 self.isHomeIndicatorHidden = YES;
1757- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
1758 self.isHomeIndicatorHidden = NO;
1761- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
1762 if (hideHomeIndicator != _isHomeIndicatorHidden) {
1763 _isHomeIndicatorHidden = hideHomeIndicator;
1764 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
1768- (
BOOL)prefersHomeIndicatorAutoHidden {
1769 return self.isHomeIndicatorHidden;
1772- (
BOOL)shouldAutorotate {
1776- (NSUInteger)supportedInterfaceOrientations {
1777 return self.orientationPreferences;
1780#pragma mark - Accessibility
1782- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1787 int32_t flags = [
self.accessibilityFeatures flags];
1788#if TARGET_OS_SIMULATOR
1794 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
1795 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
1796 [
self.accessibilityFeatures isSpeakScreenEnabled];
1798 [
self.engine enableSemantics:enabled withFlags:flags];
1801- (
BOOL)accessibilityPerformEscape {
1803 if (navigationChannel) {
1810#pragma mark - Set user settings
1812- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1813 [
super traitCollectionDidChange:previousTraitCollection];
1814 [
self onUserSettingsChanged:nil];
1818 if (
self.isAutoResizable) {
1819 [
self.flutterView resetIntrinsicContentSize];
1823- (void)onUserSettingsChanged:(NSNotification*)notification {
1824 [
self.engine.settingsChannel sendMessage:@{
1825 @"textScaleFactor" : @(
self.textScaleFactor),
1827 @"platformBrightness" :
self.brightnessMode,
1828 @"platformContrast" : self.contrastMode,
1829 @"nativeSpellCheckServiceDefined" : @YES,
1830 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
1834- (CGFloat)textScaleFactor {
1836 if (flutterApplication == nil) {
1837 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
1841 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
1847 const CGFloat xs = 14;
1848 const CGFloat s = 15;
1849 const CGFloat m = 16;
1850 const CGFloat l = 17;
1851 const CGFloat xl = 19;
1852 const CGFloat xxl = 21;
1853 const CGFloat xxxl = 23;
1856 const CGFloat ax1 = 28;
1857 const CGFloat ax2 = 33;
1858 const CGFloat ax3 = 40;
1859 const CGFloat ax4 = 47;
1860 const CGFloat ax5 = 53;
1864 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
1866 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
1868 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
1870 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
1872 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
1874 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
1876 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
1878 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
1880 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
1882 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
1884 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
1886 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
1893- (
BOOL)supportsShowingSystemContextMenu {
1894 if (@available(iOS 16.0, *)) {
1904- (NSString*)brightnessMode {
1905 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
1907 if (style == UIUserInterfaceStyleDark) {
1917- (NSString*)contrastMode {
1918 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
1920 if (contrast == UIAccessibilityContrastHigh) {
1927#pragma mark - Status bar style
1929- (UIStatusBarStyle)preferredStatusBarStyle {
1930 return self.statusBarStyle;
1933- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
1936 dispatch_async(dispatch_get_main_queue(), ^{
1942 NSDictionary* info = notification.userInfo;
1943 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
1944 if (update == nil) {
1948 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
1949 if (style != strongSelf.statusBarStyle) {
1950 strongSelf.statusBarStyle = style;
1951 [strongSelf setNeedsStatusBarAppearanceUpdate];
1956- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
1957 if (hidden !=
self.flutterPrefersStatusBarHidden) {
1958 self.flutterPrefersStatusBarHidden = hidden;
1959 [
self setNeedsStatusBarAppearanceUpdate];
1963- (
BOOL)prefersStatusBarHidden {
1964 return self.flutterPrefersStatusBarHidden;
1967#pragma mark - Platform views
1970 return self.engine.platformViewsController;
1973- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1974 return self.engine.binaryMessenger;
1977#pragma mark - FlutterBinaryMessenger
1979- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1980 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
1983- (void)sendOnChannel:(NSString*)channel
1984 message:(NSData*)message
1986 NSAssert(
channel,
@"The channel must not be null");
1987 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
1990- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1991 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
1995 binaryMessageHandler:
1997 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2001 setMessageHandlerOnChannel:(NSString*)channel
2003 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2004 NSAssert(
channel,
@"The channel must not be null");
2005 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2006 binaryMessageHandler:handler
2007 taskQueue:taskQueue];
2011 [
self.engine.binaryMessenger cleanUpConnection:connection];
2014#pragma mark - FlutterTextureRegistry
2017 return [
self.engine.textureRegistry registerTexture:texture];
2020- (void)unregisterTexture:(int64_t)textureId {
2021 [
self.engine.textureRegistry unregisterTexture:textureId];
2024- (void)textureFrameAvailable:(int64_t)textureId {
2025 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2028- (NSString*)lookupKeyForAsset:(NSString*)asset {
2032- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2036- (
id<FlutterPluginRegistry>)pluginRegistry {
2040+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2041 return UIAccessibilityIsVoiceOverRunning();
2044#pragma mark - FlutterPluginRegistry
2046- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
2047 return [
self.engine registrarForPlugin:pluginKey];
2050- (
BOOL)hasPlugin:(NSString*)pluginKey {
2051 return [
self.engine hasPlugin:pluginKey];
2054- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2055 return [
self.engine valuePublishedByPlugin:pluginKey];
2058- (void)presentViewController:(UIViewController*)viewControllerToPresent
2060 completion:(
void (^)(
void))completion {
2061 self.isPresentingViewControllerAnimating = YES;
2063 [
super presentViewController:viewControllerToPresent
2066 weakSelf.isPresentingViewControllerAnimating = NO;
2073- (
BOOL)isPresentingViewController {
2074 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2077- (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2078 API_AVAILABLE(ios(13.4)) {
2079 CGPoint
location = [gestureRecognizer locationInView:self.view];
2080 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2083 pointer_data.
Clear();
2087 return pointer_data;
2090- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2091 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2092 API_AVAILABLE(ios(13.4)) {
2096- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2097 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2098 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2099 event.type == UIEventTypeScroll) {
2102 pointer_data.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2107 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2110 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2111 packet->SetPointerData(0, pointer_data);
2112 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2113 self.scrollInertiaEventAppKitDeadline = 0;
2120- (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2124 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2128 switch (_hoverGestureRecognizer.state) {
2129 case UIGestureRecognizerStateBegan:
2132 case UIGestureRecognizerStateChanged:
2135 case UIGestureRecognizerStateEnded:
2136 case UIGestureRecognizerStateCancelled:
2146 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2147 BOOL isRunningOnMac = NO;
2151 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2153 time >
self.scrollInertiaEventStartline) {
2157 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2158 packet->SetPointerData(0, pointer_data);
2160 inertia_cancel.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2164 packet->SetPointerData(1, inertia_cancel);
2165 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2166 self.scrollInertiaEventStartline = DBL_MAX;
2168 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2169 packet->SetPointerData(0, pointer_data);
2170 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2174- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2175 CGPoint translation = [recognizer translationInView:self.view];
2176 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2178 translation.x *= scale;
2179 translation.y *= scale;
2182 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2193 if (recognizer.state != UIGestureRecognizerStateEnded) {
2199 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2200 packet->SetPointerData(0, pointer_data);
2201 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2204- (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2205 CGPoint translation = [recognizer translationInView:self.view];
2206 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2209 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2212 switch (recognizer.state) {
2213 case UIGestureRecognizerStateBegan:
2216 case UIGestureRecognizerStateChanged:
2218 pointer_data.
pan_x = translation.x * scale;
2219 pointer_data.
pan_y = translation.y * scale;
2222 pointer_data.
scale = 1;
2224 case UIGestureRecognizerStateEnded:
2225 case UIGestureRecognizerStateCancelled:
2226 self.scrollInertiaEventStartline =
2227 [[NSProcessInfo processInfo] systemUptime] +
2237 self.scrollInertiaEventAppKitDeadline =
2238 [[NSProcessInfo processInfo] systemUptime] +
2239 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2240 [recognizer velocityInView:self.view].y))) -
2246 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2247 (
long)recognizer.state);
2251 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2252 packet->SetPointerData(0, pointer_data);
2253 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2256- (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2258 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2261 switch (recognizer.state) {
2262 case UIGestureRecognizerStateBegan:
2265 case UIGestureRecognizerStateChanged:
2267 pointer_data.
scale = recognizer.scale;
2268 pointer_data.
rotation = _rotationGestureRecognizer.rotation;
2270 case UIGestureRecognizerStateEnded:
2271 case UIGestureRecognizerStateCancelled:
2276 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2277 (
long)recognizer.state);
2281 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2282 packet->SetPointerData(0, pointer_data);
2283 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2286#pragma mark - State Restoration
2288- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2289 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2290 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2291 length:restorationData.length
2292 forKey:kFlutterRestorationStateAppData];
2293 [
super encodeRestorableStateWithCoder:coder];
2296- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2297 NSUInteger restorationDataLength;
2298 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2299 returnedLength:&restorationDataLength];
2300 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2301 [
self.engine.restorationPlugin setRestorationData:restorationData];
2305 return self.engine.restorationPlugin;
2309 return self.engine.textInputPlugin;
2312#pragma mark - FlutterKeyboardInsetManagerDelegate
2314- (void)updateViewportMetricsWithInset:(CGFloat)inset {
2315 _viewportMetrics.physical_view_inset_bottom = inset;
2316 [
self updateViewportMetricsIfNeeded];
2319- (CGFloat)physicalViewInsetBottom {
2320 return _viewportMetrics.physical_view_inset_bottom;
2324 return self.engine.uiTaskRunner;
2327- (
BOOL)isPadInSlideOverOrStageManagerMode {
2328 if (
self.view.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
2329 self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
2330 self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
2336- (CGRect)convertViewRectToScreen:(CGRect)rect {
2337 return [
self.view convertRect:rect
2338 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
UIPanGestureRecognizer *discreteScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPinchGestureRecognizer *pinchGestureRecognizer API_AVAILABLE(ios(13.4))
UIHoverGestureRecognizer *hoverGestureRecognizer API_AVAILABLE(ios(13.4))
UIPanGestureRecognizer *continuousScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
void(* FlutterKeyEventCallback)(bool, void *)
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
#define FML_CHECK(condition)
#define FML_DCHECK(condition)
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
FlutterViewController * viewController
UIApplication * application
void setUpIndirectScribbleInteraction:(id< FlutterViewResponder > viewResponder)
UIView * splashScreenView
FlutterViewIdentifier viewIdentifier
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
instancetype initWithCoder
FlutterTextInputPlugin * textInputPlugin
NSNotificationName const FlutterViewControllerHideHomeIndicator
FlutterSplashScreenManager * _splashScreenManager
static NSString *const kFlutterRestorationStateAppData
static FLUTTER_ASSERT_ARC constexpr int kMicrosecondsPerSecond
NSNotificationName const FlutterViewControllerShowHomeIndicator
NSNotificationName const FlutterSemanticsUpdateNotification
static constexpr CGFloat kScrollViewContentSize
NSNotificationName const FlutterViewControllerWillDealloc
constexpr int64_t kFlutterImplicitViewId
@ kPointerButtonMouseSecondary
@ kPointerButtonMousePrimary
TracingResult GetTracingResult()
Returns if a tracing check has been performed and its result. To enable tracing, the Settings object ...
int64_t pointer_identifier
#define TRACE_EVENT0(category_group, name)