5#define FML_USED_ON_EMBEDDER
18#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
52 @"FlutterViewControllerHideHomeIndicator";
54 @"FlutterViewControllerShowHomeIndicator";
70 FlutterKeyboardInsetManagerDelegate>
79@property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
81@property(nonatomic, strong) FlutterSplashScreenManager* splashScreenManager;
83@property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
84@property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
85@property(nonatomic, assign)
BOOL initialized;
86@property(nonatomic, assign)
BOOL engineNeedsLaunch;
87@property(nonatomic, assign)
BOOL awokenFromNib;
90@property(nonatomic, assign)
BOOL isHomeIndicatorHidden;
91@property(nonatomic, assign)
BOOL isPresentingViewControllerAnimating;
94@property(nonatomic, assign)
BOOL flutterPrefersStatusBarHidden;
96@property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
101@property(nonatomic, strong) UIScrollView* scrollView;
106@property(nonatomic, assign)
BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
112@property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
120@property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
128@property(nonatomic, strong) FlutterVSyncClient* touchRateCorrectionVSyncClient;
132@property(nonatomic, assign) CGSize sizeBeforeAutoResized;
138@property(nonatomic, strong)
141@property(nonatomic, strong)
142 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
144@property(nonatomic, strong)
145 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
147@property(nonatomic, strong)
150@property(nonatomic, strong)
151 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
154- (void)addInternalPlugins;
155- (void)deregisterNotifications;
158- (void)onFirstFrameRendered;
171@synthesize viewOpaque = _viewOpaque;
172@synthesize displayingFlutterUI = _displayingFlutterUI;
174- (FlutterSplashScreenManager*)splashScreenManager {
184@dynamic viewIdentifier;
186#pragma mark - Manage and override all designated initializers
189 nibName:(nullable NSString*)nibName
190 bundle:(nullable NSBundle*)nibBundle {
191 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
192 self = [
super initWithNibName:nibName bundle:nibBundle];
196 NSString* errorMessage =
197 [NSString stringWithFormat:
198 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
199 "instance %@. One instance of the FlutterEngine can only be attached to "
200 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
201 "nil before attaching it to another FlutterViewController.",
202 engine.description, engine.viewController.description];
203 [FlutterLogger logError:errorMessage];
206 _engineNeedsLaunch = NO;
207 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
208 opaque:self.isViewOpaque
209 enableWideGamut:engine.project.isWideGamutEnabled];
210 _ongoingTouches = [[NSMutableSet alloc] init];
214 [
self performCommonViewControllerInitialization];
215 [engine setViewController:self];
222 nibName:(NSString*)nibName
223 bundle:(NSBundle*)nibBundle {
224 self = [
super initWithNibName:nibName bundle:nibBundle];
228 [
self sharedSetupWithProject:project initialRoute:nil];
235 initialRoute:(NSString*)initialRoute
236 nibName:(NSString*)nibName
237 bundle:(NSBundle*)nibBundle {
238 self = [
super initWithNibName:nibName bundle:nibBundle];
242 [
self sharedSetupWithProject:project initialRoute:initialRoute];
248- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
249 return [
self initWithProject:nil nibName:nil bundle:nil];
253 self = [
super initWithCoder:aDecoder];
257- (void)awakeFromNib {
258 [
super awakeFromNib];
259 self.awokenFromNib = YES;
261 [
self sharedSetupWithProject:nil initialRoute:nil];
265- (instancetype)init {
266 return [
self initWithProject:nil nibName:nil bundle:nil];
270 initialRoute:(nullable NSString*)initialRoute {
273 if ([appDelegate respondsToSelector:@selector(takeLaunchEngine)]) {
278 engine = [appDelegate takeLaunchEngine];
283 [appDelegate takeLaunchEngine];
295 allowHeadlessExecution:self.engineAllowHeadlessExecution
296 restorationEnabled:self.restorationIdentifier != nil];
304 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
306 enableWideGamut:engine.project.isWideGamutEnabled];
307 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
313 BOOL performedCallback = [_engine performImplicitEngineCallback];
317 respondsToSelector:@selector(pluginRegistrant)]) {
318 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
320 [pluginRegistrant registerWithRegistry:self];
321 performedCallback = YES;
329 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
330 [applicationLifeCycleDelegate
331 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
332 [applicationLifeCycleDelegate
333 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
336 _engineNeedsLaunch = YES;
337 _ongoingTouches = [[NSMutableSet alloc] init];
341 [
self.splashScreenManager loadDefaultSplashScreenView];
342 [
self performCommonViewControllerInitialization];
345- (
BOOL)isViewOpaque {
349- (void)setViewOpaque:(
BOOL)value {
351 if (
self.flutterView.layer.opaque != value) {
353 [
self.flutterView.layer setNeedsLayout];
357#pragma mark - Common view controller initialization tasks
359- (void)performCommonViewControllerInitialization {
365 _orientationPreferences = UIInterfaceOrientationMaskAll;
366 _statusBarStyle = UIStatusBarStyleDefault;
368 _accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
373 [
self setUpNotificationCenterObservers];
376- (void)setUpNotificationCenterObservers {
377 NSNotificationCenter*
center = [NSNotificationCenter defaultCenter];
378 [center addObserver:self
379 selector:@selector(onOrientationPreferencesUpdated:)
380 name:@(flutter::kOrientationUpdateNotificationName)
383 [center addObserver:self
384 selector:@selector(onPreferredStatusBarStyleUpdated:)
385 name:@(flutter::kOverlayStyleUpdateNotificationName)
389 [
self setUpApplicationLifecycleNotifications:center];
391 [
self setUpSceneLifecycleNotifications:center];
394 [center addObserver:self
395 selector:@selector(keyboardWillChangeFrame:)
396 name:UIKeyboardWillChangeFrameNotification
399 [center addObserver:self
400 selector:@selector(keyboardWillShowNotification:)
401 name:UIKeyboardWillShowNotification
404 [center addObserver:self
405 selector:@selector(keyboardWillBeHidden:)
406 name:UIKeyboardWillHideNotification
409 for (NSString* notification in [
self.accessibilityFeatures observedNotificationNames]) {
410 [center addObserver:self
411 selector:@selector(onAccessibilityStatusChanged:)
416 [center addObserver:self
417 selector:@selector(onUserSettingsChanged:)
418 name:UIContentSizeCategoryDidChangeNotification
421 [center addObserver:self
422 selector:@selector(onHideHomeIndicatorNotification:)
423 name:FlutterViewControllerHideHomeIndicator
426 [center addObserver:self
427 selector:@selector(onShowHomeIndicatorNotification:)
428 name:FlutterViewControllerShowHomeIndicator
432- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
433 [center addObserver:self
434 selector:@selector(sceneBecameActive:)
435 name:UISceneDidActivateNotification
438 [center addObserver:self
439 selector:@selector(sceneWillResignActive:)
440 name:UISceneWillDeactivateNotification
443 [center addObserver:self
444 selector:@selector(sceneWillDisconnect:)
445 name:UISceneDidDisconnectNotification
448 [center addObserver:self
449 selector:@selector(sceneDidEnterBackground:)
450 name:UISceneDidEnterBackgroundNotification
453 [center addObserver:self
454 selector:@selector(sceneWillEnterForeground:)
455 name:UISceneWillEnterForegroundNotification
459- (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
460 [center addObserver:self
461 selector:@selector(applicationBecameActive:)
462 name:UIApplicationDidBecomeActiveNotification
465 [center addObserver:self
466 selector:@selector(applicationWillResignActive:)
467 name:UIApplicationWillResignActiveNotification
470 [center addObserver:self
471 selector:@selector(applicationWillTerminate:)
472 name:UIApplicationWillTerminateNotification
475 [center addObserver:self
476 selector:@selector(applicationDidEnterBackground:)
477 name:UIApplicationDidEnterBackgroundNotification
480 [center addObserver:self
481 selector:@selector(applicationWillEnterForeground:)
482 name:UIApplicationWillEnterForegroundNotification
486- (void)setInitialRoute:(NSString*)route {
487 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
491 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
494- (void)pushRoute:(NSString*)route {
495 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
498#pragma mark - Loading the view
500static UIView* GetViewOrPlaceholder(UIView* existing_view) {
502 return existing_view;
505 auto placeholder = [[UIView alloc] init];
507 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
508 placeholder.backgroundColor = UIColor.systemBackgroundColor;
509 placeholder.autoresizesSubviews = YES;
515 auto messageLabel = [[UILabel alloc] init];
516 messageLabel.numberOfLines = 0u;
517 messageLabel.textAlignment = NSTextAlignmentCenter;
518 messageLabel.autoresizingMask =
519 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
521 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
522 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
523 @"modes to enable launching from the home screen.";
524 [placeholder addSubview:messageLabel];
531 self.view = GetViewOrPlaceholder(
self.flutterView);
532 self.view.multipleTouchEnabled = YES;
533 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
535 [
self installSplashScreenViewIfNecessary];
538 UIScrollView* scrollView = [[UIScrollView alloc] init];
539 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
541 scrollView.backgroundColor = UIColor.whiteColor;
542 scrollView.delegate =
self;
548 [
self.view addSubview:scrollView];
549 self.scrollView = scrollView;
552- (
flutter::PointerData)generatePointerDataForFake {
554 pointer_data.
Clear();
563- (
BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
567 if (
self.isViewLoaded) {
569 [
self.engine onStatusBarTap];
574#pragma mark - Managing launch views
576- (void)installSplashScreenViewIfNecessary {
582 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
584 [
self.splashScreenView removeFromSuperview];
585 self.splashScreenView = nil;
590 [
self.splashScreenManager installSplashScreenViewAsSubviewOf:self.view];
593+ (
BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
597- (void)setDisplayingFlutterUI:(
BOOL)displayingFlutterUI {
598 if (_displayingFlutterUI != displayingFlutterUI) {
599 if (displayingFlutterUI == YES) {
600 if (!
self.viewIfLoaded.window) {
604 [
self willChangeValueForKey:@"displayingFlutterUI"];
605 _displayingFlutterUI = displayingFlutterUI;
606 [
self didChangeValueForKey:@"displayingFlutterUI"];
610- (void)callViewRenderedCallback {
611 self.displayingFlutterUI = YES;
612 if (
self.flutterViewRenderedCallback) {
613 self.flutterViewRenderedCallback();
614 self.flutterViewRenderedCallback = nil;
618- (void)onFirstFrameRendered {
619 if (
self.splashScreenView) {
621 [
self.splashScreenManager removeSplashScreenWithCompletion:^{
622 [weakSelf callViewRenderedCallback];
625 [
self callViewRenderedCallback];
629- (void)installFirstFrameCallback {
634 [
self.engine installFirstFrameCallback:^{
635 [weakSelf onFirstFrameRendered];
639#pragma mark - Properties
641- (int64_t)viewIdentifier {
647- (
BOOL)loadDefaultSplashScreenView {
648 return [
self.splashScreenManager loadDefaultSplashScreenView];
651- (UIView*)splashScreenView {
655- (void)setSplashScreenView:(UIView*)view {
656 self.splashScreenManager.splashScreenView =
view;
659- (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
660 _flutterViewRenderedCallback =
callback;
663- (UISceneActivationState)activationState {
664 return self.flutterWindowSceneIfViewLoaded.activationState;
667- (
BOOL)stateIsActive {
670 BOOL isActive = flutterApplication
671 ? [
self isApplicationStateMatching:UIApplicationStateActive
672 withApplication:flutterApplication]
673 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
677- (
BOOL)stateIsBackground {
680 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
681 withApplication:flutterApplication]
682 : [
self isSceneStateMatching:UISceneActivationStateBackground];
685- (
BOOL)isApplicationStateMatching:(UIApplicationState)match
686 withApplication:(UIApplication*)application {
687 switch (application.applicationState) {
688 case UIApplicationStateActive:
689 case UIApplicationStateInactive:
690 case UIApplicationStateBackground:
691 return application.applicationState == match;
695- (
BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
696 switch (
self.activationState) {
697 case UISceneActivationStateForegroundActive:
698 case UISceneActivationStateUnattached:
699 case UISceneActivationStateForegroundInactive:
700 case UISceneActivationStateBackground:
701 return self.activationState == match;
705#pragma mark - Surface creation and teardown updates
707- (void)surfaceUpdated:(
BOOL)appeared {
715 [
self installFirstFrameCallback];
716 self.platformViewsController.flutterView =
self.flutterView;
717 self.platformViewsController.flutterViewController =
self;
718 [
self.engine notifyViewCreated];
720 self.displayingFlutterUI = NO;
721 [
self.engine notifyViewDestroyed];
722 self.platformViewsController.flutterView = nil;
723 self.platformViewsController.flutterViewController = nil;
727#pragma mark - UIViewController lifecycle notifications
732 if (
self.engine &&
self.engineNeedsLaunch) {
733 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
734 [
self.engine setViewController:self];
735 self.engineNeedsLaunch = NO;
736 }
else if (
self.engine.viewController ==
self) {
737 [
self.engine attachView];
741 [
self addInternalPlugins];
744 [
self createTouchRateCorrectionVSyncClientIfNeeded];
746 if (@available(iOS 13.4, *)) {
747 _hoverGestureRecognizer =
748 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
749 _hoverGestureRecognizer.delegate =
self;
750 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
752 _discreteScrollingPanGestureRecognizer =
753 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
754 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
759 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
760 _discreteScrollingPanGestureRecognizer.delegate =
self;
761 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
762 _continuousScrollingPanGestureRecognizer =
763 [[UIPanGestureRecognizer alloc] initWithTarget:self
764 action:@selector(continuousScrollEvent:)];
765 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
766 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
767 _continuousScrollingPanGestureRecognizer.delegate =
self;
768 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
769 _pinchGestureRecognizer =
770 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
771 _pinchGestureRecognizer.allowedTouchTypes = @[];
772 _pinchGestureRecognizer.delegate =
self;
773 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
774 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
775 _rotationGestureRecognizer.allowedTouchTypes = @[];
776 _rotationGestureRecognizer.delegate =
self;
777 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
783- (void)addInternalPlugins {
788 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
790 [
self.keyboardManager
794 [
self.keyboardManager addPrimaryResponder:responder];
797 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
799 if (
self.engine.viewController ==
self) {
804- (void)removeInternalPlugins {
805 self.keyboardManager = nil;
808- (void)viewWillAppear:(
BOOL)animated {
810 if (
self.engine.viewController ==
self) {
812 [
self onUserSettingsChanged:nil];
816 if (_viewportMetrics.physical_width) {
817 [
self surfaceUpdated:YES];
819 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
820 [
self.engine.restorationPlugin markRestorationComplete];
823 [
super viewWillAppear:animated];
826- (void)viewDidAppear:(
BOOL)animated {
828 if (
self.engine.viewController ==
self) {
829 [
self onUserSettingsChanged:nil];
830 [
self onAccessibilityStatusChanged:nil];
832 if (
self.stateIsActive) {
833 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
836 [
super viewDidAppear:animated];
839- (void)viewWillDisappear:(
BOOL)animated {
841 if (
self.engine.viewController ==
self) {
842 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
844 [
super viewWillDisappear:animated];
847- (void)viewDidDisappear:(
BOOL)animated {
849 if (
self.engine.viewController ==
self) {
850 [
self.keyboardInsetManager hideKeyboardImmediately];
851 [
self surfaceUpdated:NO];
852 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
853 [
self flushOngoingTouches];
854 [
self.engine notifyLowMemory];
857 [
super viewDidDisappear:animated];
860- (void)viewWillTransitionToSize:(CGSize)size
861 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
862 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
872 NSTimeInterval transitionDuration = coordinator.transitionDuration;
874 if (transitionDuration == 0) {
879 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
880 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
881 static_cast<int64_t
>(transitionDuration / 2.0 *
NSEC_PER_SEC)),
882 dispatch_get_main_queue(), ^{
890 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
891 [strongSelf updateViewportMetricsIfNeeded];
895- (void)flushOngoingTouches {
896 if (
self.engine &&
self.ongoingTouches.count > 0) {
897 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
898 size_t pointer_index = 0;
901 for (NSNumber*
device in
self.ongoingTouches) {
918 packet->SetPointerData(pointer_index++, pointer_data);
921 [
self.ongoingTouches removeAllObjects];
922 [
self.engine dispatchPointerDataPacket:std::move(packet)];
926- (void)deregisterNotifications {
927 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
930 [[NSNotificationCenter defaultCenter] removeObserver:self];
936 [
self removeInternalPlugins];
937 [
self deregisterNotifications];
939 [
self.keyboardInsetManager invalidate];
940 [
self invalidateTouchRateCorrectionVSyncClient];
944 _scrollView.delegate = nil;
945 _hoverGestureRecognizer.delegate = nil;
946 _discreteScrollingPanGestureRecognizer.delegate = nil;
947 _continuousScrollingPanGestureRecognizer.delegate = nil;
948 _pinchGestureRecognizer.delegate = nil;
949 _rotationGestureRecognizer.delegate = nil;
952#pragma mark - Application lifecycle notifications
954- (void)applicationBecameActive:(NSNotification*)notification {
956 [
self appOrSceneBecameActive];
959- (void)applicationWillResignActive:(NSNotification*)notification {
961 [
self appOrSceneWillResignActive];
964- (void)applicationWillTerminate:(NSNotification*)notification {
965 [
self appOrSceneWillTerminate];
968- (void)applicationDidEnterBackground:(NSNotification*)notification {
969 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
970 [
self appOrSceneDidEnterBackground];
973- (void)applicationWillEnterForeground:(NSNotification*)notification {
974 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
975 [
self appOrSceneWillEnterForeground];
978#pragma mark - Scene lifecycle notifications
980- (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
982 [
self appOrSceneBecameActive];
985- (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
987 [
self appOrSceneWillResignActive];
990- (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
991 [
self appOrSceneWillTerminate];
994- (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
996 [
self appOrSceneDidEnterBackground];
999- (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1001 [
self appOrSceneWillEnterForeground];
1004#pragma mark - Lifecycle shared
1006- (void)appOrSceneBecameActive {
1007 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = NO;
1008 if (_viewportMetrics.physical_width) {
1009 [
self surfaceUpdated:YES];
1011 [
self performSelector:@selector(goToApplicationLifecycle:)
1012 withObject:@"AppLifecycleState.resumed"
1016- (void)appOrSceneWillResignActive {
1017 [NSObject cancelPreviousPerformRequestsWithTarget:self
1018 selector:@selector(goToApplicationLifecycle:)
1019 object:@"AppLifecycleState.resumed"];
1020 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1023- (void)appOrSceneWillTerminate {
1024 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1025 [
self.engine destroyContext];
1028- (void)appOrSceneDidEnterBackground {
1029 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = YES;
1030 [
self surfaceUpdated:NO];
1031 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1034- (void)appOrSceneWillEnterForeground {
1035 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1039- (void)goToApplicationLifecycle:(nonnull NSString*)state {
1042 if (
self.viewIfLoaded.window) {
1043 [
self.engine.lifecycleChannel sendMessage:state];
1047#pragma mark - Touch event handling
1051 case UITouchPhaseBegan:
1053 case UITouchPhaseMoved:
1054 case UITouchPhaseStationary:
1058 case UITouchPhaseEnded:
1060 case UITouchPhaseCancelled:
1064 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1072 switch (touch.type) {
1073 case UITouchTypeDirect:
1074 case UITouchTypeIndirect:
1076 case UITouchTypeStylus:
1078 case UITouchTypeIndirectPointer:
1081 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1092- (void)dispatchTouches:(NSSet*)touches
1093 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1094 event:(UIEvent*)event {
1119 NSUInteger touches_to_remove_count = 0;
1120 for (UITouch* touch in touches) {
1121 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1122 touches_to_remove_count++;
1127 [
self triggerTouchRateCorrectionIfNeeded:touches];
1129 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1131 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1133 size_t pointer_index = 0;
1135 for (UITouch* touch in touches) {
1136 CGPoint windowCoordinates = [touch locationInView:self.view];
1139 pointer_data.
Clear();
1144 pointer_data.
change = overridden_change !=
nullptr
1145 ? *overridden_change
1146 : PointerDataChangeFromUITouchPhase(touch.phase);
1148 pointer_data.
kind = DeviceKindFromTouchType(touch);
1150 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1157 pointer_data.
physical_x = windowCoordinates.x * scale;
1158 pointer_data.
physical_y = windowCoordinates.y * scale;
1164 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1167 switch (pointer_data.
change) {
1169 [
self.ongoingTouches addObject:deviceKey];
1173 [
self.ongoingTouches removeObject:deviceKey];
1191 pointer_data.
pressure = touch.force;
1192 pointer_data.
pressure_max = touch.maximumPossibleForce;
1194 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1195 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1210 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1230 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1232 if (@available(iOS 13.4, *)) {
1233 if (event !=
nullptr) {
1234 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1237 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1243 packet->SetPointerData(pointer_index++, pointer_data);
1245 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1248 packet->SetPointerData(pointer_index++, remove_pointer_data);
1252 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1255- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1256 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1259- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1260 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1263- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1264 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1267- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1268 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1271- (void)forceTouchesCancelled:(NSSet*)touches {
1273 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1276- (
BOOL)platformViewShouldAcceptTouchAtTouchBeganLocation:(CGPoint)location {
1278 return [
self.engine platformViewShouldAcceptTouchAtTouchBeganLocation:point
1279 viewId:self.viewIdentifier];
1282#pragma mark - Touch events rate correction
1284- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1285 if (_touchRateCorrectionVSyncClient != nil) {
1289 double displayRefreshRate = FlutterDisplayLinkManager.displayRefreshRate;
1290 const double epsilon = 0.1;
1291 if (displayRefreshRate < 60.0 + epsilon) {
1299 void (^
callback)(CFTimeInterval, CFTimeInterval) =
1300 ^(CFTimeInterval startTime, CFTimeInterval targetTime) {
1303 _touchRateCorrectionVSyncClient = [[FlutterVSyncClient alloc]
1304 initWithTaskRunner:self.engine.platformTaskRunner
1305 isVariableRefreshRateEnabled:FlutterDisplayLinkManager.maxRefreshRateEnabledOnIPhone
1306 maxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate
1308 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1311- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1312 if (_touchRateCorrectionVSyncClient == nil) {
1320 BOOL isUserInteracting = NO;
1321 for (UITouch* touch in touches) {
1322 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1323 isUserInteracting = YES;
1328 if (isUserInteracting &&
self.engine.viewController ==
self) {
1329 [_touchRateCorrectionVSyncClient await];
1331 [_touchRateCorrectionVSyncClient pause];
1335- (void)invalidateTouchRateCorrectionVSyncClient {
1336 [_touchRateCorrectionVSyncClient invalidate];
1337 _touchRateCorrectionVSyncClient = nil;
1340#pragma mark - Handle view resizing
1342- (void)updateViewportMetricsIfNeeded {
1343 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1346 if (
self.engine.viewController ==
self) {
1347 [
self.engine updateViewportMetrics:_viewportMetrics];
1351- (void)viewDidLayoutSubviews {
1352 CGRect viewBounds =
self.view.bounds;
1353 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1356 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1360 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1361 _viewportMetrics.device_pixel_ratio = scale;
1362 [
self setViewportMetricsSize];
1363 [
self checkAndUpdateAutoResizeConstraints];
1364 [
self setViewportMetricsPaddings];
1365 [
self updateViewportMetricsIfNeeded];
1372 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1373 [
self surfaceUpdated:YES];
1374#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1375 NSTimeInterval timeout = 0.2;
1377 NSTimeInterval timeout = 0.1;
1380 waitForFirstFrameSync:timeout
1381 callback:^(BOOL didTimeout) {
1383 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1384 "This may happen in unoptimized builds. If this is"
1385 "a release build, you should load a less complex "
1386 "frame to avoid the timeout."];
1392- (
BOOL)isAutoResizable {
1393 return self.flutterView.autoResizable;
1396- (void)setAutoResizable:(
BOOL)value {
1398 self.flutterView.contentMode = UIViewContentModeCenter;
1401- (void)checkAndUpdateAutoResizeConstraints {
1402 if (!
self.isAutoResizable) {
1406 [
self updateAutoResizeConstraints];
1430- (void)updateAutoResizeConstraints {
1431 BOOL hasBeenAutoResized = NO;
1432 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1434 hasBeenAutoResized = YES;
1438 if (!hasBeenAutoResized) {
1439 self.sizeBeforeAutoResized =
self.view.frame.size;
1442 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1443 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1444 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1445 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1449 if (maxWidth == 0) {
1450 maxWidth = CGFLOAT_MAX;
1453 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1454 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1455 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1456 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1457 @"constraints (e.g., using SizedBox or Container)."];
1459 if (maxHeight == 0) {
1460 maxHeight = CGFLOAT_MAX;
1463 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1464 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1465 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1466 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1467 @"constraints (e.g., using SizedBox or Container)."];
1469 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1470 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1471 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1472 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1475- (void)viewSafeAreaInsetsDidChange {
1476 [
self setViewportMetricsPaddings];
1477 [
self updateViewportMetricsIfNeeded];
1478 [
super viewSafeAreaInsetsDidChange];
1482- (void)setViewportMetricsSize {
1483 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1488 CGFloat scale = screen.scale;
1489 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1490 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1492 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1493 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1494 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1495 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1501- (void)setViewportMetricsPaddings {
1502 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1507 CGFloat scale = screen.scale;
1508 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1509 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1510 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1511 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1514#pragma mark - Keyboard events
1516- (void)keyboardWillShowNotification:(NSNotification*)notification {
1521 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1524- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1529 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1532- (void)keyboardWillBeHidden:(NSNotification*)notification {
1536 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1539- (void)handlePressEvent:(FlutterUIPressProxy*)press
1540 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1541 if (@available(iOS 13.4, *)) {
1546 [
self.keyboardManager handlePress:press nextAction:next];
1562- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1563 [
super pressesBegan:presses withEvent:event];
1566- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1567 [
super pressesChanged:presses withEvent:event];
1570- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1571 [
super pressesEnded:presses withEvent:event];
1574- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1575 [
super pressesCancelled:presses withEvent:event];
1583- (void)pressesBegan:(NSSet<UIPress*>*)presses
1584 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1585 if (@available(iOS 13.4, *)) {
1587 for (UIPress* press in presses) {
1588 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1590 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1594 [
super pressesBegan:presses withEvent:event];
1598- (void)pressesChanged:(NSSet<UIPress*>*)presses
1599 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1600 if (@available(iOS 13.4, *)) {
1602 for (UIPress* press in presses) {
1603 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1605 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1609 [
super pressesChanged:presses withEvent:event];
1613- (void)pressesEnded:(NSSet<UIPress*>*)presses
1614 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1615 if (@available(iOS 13.4, *)) {
1617 for (UIPress* press in presses) {
1618 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1620 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1624 [
super pressesEnded:presses withEvent:event];
1628- (void)pressesCancelled:(NSSet<UIPress*>*)presses
1629 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1630 if (@available(iOS 13.4, *)) {
1632 for (UIPress* press in presses) {
1633 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1635 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1639 [
super pressesCancelled:presses withEvent:event];
1643#pragma mark - Orientation updates
1645- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1648 dispatch_async(dispatch_get_main_queue(), ^{
1649 NSDictionary* info = notification.userInfo;
1650 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1651 if (update == nil) {
1654 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1658- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1659 API_AVAILABLE(ios(16.0)) {
1660 for (UIScene* windowScene in windowScenes) {
1661 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
1662 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1663 initWithInterfaceOrientations:self.orientationPreferences];
1664 [(UIWindowScene*)windowScene
1665 requestGeometryUpdateWithPreferences:preference
1666 errorHandler:^(NSError* error) {
1667 os_log_error(OS_LOG_DEFAULT,
1668 "Failed to change device orientation: %@", error);
1670 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
1674- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
1675 if (new_preferences !=
self.orientationPreferences) {
1676 self.orientationPreferences = new_preferences;
1678 if (@available(iOS 16.0, *)) {
1680 NSSet<UIScene*>* scenes = [NSSet set];
1681 if (flutterApplication) {
1682 scenes = [flutterApplication.connectedScenes
1683 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
1684 id scene, NSDictionary* bindings) {
1685 return [scene isKindOfClass:[UIWindowScene class]];
1687 }
else if (
self.flutterWindowSceneIfViewLoaded) {
1688 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
1690 [
self requestGeometryUpdateForWindowScenes:scenes];
1692 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
1693 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
1697 @"Accessing the interface orientation when the window scene is unavailable."];
1700 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
1701 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
1702 [UIViewController attemptRotationToDeviceOrientation];
1704 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
1708 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
1709 forKey:@"orientation"];
1710 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
1711 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
1712 forKey:@"orientation"];
1713 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
1714 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
1715 forKey:@"orientation"];
1716 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
1717 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
1718 forKey:@"orientation"];
1725- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
1726 self.isHomeIndicatorHidden = YES;
1729- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
1730 self.isHomeIndicatorHidden = NO;
1733- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
1734 if (hideHomeIndicator != _isHomeIndicatorHidden) {
1735 _isHomeIndicatorHidden = hideHomeIndicator;
1736 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
1740- (
BOOL)prefersHomeIndicatorAutoHidden {
1741 return self.isHomeIndicatorHidden;
1744- (
BOOL)shouldAutorotate {
1748- (NSUInteger)supportedInterfaceOrientations {
1749 return self.orientationPreferences;
1752#pragma mark - Accessibility
1754- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1759 int32_t flags = [
self.accessibilityFeatures flags];
1760#if TARGET_OS_SIMULATOR
1766 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
1767 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
1768 [
self.accessibilityFeatures isSpeakScreenEnabled];
1770 [
self.engine enableSemantics:enabled withFlags:flags];
1773- (
BOOL)accessibilityPerformEscape {
1775 if (navigationChannel) {
1782#pragma mark - Set user settings
1784- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1785 [
super traitCollectionDidChange:previousTraitCollection];
1786 [
self onUserSettingsChanged:nil];
1790 if (
self.isAutoResizable) {
1791 [
self.flutterView resetIntrinsicContentSize];
1795- (void)onUserSettingsChanged:(NSNotification*)notification {
1796 [
self.engine.settingsChannel sendMessage:@{
1797 @"textScaleFactor" : @(
self.textScaleFactor),
1799 @"platformBrightness" :
self.brightnessMode,
1800 @"platformContrast" : self.contrastMode,
1801 @"nativeSpellCheckServiceDefined" : @YES,
1802 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
1806- (CGFloat)textScaleFactor {
1808 if (flutterApplication == nil) {
1809 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
1813 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
1819 const CGFloat xs = 14;
1820 const CGFloat s = 15;
1821 const CGFloat m = 16;
1822 const CGFloat l = 17;
1823 const CGFloat xl = 19;
1824 const CGFloat xxl = 21;
1825 const CGFloat xxxl = 23;
1828 const CGFloat ax1 = 28;
1829 const CGFloat ax2 = 33;
1830 const CGFloat ax3 = 40;
1831 const CGFloat ax4 = 47;
1832 const CGFloat ax5 = 53;
1836 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
1838 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
1840 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
1842 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
1844 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
1846 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
1848 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
1850 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
1852 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
1854 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
1856 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
1858 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
1865- (
BOOL)supportsShowingSystemContextMenu {
1866 if (@available(iOS 16.0, *)) {
1876- (NSString*)brightnessMode {
1877 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
1879 if (style == UIUserInterfaceStyleDark) {
1889- (NSString*)contrastMode {
1890 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
1892 if (contrast == UIAccessibilityContrastHigh) {
1899#pragma mark - Status bar style
1901- (UIStatusBarStyle)preferredStatusBarStyle {
1902 return self.statusBarStyle;
1905- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
1908 dispatch_async(dispatch_get_main_queue(), ^{
1914 NSDictionary* info = notification.userInfo;
1915 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
1916 if (update == nil) {
1920 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
1921 if (style != strongSelf.statusBarStyle) {
1922 strongSelf.statusBarStyle = style;
1923 [strongSelf setNeedsStatusBarAppearanceUpdate];
1928- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
1929 if (hidden !=
self.flutterPrefersStatusBarHidden) {
1930 self.flutterPrefersStatusBarHidden = hidden;
1931 [
self setNeedsStatusBarAppearanceUpdate];
1935- (
BOOL)prefersStatusBarHidden {
1936 return self.flutterPrefersStatusBarHidden;
1939#pragma mark - Platform views
1942 return self.engine.platformViewsController;
1945- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1946 return self.engine.binaryMessenger;
1949#pragma mark - FlutterBinaryMessenger
1951- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1952 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
1955- (void)sendOnChannel:(NSString*)channel
1956 message:(NSData*)message
1958 NSAssert(
channel,
@"The channel must not be null");
1959 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
1962- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1963 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
1967 binaryMessageHandler:
1969 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
1973 setMessageHandlerOnChannel:(NSString*)channel
1975 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
1976 NSAssert(
channel,
@"The channel must not be null");
1977 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
1978 binaryMessageHandler:handler
1979 taskQueue:taskQueue];
1983 [
self.engine.binaryMessenger cleanUpConnection:connection];
1986#pragma mark - FlutterTextureRegistry
1989 return [
self.engine.textureRegistry registerTexture:texture];
1992- (void)unregisterTexture:(int64_t)textureId {
1993 [
self.engine.textureRegistry unregisterTexture:textureId];
1996- (void)textureFrameAvailable:(int64_t)textureId {
1997 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2000- (NSString*)lookupKeyForAsset:(NSString*)asset {
2004- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2008- (
id<FlutterPluginRegistry>)pluginRegistry {
2012+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2013 return UIAccessibilityIsVoiceOverRunning();
2016#pragma mark - FlutterPluginRegistry
2018- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
2019 return [
self.engine registrarForPlugin:pluginKey];
2022- (
BOOL)hasPlugin:(NSString*)pluginKey {
2023 return [
self.engine hasPlugin:pluginKey];
2026- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2027 return [
self.engine valuePublishedByPlugin:pluginKey];
2030- (void)presentViewController:(UIViewController*)viewControllerToPresent
2032 completion:(
void (^)(
void))completion {
2033 self.isPresentingViewControllerAnimating = YES;
2035 [
super presentViewController:viewControllerToPresent
2038 weakSelf.isPresentingViewControllerAnimating = NO;
2045- (
BOOL)isPresentingViewController {
2046 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2049- (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2050 API_AVAILABLE(ios(13.4)) {
2051 CGPoint
location = [gestureRecognizer locationInView:self.view];
2052 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2055 pointer_data.
Clear();
2059 return pointer_data;
2062- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2063 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2064 API_AVAILABLE(ios(13.4)) {
2068- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2069 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2070 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2071 event.type == UIEventTypeScroll) {
2074 pointer_data.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2079 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2082 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2083 packet->SetPointerData(0, pointer_data);
2084 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2085 self.scrollInertiaEventAppKitDeadline = 0;
2092- (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2096 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2100 switch (_hoverGestureRecognizer.state) {
2101 case UIGestureRecognizerStateBegan:
2104 case UIGestureRecognizerStateChanged:
2107 case UIGestureRecognizerStateEnded:
2108 case UIGestureRecognizerStateCancelled:
2118 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2119 BOOL isRunningOnMac = NO;
2120 if (@available(iOS 14.0, *)) {
2124 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2127 time >
self.scrollInertiaEventStartline) {
2131 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2132 packet->SetPointerData(0, pointer_data);
2134 inertia_cancel.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2138 packet->SetPointerData(1, inertia_cancel);
2139 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2140 self.scrollInertiaEventStartline = DBL_MAX;
2142 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2143 packet->SetPointerData(0, pointer_data);
2144 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2148- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2149 CGPoint translation = [recognizer translationInView:self.view];
2150 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2152 translation.x *= scale;
2153 translation.y *= scale;
2156 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2167 if (recognizer.state != UIGestureRecognizerStateEnded) {
2173 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2174 packet->SetPointerData(0, pointer_data);
2175 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2178- (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2179 CGPoint translation = [recognizer translationInView:self.view];
2180 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2183 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2186 switch (recognizer.state) {
2187 case UIGestureRecognizerStateBegan:
2190 case UIGestureRecognizerStateChanged:
2192 pointer_data.
pan_x = translation.x * scale;
2193 pointer_data.
pan_y = translation.y * scale;
2196 pointer_data.
scale = 1;
2198 case UIGestureRecognizerStateEnded:
2199 case UIGestureRecognizerStateCancelled:
2200 self.scrollInertiaEventStartline =
2201 [[NSProcessInfo processInfo] systemUptime] +
2211 self.scrollInertiaEventAppKitDeadline =
2212 [[NSProcessInfo processInfo] systemUptime] +
2213 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2214 [recognizer velocityInView:self.view].y))) -
2220 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2221 (
long)recognizer.state);
2225 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2226 packet->SetPointerData(0, pointer_data);
2227 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2230- (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2232 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2235 switch (recognizer.state) {
2236 case UIGestureRecognizerStateBegan:
2239 case UIGestureRecognizerStateChanged:
2241 pointer_data.
scale = recognizer.scale;
2242 pointer_data.
rotation = _rotationGestureRecognizer.rotation;
2244 case UIGestureRecognizerStateEnded:
2245 case UIGestureRecognizerStateCancelled:
2250 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2251 (
long)recognizer.state);
2255 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2256 packet->SetPointerData(0, pointer_data);
2257 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2260#pragma mark - State Restoration
2262- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2263 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2264 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2265 length:restorationData.length
2266 forKey:kFlutterRestorationStateAppData];
2267 [
super encodeRestorableStateWithCoder:coder];
2270- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2271 NSUInteger restorationDataLength;
2272 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2273 returnedLength:&restorationDataLength];
2274 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2275 [
self.engine.restorationPlugin setRestorationData:restorationData];
2279 return self.engine.restorationPlugin;
2283 return self.engine.textInputPlugin;
2286#pragma mark - FlutterKeyboardInsetManagerDelegate
2288- (void)updateViewportMetricsWithInset:(CGFloat)inset {
2289 _viewportMetrics.physical_view_inset_bottom = inset;
2290 [
self updateViewportMetricsIfNeeded];
2293- (CGFloat)physicalViewInsetBottom {
2294 return _viewportMetrics.physical_view_inset_bottom;
2297- (
BOOL)isPadInSlideOverOrStageManagerMode {
2298 if (
self.view.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
2299 self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
2300 self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
2306- (CGRect)convertViewRectToScreen:(CGRect)rect {
2307 return [
self.view convertRect:rect
2308 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
Coordinates the animation of the bottom viewport inset in response to system keyboard visibility chan...
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)