5 #define FML_USED_ON_EMBEDDER
12 #include "flutter/common/constants.h"
13 #include "flutter/fml/memory/weak_ptr.h"
14 #include "flutter/fml/message_loop.h"
15 #include "flutter/fml/platform/darwin/platform_version.h"
16 #include "flutter/runtime/ptrace_check.h"
17 #include "flutter/shell/common/thread_host.h"
18 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
38 #import "flutter/shell/platform/embedder/embedder.h"
39 #import "flutter/third_party/spring_animation/spring_animation.h"
51 @"FlutterViewControllerHideHomeIndicator";
53 @"FlutterViewControllerShowHomeIndicator";
71 @property(nonatomic, readonly) int64_t viewIdentifier;
76 @property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
78 @property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
79 @property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
80 @property(nonatomic, assign) BOOL initialized;
81 @property(nonatomic, assign) BOOL engineNeedsLaunch;
82 @property(nonatomic, assign) BOOL awokenFromNib;
85 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
86 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
89 @property(nonatomic, assign) BOOL flutterPrefersStatusBarHidden;
91 @property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
96 @property(nonatomic, strong) UIScrollView* scrollView;
97 @property(nonatomic, strong) UIView* keyboardAnimationView;
98 @property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation;
103 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
108 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
109 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
110 @property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
111 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
112 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
113 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
116 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
124 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
132 @property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
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;
161 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime;
165 flutter::ViewportMetrics _viewportMetrics;
170 @synthesize viewOpaque = _viewOpaque;
171 @synthesize displayingFlutterUI = _displayingFlutterUI;
176 @dynamic viewIdentifier;
178 #pragma mark - Manage and override all designated initializers
181 nibName:(nullable NSString*)nibName
182 bundle:(nullable NSBundle*)nibBundle {
183 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
184 self = [
super initWithNibName:nibName bundle:nibBundle];
187 if (
engine.viewController) {
188 NSString* errorMessage =
189 [NSString stringWithFormat:
190 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
191 "instance %@. One instance of the FlutterEngine can only be attached to "
192 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
193 "nil before attaching it to another FlutterViewController.",
194 engine.description, engine.viewController.description];
195 [FlutterLogger logError:errorMessage];
198 _engineNeedsLaunch = NO;
199 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
200 opaque:self.isViewOpaque
201 enableWideGamut:engine.project.isWideGamutEnabled];
202 _ongoingTouches = [[NSMutableSet alloc] init];
206 [
self performCommonViewControllerInitialization];
207 [engine setViewController:self];
214 nibName:(NSString*)nibName
215 bundle:(NSBundle*)nibBundle {
216 self = [
super initWithNibName:nibName bundle:nibBundle];
220 [
self sharedSetupWithProject:project initialRoute:nil];
227 initialRoute:(NSString*)initialRoute
228 nibName:(NSString*)nibName
229 bundle:(NSBundle*)nibBundle {
230 self = [
super initWithNibName:nibName bundle:nibBundle];
234 [
self sharedSetupWithProject:project initialRoute:initialRoute];
240 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
241 return [
self initWithProject:nil nibName:nil bundle:nil];
245 self = [
super initWithCoder:aDecoder];
249 - (void)awakeFromNib {
250 [
super awakeFromNib];
251 self.awokenFromNib = YES;
253 [
self sharedSetupWithProject:nil initialRoute:nil];
257 - (instancetype)init {
258 return [
self initWithProject:nil nibName:nil bundle:nil];
262 initialRoute:(nullable NSString*)initialRoute {
265 if ([appDelegate respondsToSelector:
@selector(takeLaunchEngine)]) {
270 engine = [appDelegate takeLaunchEngine];
275 [appDelegate takeLaunchEngine];
287 allowHeadlessExecution:self.engineAllowHeadlessExecution
288 restorationEnabled:self.restorationIdentifier != nil];
296 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
298 enableWideGamut:engine.project.isWideGamutEnabled];
299 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
305 BOOL performedCallback = [_engine performImplicitEngineCallback];
309 respondsToSelector:
@selector(pluginRegistrant)]) {
310 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
312 [pluginRegistrant registerWithRegistry:self];
313 performedCallback = YES;
321 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
322 [applicationLifeCycleDelegate
323 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
324 [applicationLifeCycleDelegate
325 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
328 _engineNeedsLaunch = YES;
329 _ongoingTouches = [[NSMutableSet alloc] init];
333 [
self loadDefaultSplashScreenView];
334 [
self performCommonViewControllerInitialization];
337 - (BOOL)isViewOpaque {
341 - (void)setViewOpaque:(BOOL)value {
343 if (
self.flutterView.layer.opaque != value) {
344 self.flutterView.layer.opaque = value;
345 [
self.flutterView.layer setNeedsLayout];
349 #pragma mark - Common view controller initialization tasks
351 - (void)performCommonViewControllerInitialization {
357 _orientationPreferences = UIInterfaceOrientationMaskAll;
358 _statusBarStyle = UIStatusBarStyleDefault;
362 [
self setUpNotificationCenterObservers];
365 - (void)setUpNotificationCenterObservers {
366 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
367 [center addObserver:self
368 selector:@selector(onOrientationPreferencesUpdated:)
369 name:@(flutter::kOrientationUpdateNotificationName)
372 [center addObserver:self
373 selector:@selector(onPreferredStatusBarStyleUpdated:)
374 name:@(flutter::kOverlayStyleUpdateNotificationName)
378 [
self setUpApplicationLifecycleNotifications:center];
380 [
self setUpSceneLifecycleNotifications:center];
383 [center addObserver:self
384 selector:@selector(keyboardWillChangeFrame:)
385 name:UIKeyboardWillChangeFrameNotification
388 [center addObserver:self
389 selector:@selector(keyboardWillShowNotification:)
390 name:UIKeyboardWillShowNotification
393 [center addObserver:self
394 selector:@selector(keyboardWillBeHidden:)
395 name:UIKeyboardWillHideNotification
398 [center addObserver:self
399 selector:@selector(onAccessibilityStatusChanged:)
400 name:UIAccessibilityVoiceOverStatusDidChangeNotification
403 [center addObserver:self
404 selector:@selector(onAccessibilityStatusChanged:)
405 name:UIAccessibilitySwitchControlStatusDidChangeNotification
408 [center addObserver:self
409 selector:@selector(onAccessibilityStatusChanged:)
410 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
413 [center addObserver:self
414 selector:@selector(onAccessibilityStatusChanged:)
415 name:UIAccessibilityInvertColorsStatusDidChangeNotification
418 [center addObserver:self
419 selector:@selector(onAccessibilityStatusChanged:)
420 name:UIAccessibilityReduceMotionStatusDidChangeNotification
423 [center addObserver:self
424 selector:@selector(onAccessibilityStatusChanged:)
425 name:UIAccessibilityBoldTextStatusDidChangeNotification
428 [center addObserver:self
429 selector:@selector(onAccessibilityStatusChanged:)
430 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
433 [center addObserver:self
434 selector:@selector(onAccessibilityStatusChanged:)
435 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
438 [center addObserver:self
439 selector:@selector(onUserSettingsChanged:)
440 name:UIContentSizeCategoryDidChangeNotification
443 [center addObserver:self
444 selector:@selector(onHideHomeIndicatorNotification:)
445 name:FlutterViewControllerHideHomeIndicator
448 [center addObserver:self
449 selector:@selector(onShowHomeIndicatorNotification:)
450 name:FlutterViewControllerShowHomeIndicator
454 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
455 [center addObserver:self
456 selector:@selector(sceneBecameActive:)
457 name:UISceneDidActivateNotification
460 [center addObserver:self
461 selector:@selector(sceneWillResignActive:)
462 name:UISceneWillDeactivateNotification
465 [center addObserver:self
466 selector:@selector(sceneWillDisconnect:)
467 name:UISceneDidDisconnectNotification
470 [center addObserver:self
471 selector:@selector(sceneDidEnterBackground:)
472 name:UISceneDidEnterBackgroundNotification
475 [center addObserver:self
476 selector:@selector(sceneWillEnterForeground:)
477 name:UISceneWillEnterForegroundNotification
481 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
482 [center addObserver:self
483 selector:@selector(applicationBecameActive:)
484 name:UIApplicationDidBecomeActiveNotification
487 [center addObserver:self
488 selector:@selector(applicationWillResignActive:)
489 name:UIApplicationWillResignActiveNotification
492 [center addObserver:self
493 selector:@selector(applicationWillTerminate:)
494 name:UIApplicationWillTerminateNotification
497 [center addObserver:self
498 selector:@selector(applicationDidEnterBackground:)
499 name:UIApplicationDidEnterBackgroundNotification
502 [center addObserver:self
503 selector:@selector(applicationWillEnterForeground:)
504 name:UIApplicationWillEnterForegroundNotification
508 - (void)setInitialRoute:(NSString*)route {
509 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
513 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
516 - (void)pushRoute:(NSString*)route {
517 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
520 #pragma mark - Loading the view
522 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
524 return existing_view;
527 auto placeholder = [[UIView alloc] init];
529 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
530 placeholder.backgroundColor = UIColor.systemBackgroundColor;
531 placeholder.autoresizesSubviews = YES;
536 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
537 auto messageLabel = [[UILabel alloc] init];
538 messageLabel.numberOfLines = 0u;
539 messageLabel.textAlignment = NSTextAlignmentCenter;
540 messageLabel.autoresizingMask =
541 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
543 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
544 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
545 @"modes to enable launching from the home screen.";
546 [placeholder addSubview:messageLabel];
553 self.view = GetViewOrPlaceholder(
self.flutterView);
554 self.view.multipleTouchEnabled = YES;
555 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
557 [
self installSplashScreenViewIfNecessary];
560 UIScrollView* scrollView = [[UIScrollView alloc] init];
561 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
563 scrollView.backgroundColor = UIColor.whiteColor;
564 scrollView.delegate =
self;
570 [
self.view addSubview:scrollView];
571 self.scrollView = scrollView;
574 - (
flutter::PointerData)generatePointerDataForFake {
575 flutter::PointerData pointer_data;
576 pointer_data.Clear();
577 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
585 static void SendFakeTouchEvent(UIScreen* screen,
588 flutter::PointerData::Change change) {
589 const CGFloat scale = screen.scale;
590 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
591 pointer_data.physical_x = location.x * scale;
592 pointer_data.physical_y = location.y * scale;
593 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
594 pointer_data.change = change;
595 packet->SetPointerData(0, pointer_data);
596 [engine dispatchPointerDataPacket:std::move(packet)];
599 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
603 CGPoint statusBarPoint = CGPointZero;
604 UIScreen* screen =
self.flutterScreenIfViewLoaded;
606 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
607 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
612 #pragma mark - Managing launch views
614 - (void)installSplashScreenViewIfNecessary {
617 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
618 [
self.splashScreenView removeFromSuperview];
619 self.splashScreenView = nil;
624 UIView* splashScreenView =
self.splashScreenView;
625 if (splashScreenView == nil) {
628 splashScreenView.frame =
self.view.bounds;
629 [
self.view addSubview:splashScreenView];
632 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
636 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
637 if (_displayingFlutterUI != displayingFlutterUI) {
638 if (displayingFlutterUI == YES) {
639 if (!
self.viewIfLoaded.window) {
643 [
self willChangeValueForKey:@"displayingFlutterUI"];
644 _displayingFlutterUI = displayingFlutterUI;
645 [
self didChangeValueForKey:@"displayingFlutterUI"];
649 - (void)callViewRenderedCallback {
650 self.displayingFlutterUI = YES;
651 if (
self.flutterViewRenderedCallback) {
652 self.flutterViewRenderedCallback();
653 self.flutterViewRenderedCallback = nil;
657 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
658 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
659 UIView* splashScreen =
self.splashScreenView;
661 _splashScreenView = nil;
662 [UIView animateWithDuration:0.2
664 splashScreen.alpha = 0;
666 completion:^(BOOL finished) {
667 [splashScreen removeFromSuperview];
674 - (void)onFirstFrameRendered {
675 if (
self.splashScreenView) {
677 [
self removeSplashScreenWithCompletion:^{
678 [weakSelf callViewRenderedCallback];
681 [
self callViewRenderedCallback];
685 - (void)installFirstFrameCallback {
690 [
self.engine installFirstFrameCallback:^{
691 [weakSelf onFirstFrameRendered];
695 #pragma mark - Properties
697 - (int64_t)viewIdentifier {
700 return flutter::kFlutterImplicitViewId;
703 - (BOOL)loadDefaultSplashScreenView {
704 NSString* launchscreenName =
705 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
706 if (launchscreenName == nil) {
709 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
711 splashView = [
self splashScreenFromXib:launchscreenName];
720 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
721 UIStoryboard* storyboard = nil;
723 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
724 }
@catch (NSException* exception) {
728 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
729 return splashScreenViewController.view;
734 - (UIView*)splashScreenFromXib:(NSString*)name {
735 NSArray* objects = nil;
737 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
738 }
@catch (NSException* exception) {
741 if ([objects count] != 0) {
742 UIView* view = [objects objectAtIndex:0];
748 - (void)setSplashScreenView:(UIView*)view {
749 if (view == _splashScreenView) {
755 if (_splashScreenView) {
756 [
self removeSplashScreenWithCompletion:nil];
761 _splashScreenView = view;
762 _splashScreenView.autoresizingMask =
763 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
766 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
767 _flutterViewRenderedCallback = callback;
770 - (UISceneActivationState)activationState {
771 return self.flutterWindowSceneIfViewLoaded.activationState;
774 - (BOOL)stateIsActive {
777 BOOL isActive = flutterApplication
778 ? [
self isApplicationStateMatching:UIApplicationStateActive
779 withApplication:flutterApplication]
780 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
784 - (BOOL)stateIsBackground {
787 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
788 withApplication:flutterApplication]
789 : [
self isSceneStateMatching:UISceneActivationStateBackground];
792 - (BOOL)isApplicationStateMatching:(UIApplicationState)match
793 withApplication:(UIApplication*)application {
794 switch (application.applicationState) {
795 case UIApplicationStateActive:
796 case UIApplicationStateInactive:
797 case UIApplicationStateBackground:
798 return application.applicationState == match;
802 - (BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
803 switch (
self.activationState) {
804 case UISceneActivationStateForegroundActive:
805 case UISceneActivationStateUnattached:
806 case UISceneActivationStateForegroundInactive:
807 case UISceneActivationStateBackground:
808 return self.activationState == match;
812 #pragma mark - Surface creation and teardown updates
814 - (void)surfaceUpdated:(BOOL)appeared {
822 [
self installFirstFrameCallback];
823 self.platformViewsController.flutterView =
self.flutterView;
824 self.platformViewsController.flutterViewController =
self;
825 [
self.engine notifyViewCreated];
827 self.displayingFlutterUI = NO;
828 [
self.engine notifyViewDestroyed];
829 self.platformViewsController.flutterView = nil;
830 self.platformViewsController.flutterViewController = nil;
834 #pragma mark - UIViewController lifecycle notifications
836 - (void)viewDidLoad {
837 TRACE_EVENT0(
"flutter",
"viewDidLoad");
839 if (
self.
engine &&
self.engineNeedsLaunch) {
840 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
841 [
self.engine setViewController:self];
842 self.engineNeedsLaunch = NO;
843 }
else if (
self.
engine.viewController ==
self) {
844 [
self.engine attachView];
848 [
self addInternalPlugins];
851 [
self createTouchRateCorrectionVSyncClientIfNeeded];
853 if (@available(iOS 13.4, *)) {
854 _hoverGestureRecognizer =
855 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
856 _hoverGestureRecognizer.delegate =
self;
857 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
859 _discreteScrollingPanGestureRecognizer =
860 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
861 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
866 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
867 _discreteScrollingPanGestureRecognizer.delegate =
self;
868 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
869 _continuousScrollingPanGestureRecognizer =
870 [[UIPanGestureRecognizer alloc] initWithTarget:self
871 action:@selector(continuousScrollEvent:)];
872 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
873 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
874 _continuousScrollingPanGestureRecognizer.delegate =
self;
875 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
876 _pinchGestureRecognizer =
877 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
878 _pinchGestureRecognizer.allowedTouchTypes = @[];
879 _pinchGestureRecognizer.delegate =
self;
880 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
881 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
882 _rotationGestureRecognizer.allowedTouchTypes = @[];
883 _rotationGestureRecognizer.delegate =
self;
884 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
890 - (void)addInternalPlugins {
894 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
895 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
897 [
self.keyboardManager
901 [
self.keyboardManager addPrimaryResponder:responder];
904 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
906 if (
self.
engine.viewController ==
self) {
911 - (void)removeInternalPlugins {
912 self.keyboardManager = nil;
915 - (void)viewWillAppear:(BOOL)animated {
916 TRACE_EVENT0(
"flutter",
"viewWillAppear");
917 if (
self.
engine.viewController ==
self) {
919 [
self onUserSettingsChanged:nil];
923 if (_viewportMetrics.physical_width) {
924 [
self surfaceUpdated:YES];
926 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
927 [
self.engine.restorationPlugin markRestorationComplete];
930 [
super viewWillAppear:animated];
933 - (void)viewDidAppear:(BOOL)animated {
934 TRACE_EVENT0(
"flutter",
"viewDidAppear");
935 if (
self.
engine.viewController ==
self) {
936 [
self onUserSettingsChanged:nil];
937 [
self onAccessibilityStatusChanged:nil];
939 if (
self.stateIsActive) {
940 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
943 [
super viewDidAppear:animated];
946 - (void)viewWillDisappear:(BOOL)animated {
947 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
948 if (
self.
engine.viewController ==
self) {
949 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
951 [
super viewWillDisappear:animated];
954 - (void)viewDidDisappear:(BOOL)animated {
955 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
956 if (
self.
engine.viewController ==
self) {
957 [
self invalidateKeyboardAnimationVSyncClient];
958 [
self ensureViewportMetricsIsCorrect];
959 [
self surfaceUpdated:NO];
960 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
961 [
self flushOngoingTouches];
962 [
self.engine notifyLowMemory];
965 [
super viewDidDisappear:animated];
968 - (void)viewWillTransitionToSize:(CGSize)size
969 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
970 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
980 NSTimeInterval transitionDuration = coordinator.transitionDuration;
982 if (transitionDuration == 0) {
987 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
988 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
989 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
990 dispatch_get_main_queue(), ^{
998 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
999 [strongSelf updateViewportMetricsIfNeeded];
1003 - (void)flushOngoingTouches {
1004 if (
self.
engine &&
self.ongoingTouches.count > 0) {
1005 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
1006 size_t pointer_index = 0;
1009 for (NSNumber* device in
self.ongoingTouches) {
1011 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
1013 pointer_data.change = flutter::PointerData::Change::kCancel;
1014 pointer_data.device = device.longLongValue;
1015 pointer_data.pointer_identifier = 0;
1016 pointer_data.view_id =
self.viewIdentifier;
1019 pointer_data.physical_x = 0;
1020 pointer_data.physical_y = 0;
1021 pointer_data.physical_delta_x = 0.0;
1022 pointer_data.physical_delta_y = 0.0;
1023 pointer_data.pressure = 1.0;
1024 pointer_data.pressure_max = 1.0;
1026 packet->SetPointerData(pointer_index++, pointer_data);
1029 [
self.ongoingTouches removeAllObjects];
1030 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1034 - (void)deregisterNotifications {
1035 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
1038 [[NSNotificationCenter defaultCenter] removeObserver:self];
1044 [
self removeInternalPlugins];
1045 [
self deregisterNotifications];
1047 [
self invalidateKeyboardAnimationVSyncClient];
1048 [
self invalidateTouchRateCorrectionVSyncClient];
1052 _scrollView.delegate = nil;
1053 _hoverGestureRecognizer.delegate = nil;
1054 _discreteScrollingPanGestureRecognizer.delegate = nil;
1055 _continuousScrollingPanGestureRecognizer.delegate = nil;
1056 _pinchGestureRecognizer.delegate = nil;
1057 _rotationGestureRecognizer.delegate = nil;
1060 #pragma mark - Application lifecycle notifications
1062 - (void)applicationBecameActive:(NSNotification*)notification {
1063 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
1064 [
self appOrSceneBecameActive];
1067 - (void)applicationWillResignActive:(NSNotification*)notification {
1068 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1069 [
self appOrSceneWillResignActive];
1072 - (void)applicationWillTerminate:(NSNotification*)notification {
1073 [
self appOrSceneWillTerminate];
1076 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1077 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1078 [
self appOrSceneDidEnterBackground];
1081 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1082 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1083 [
self appOrSceneWillEnterForeground];
1086 #pragma mark - Scene lifecycle notifications
1088 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1089 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1090 [
self appOrSceneBecameActive];
1093 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1094 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1095 [
self appOrSceneWillResignActive];
1098 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1099 [
self appOrSceneWillTerminate];
1102 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1103 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1104 [
self appOrSceneDidEnterBackground];
1107 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1108 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1109 [
self appOrSceneWillEnterForeground];
1112 #pragma mark - Lifecycle shared
1114 - (void)appOrSceneBecameActive {
1115 self.isKeyboardInOrTransitioningFromBackground = NO;
1116 if (_viewportMetrics.physical_width) {
1117 [
self surfaceUpdated:YES];
1119 [
self performSelector:@selector(goToApplicationLifecycle:)
1120 withObject:@"AppLifecycleState.resumed"
1124 - (void)appOrSceneWillResignActive {
1125 [NSObject cancelPreviousPerformRequestsWithTarget:self
1126 selector:@selector(goToApplicationLifecycle:)
1127 object:@"AppLifecycleState.resumed"];
1128 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1131 - (void)appOrSceneWillTerminate {
1132 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1133 [
self.engine destroyContext];
1136 - (void)appOrSceneDidEnterBackground {
1137 self.isKeyboardInOrTransitioningFromBackground = YES;
1138 [
self surfaceUpdated:NO];
1139 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1142 - (void)appOrSceneWillEnterForeground {
1143 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1147 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1150 if (
self.viewIfLoaded.window) {
1151 [
self.engine.lifecycleChannel sendMessage:state];
1155 #pragma mark - Touch event handling
1157 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1159 case UITouchPhaseBegan:
1160 return flutter::PointerData::Change::kDown;
1161 case UITouchPhaseMoved:
1162 case UITouchPhaseStationary:
1165 return flutter::PointerData::Change::kMove;
1166 case UITouchPhaseEnded:
1167 return flutter::PointerData::Change::kUp;
1168 case UITouchPhaseCancelled:
1169 return flutter::PointerData::Change::kCancel;
1172 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1176 return flutter::PointerData::Change::kCancel;
1179 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1180 switch (touch.type) {
1181 case UITouchTypeDirect:
1182 case UITouchTypeIndirect:
1183 return flutter::PointerData::DeviceKind::kTouch;
1184 case UITouchTypeStylus:
1185 return flutter::PointerData::DeviceKind::kStylus;
1186 case UITouchTypeIndirectPointer:
1187 return flutter::PointerData::DeviceKind::kMouse;
1189 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1193 return flutter::PointerData::DeviceKind::kTouch;
1200 - (void)dispatchTouches:(NSSet*)touches
1201 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1202 event:(UIEvent*)event {
1227 NSUInteger touches_to_remove_count = 0;
1228 for (UITouch* touch in touches) {
1229 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1230 touches_to_remove_count++;
1235 [
self triggerTouchRateCorrectionIfNeeded:touches];
1237 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1239 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1241 size_t pointer_index = 0;
1243 for (UITouch* touch in touches) {
1244 CGPoint windowCoordinates = [touch locationInView:self.view];
1246 flutter::PointerData pointer_data;
1247 pointer_data.Clear();
1252 pointer_data.change = overridden_change !=
nullptr
1253 ? *overridden_change
1254 : PointerDataChangeFromUITouchPhase(touch.phase);
1256 pointer_data.kind = DeviceKindFromTouchType(touch);
1258 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1260 pointer_data.view_id =
self.viewIdentifier;
1263 pointer_data.pointer_identifier = 0;
1265 pointer_data.physical_x = windowCoordinates.x * scale;
1266 pointer_data.physical_y = windowCoordinates.y * scale;
1269 pointer_data.physical_delta_x = 0.0;
1270 pointer_data.physical_delta_y = 0.0;
1272 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1275 switch (pointer_data.change) {
1276 case flutter::PointerData::Change::kDown:
1277 [
self.ongoingTouches addObject:deviceKey];
1279 case flutter::PointerData::Change::kCancel:
1280 case flutter::PointerData::Change::kUp:
1281 [
self.ongoingTouches removeObject:deviceKey];
1283 case flutter::PointerData::Change::kHover:
1284 case flutter::PointerData::Change::kMove:
1287 case flutter::PointerData::Change::kAdd:
1288 case flutter::PointerData::Change::kRemove:
1291 case flutter::PointerData::Change::kPanZoomStart:
1292 case flutter::PointerData::Change::kPanZoomUpdate:
1293 case flutter::PointerData::Change::kPanZoomEnd:
1299 pointer_data.pressure = touch.force;
1300 pointer_data.pressure_max = touch.maximumPossibleForce;
1301 pointer_data.radius_major = touch.majorRadius;
1302 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1303 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1318 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1338 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1340 if (@available(iOS 13.4, *)) {
1341 if (event !=
nullptr) {
1342 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1343 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1345 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1346 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1351 packet->SetPointerData(pointer_index++, pointer_data);
1353 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1354 flutter::PointerData remove_pointer_data = pointer_data;
1355 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1356 packet->SetPointerData(pointer_index++, remove_pointer_data);
1360 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1363 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1364 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1367 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1368 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1371 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1372 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1375 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1376 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1379 - (void)forceTouchesCancelled:(NSSet*)touches {
1380 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1381 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1384 #pragma mark - Touch events rate correction
1386 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1387 if (_touchRateCorrectionVSyncClient != nil) {
1392 const double epsilon = 0.1;
1393 if (displayRefreshRate < 60.0 + epsilon) {
1401 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1404 _touchRateCorrectionVSyncClient =
1405 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1406 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1409 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1410 if (_touchRateCorrectionVSyncClient == nil) {
1418 BOOL isUserInteracting = NO;
1419 for (UITouch* touch in touches) {
1420 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1421 isUserInteracting = YES;
1426 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1427 [_touchRateCorrectionVSyncClient await];
1429 [_touchRateCorrectionVSyncClient pause];
1433 - (void)invalidateTouchRateCorrectionVSyncClient {
1434 [_touchRateCorrectionVSyncClient invalidate];
1435 _touchRateCorrectionVSyncClient = nil;
1438 #pragma mark - Handle view resizing
1440 - (void)updateViewportMetricsIfNeeded {
1441 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1444 if (
self.
engine.viewController ==
self) {
1445 [
self.engine updateViewportMetrics:_viewportMetrics];
1449 - (void)viewDidLayoutSubviews {
1450 CGRect viewBounds =
self.view.bounds;
1451 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1454 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1458 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1459 _viewportMetrics.device_pixel_ratio = scale;
1460 [
self setViewportMetricsSize];
1461 [
self setViewportMetricsPaddings];
1462 [
self updateViewportMetricsIfNeeded];
1469 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.
engine) {
1470 [
self surfaceUpdated:YES];
1471 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1472 NSTimeInterval timeout = 0.2;
1474 NSTimeInterval timeout = 0.1;
1477 waitForFirstFrameSync:timeout
1478 callback:^(BOOL didTimeout) {
1480 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1481 "This may happen in unoptimized builds. If this is"
1482 "a release build, you should load a less complex "
1483 "frame to avoid the timeout."];
1489 - (void)viewSafeAreaInsetsDidChange {
1490 [
self setViewportMetricsPaddings];
1491 [
self updateViewportMetricsIfNeeded];
1492 [
super viewSafeAreaInsetsDidChange];
1496 - (void)setViewportMetricsSize {
1497 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1502 CGFloat scale = screen.scale;
1503 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1504 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1506 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1507 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1508 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1509 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1515 - (void)setViewportMetricsPaddings {
1516 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1521 CGFloat scale = screen.scale;
1522 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1523 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1524 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1525 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1528 #pragma mark - Keyboard events
1530 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1535 [
self handleKeyboardNotification:notification];
1538 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1543 [
self handleKeyboardNotification:notification];
1546 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1550 [
self handleKeyboardNotification:notification];
1553 - (void)handleKeyboardNotification:(NSNotification*)notification {
1556 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1560 NSDictionary* info = notification.userInfo;
1561 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1562 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1563 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1564 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1565 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1572 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1573 [
self hideKeyboardImmediately];
1578 if (
self.targetViewInsetBottom == calculatedInset) {
1582 self.targetViewInsetBottom = calculatedInset;
1589 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1590 BOOL keyboardAnimationIsCompounding =
1591 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1594 self.keyboardAnimationIsShowing = keyboardWillShow;
1596 if (!keyboardAnimationIsCompounding) {
1597 [
self startKeyBoardAnimation:duration];
1598 }
else if (
self.keyboardSpringAnimation) {
1599 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1603 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1608 if (notification.name == UIKeyboardWillHideNotification) {
1617 NSDictionary* info = notification.userInfo;
1618 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1619 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1620 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1626 if (CGRectIsEmpty(keyboardFrame)) {
1631 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1637 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1638 NSDictionary* info = notification.userInfo;
1642 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1643 if (isLocal && ![isLocal boolValue]) {
1646 return self.engine.viewController !=
self;
1649 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1657 NSDictionary* info = notification.userInfo;
1658 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1660 if (notification.name == UIKeyboardWillHideNotification) {
1661 return FlutterKeyboardModeHidden;
1666 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1667 return FlutterKeyboardModeFloating;
1670 if (CGRectIsEmpty(keyboardFrame)) {
1671 return FlutterKeyboardModeHidden;
1674 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1675 CGRect adjustedKeyboardFrame = keyboardFrame;
1676 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1677 keyboardFrame:keyboardFrame];
1682 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1683 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1684 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1685 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1687 CGFloat screenHeight = CGRectGetHeight(screenRect);
1688 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1689 if (round(adjustedKeyboardBottom) < screenHeight) {
1690 return FlutterKeyboardModeFloating;
1692 return FlutterKeyboardModeDocked;
1694 return FlutterKeyboardModeHidden;
1697 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1701 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1702 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1703 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1704 CGFloat screenHeight = CGRectGetHeight(screenRect);
1705 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1709 if (screenHeight == keyboardBottom) {
1712 CGRect viewRectRelativeToScreen =
1713 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1714 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1715 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1716 CGFloat offset = screenHeight - viewBottom;
1724 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1726 if (keyboardMode == FlutterKeyboardModeDocked) {
1728 CGRect viewRectRelativeToScreen =
1729 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1730 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1731 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1732 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1737 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1738 return portionOfKeyboardInView * scale;
1743 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1745 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1751 if (!
self.keyboardAnimationView) {
1752 UIView* keyboardAnimationView = [[UIView alloc] init];
1753 keyboardAnimationView.hidden = YES;
1754 self.keyboardAnimationView = keyboardAnimationView;
1757 if (!
self.keyboardAnimationView.superview) {
1758 [
self.view addSubview:self.keyboardAnimationView];
1762 [
self.keyboardAnimationView.layer removeAllAnimations];
1765 self.keyboardAnimationView.frame =
1766 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1767 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1768 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1771 [
self invalidateKeyboardAnimationVSyncClient];
1774 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1775 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1777 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1779 [UIView animateWithDuration:duration
1787 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1790 CAAnimation* keyboardAnimation =
1791 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1792 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1794 completion:^(BOOL finished) {
1795 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1804 [strongSelf invalidateKeyboardAnimationVSyncClient];
1805 [strongSelf removeKeyboardAnimationView];
1806 [strongSelf ensureViewportMetricsIsCorrect];
1811 - (void)hideKeyboardImmediately {
1812 [
self invalidateKeyboardAnimationVSyncClient];
1813 if (
self.keyboardAnimationView) {
1814 [
self.keyboardAnimationView.layer removeAllAnimations];
1815 [
self removeKeyboardAnimationView];
1816 self.keyboardAnimationView = nil;
1818 if (
self.keyboardSpringAnimation) {
1819 self.keyboardSpringAnimation = nil;
1822 self.targetViewInsetBottom = 0.0;
1823 [
self ensureViewportMetricsIsCorrect];
1826 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1828 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1829 _keyboardSpringAnimation = nil;
1834 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1835 _keyboardSpringAnimation =
1836 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1837 damping:keyboardCASpringAnimation.damping
1838 mass:keyboardCASpringAnimation.mass
1839 initialVelocity:keyboardCASpringAnimation.initialVelocity
1840 fromValue:self.originalViewInsetBottom
1841 toValue:self.targetViewInsetBottom];
1844 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1846 if (!
self.isViewLoaded) {
1851 if (!
self.keyboardAnimationView) {
1856 if (!
self.keyboardAnimationVSyncClient) {
1860 if (!
self.keyboardAnimationView.superview) {
1862 [
self.view addSubview:self.keyboardAnimationView];
1865 if (!
self.keyboardSpringAnimation) {
1866 if (
self.keyboardAnimationView.layer.presentationLayer) {
1867 self->_viewportMetrics.physical_view_inset_bottom =
1868 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1869 [
self updateViewportMetricsIfNeeded];
1872 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1873 self->_viewportMetrics.physical_view_inset_bottom =
1874 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1875 [
self updateViewportMetricsIfNeeded];
1879 - (void)setUpKeyboardAnimationVsyncClient:
1881 if (!keyboardAnimationCallback) {
1884 NSAssert(_keyboardAnimationVSyncClient == nil,
1885 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1889 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1890 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1891 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1892 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1893 animationCallback(targetTime);
1897 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1898 callback:uiCallback];
1899 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1900 [_keyboardAnimationVSyncClient await];
1903 - (void)invalidateKeyboardAnimationVSyncClient {
1904 [_keyboardAnimationVSyncClient invalidate];
1905 _keyboardAnimationVSyncClient = nil;
1908 - (void)removeKeyboardAnimationView {
1909 if (
self.keyboardAnimationView.superview != nil) {
1910 [
self.keyboardAnimationView removeFromSuperview];
1914 - (void)ensureViewportMetricsIsCorrect {
1915 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1917 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1918 [
self updateViewportMetricsIfNeeded];
1922 - (void)handlePressEvent:(FlutterUIPressProxy*)press
1923 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1924 if (@available(iOS 13.4, *)) {
1929 [
self.keyboardManager handlePress:press nextAction:next];
1945 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1946 [
super pressesBegan:presses withEvent:event];
1949 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1950 [
super pressesChanged:presses withEvent:event];
1953 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1954 [
super pressesEnded:presses withEvent:event];
1957 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1958 [
super pressesCancelled:presses withEvent:event];
1966 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1967 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1968 if (@available(iOS 13.4, *)) {
1970 for (UIPress* press in presses) {
1971 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1973 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1977 [
super pressesBegan:presses withEvent:event];
1981 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1982 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1983 if (@available(iOS 13.4, *)) {
1985 for (UIPress* press in presses) {
1986 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1988 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1992 [
super pressesChanged:presses withEvent:event];
1996 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1997 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1998 if (@available(iOS 13.4, *)) {
2000 for (UIPress* press in presses) {
2001 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2003 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2007 [
super pressesEnded:presses withEvent:event];
2011 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
2012 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2013 if (@available(iOS 13.4, *)) {
2015 for (UIPress* press in presses) {
2016 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2018 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2022 [
super pressesCancelled:presses withEvent:event];
2026 #pragma mark - Orientation updates
2028 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2031 dispatch_async(dispatch_get_main_queue(), ^{
2032 NSDictionary* info = notification.userInfo;
2033 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2034 if (update == nil) {
2037 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2041 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2042 API_AVAILABLE(ios(16.0)) {
2043 for (UIScene* windowScene in windowScenes) {
2044 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
2045 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2046 initWithInterfaceOrientations:self.orientationPreferences];
2047 [(UIWindowScene*)windowScene
2048 requestGeometryUpdateWithPreferences:preference
2049 errorHandler:^(NSError* error) {
2050 os_log_error(OS_LOG_DEFAULT,
2051 "Failed to change device orientation: %@", error);
2053 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2057 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2058 if (new_preferences !=
self.orientationPreferences) {
2059 self.orientationPreferences = new_preferences;
2061 if (@available(iOS 16.0, *)) {
2063 NSSet<UIScene*>* scenes = [NSSet set];
2064 if (flutterApplication) {
2065 scenes = [flutterApplication.connectedScenes
2066 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2067 id scene, NSDictionary* bindings) {
2068 return [scene isKindOfClass:[UIWindowScene class]];
2070 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2071 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2073 [
self requestGeometryUpdateForWindowScenes:scenes];
2075 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2076 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2080 @"Accessing the interface orientation when the window scene is unavailable."];
2083 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2084 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2085 [UIViewController attemptRotationToDeviceOrientation];
2087 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2091 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2092 forKey:@"orientation"];
2093 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2094 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2095 forKey:@"orientation"];
2096 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2097 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2098 forKey:@"orientation"];
2099 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2100 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2101 forKey:@"orientation"];
2108 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2109 self.isHomeIndicatorHidden = YES;
2112 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2113 self.isHomeIndicatorHidden = NO;
2116 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2117 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2118 _isHomeIndicatorHidden = hideHomeIndicator;
2119 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2123 - (BOOL)prefersHomeIndicatorAutoHidden {
2124 return self.isHomeIndicatorHidden;
2127 - (BOOL)shouldAutorotate {
2131 - (NSUInteger)supportedInterfaceOrientations {
2132 return self.orientationPreferences;
2135 #pragma mark - Accessibility
2137 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2142 int32_t flags =
self.accessibilityFlags;
2143 #if TARGET_OS_SIMULATOR
2149 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2150 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2152 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2154 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2156 [
self.engine enableSemantics:enabled withFlags:flags];
2159 - (int32_t)accessibilityFlags {
2161 if (UIAccessibilityIsInvertColorsEnabled()) {
2162 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2164 if (UIAccessibilityIsReduceMotionEnabled()) {
2165 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2167 if (UIAccessibilityIsBoldTextEnabled()) {
2168 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2170 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2171 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2174 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2180 - (BOOL)accessibilityPerformEscape {
2182 if (navigationChannel) {
2189 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2190 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2193 #pragma mark - Set user settings
2195 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2196 [
super traitCollectionDidChange:previousTraitCollection];
2197 [
self onUserSettingsChanged:nil];
2200 - (void)onUserSettingsChanged:(NSNotification*)notification {
2201 [
self.engine.settingsChannel sendMessage:@{
2202 @"textScaleFactor" : @(
self.textScaleFactor),
2204 @"platformBrightness" :
self.brightnessMode,
2205 @"platformContrast" : self.contrastMode,
2206 @"nativeSpellCheckServiceDefined" : @YES,
2207 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2211 - (CGFloat)textScaleFactor {
2213 if (flutterApplication == nil) {
2214 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2218 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2224 const CGFloat xs = 14;
2225 const CGFloat s = 15;
2226 const CGFloat m = 16;
2227 const CGFloat l = 17;
2228 const CGFloat xl = 19;
2229 const CGFloat xxl = 21;
2230 const CGFloat xxxl = 23;
2233 const CGFloat ax1 = 28;
2234 const CGFloat ax2 = 33;
2235 const CGFloat ax3 = 40;
2236 const CGFloat ax4 = 47;
2237 const CGFloat ax5 = 53;
2241 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2243 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2245 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2247 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2249 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2251 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2253 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2255 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2257 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2259 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2261 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2263 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2270 - (BOOL)supportsShowingSystemContextMenu {
2271 if (@available(iOS 16.0, *)) {
2281 - (NSString*)brightnessMode {
2282 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2284 if (style == UIUserInterfaceStyleDark) {
2294 - (NSString*)contrastMode {
2295 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2297 if (contrast == UIAccessibilityContrastHigh) {
2304 #pragma mark - Status bar style
2306 - (UIStatusBarStyle)preferredStatusBarStyle {
2307 return self.statusBarStyle;
2310 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2313 dispatch_async(dispatch_get_main_queue(), ^{
2319 NSDictionary* info = notification.userInfo;
2320 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2321 if (update == nil) {
2325 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2326 if (style != strongSelf.statusBarStyle) {
2327 strongSelf.statusBarStyle = style;
2328 [strongSelf setNeedsStatusBarAppearanceUpdate];
2333 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2334 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2335 self.flutterPrefersStatusBarHidden = hidden;
2336 [
self setNeedsStatusBarAppearanceUpdate];
2340 - (BOOL)prefersStatusBarHidden {
2341 return self.flutterPrefersStatusBarHidden;
2344 #pragma mark - Platform views
2347 return self.engine.platformViewsController;
2351 return self.engine.binaryMessenger;
2354 #pragma mark - FlutterBinaryMessenger
2356 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2357 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2360 - (void)sendOnChannel:(NSString*)channel
2361 message:(NSData*)message
2363 NSAssert(channel,
@"The channel must not be null");
2364 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2368 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2372 binaryMessageHandler:
2374 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2378 setMessageHandlerOnChannel:(NSString*)channel
2381 NSAssert(channel,
@"The channel must not be null");
2382 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2383 binaryMessageHandler:handler
2384 taskQueue:taskQueue];
2388 [
self.engine.binaryMessenger cleanUpConnection:connection];
2391 #pragma mark - FlutterTextureRegistry
2394 return [
self.engine.textureRegistry registerTexture:texture];
2397 - (void)unregisterTexture:(int64_t)textureId {
2398 [
self.engine.textureRegistry unregisterTexture:textureId];
2401 - (void)textureFrameAvailable:(int64_t)textureId {
2402 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2405 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2409 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2413 - (id<FlutterPluginRegistry>)pluginRegistry {
2417 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2418 return UIAccessibilityIsVoiceOverRunning();
2421 #pragma mark - FlutterPluginRegistry
2424 return [
self.engine registrarForPlugin:pluginKey];
2427 - (BOOL)hasPlugin:(NSString*)pluginKey {
2428 return [
self.engine hasPlugin:pluginKey];
2431 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2432 return [
self.engine valuePublishedByPlugin:pluginKey];
2435 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2437 completion:(
void (^)(
void))completion {
2438 self.isPresentingViewControllerAnimating = YES;
2440 [
super presentViewController:viewControllerToPresent
2443 weakSelf.isPresentingViewControllerAnimating = NO;
2450 - (BOOL)isPresentingViewController {
2451 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2454 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2455 API_AVAILABLE(ios(13.4)) {
2456 CGPoint location = [gestureRecognizer locationInView:self.view];
2457 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2459 flutter::PointerData pointer_data;
2460 pointer_data.Clear();
2464 return pointer_data;
2467 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2468 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2469 API_AVAILABLE(ios(13.4)) {
2473 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2474 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2475 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2476 event.type == UIEventTypeScroll) {
2478 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2479 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2480 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2481 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2482 pointer_data.view_id =
self.viewIdentifier;
2484 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2487 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2488 packet->SetPointerData(0, pointer_data);
2489 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2490 self.scrollInertiaEventAppKitDeadline = 0;
2497 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2500 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2501 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2502 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2503 pointer_data.view_id =
self.viewIdentifier;
2505 switch (_hoverGestureRecognizer.state) {
2506 case UIGestureRecognizerStateBegan:
2507 pointer_data.change = flutter::PointerData::Change::kAdd;
2509 case UIGestureRecognizerStateChanged:
2510 pointer_data.change = flutter::PointerData::Change::kHover;
2512 case UIGestureRecognizerStateEnded:
2513 case UIGestureRecognizerStateCancelled:
2514 pointer_data.change = flutter::PointerData::Change::kRemove;
2519 pointer_data.change = flutter::PointerData::Change::kHover;
2523 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2524 BOOL isRunningOnMac = NO;
2525 if (@available(iOS 14.0, *)) {
2529 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2532 time >
self.scrollInertiaEventStartline) {
2536 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2537 packet->SetPointerData(0, pointer_data);
2538 flutter::PointerData inertia_cancel = pointer_data;
2539 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2540 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2541 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2542 inertia_cancel.view_id =
self.viewIdentifier;
2543 packet->SetPointerData(1, inertia_cancel);
2544 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2545 self.scrollInertiaEventStartline = DBL_MAX;
2547 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2548 packet->SetPointerData(0, pointer_data);
2549 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2553 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2554 CGPoint translation = [recognizer translationInView:self.view];
2555 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2557 translation.x *= scale;
2558 translation.y *= scale;
2560 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2561 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2562 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2563 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2564 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2565 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2566 pointer_data.view_id =
self.viewIdentifier;
2572 if (recognizer.state != UIGestureRecognizerStateEnded) {
2578 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2579 packet->SetPointerData(0, pointer_data);
2580 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2583 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2584 CGPoint translation = [recognizer translationInView:self.view];
2585 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2587 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2588 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2589 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2590 pointer_data.view_id =
self.viewIdentifier;
2591 switch (recognizer.state) {
2592 case UIGestureRecognizerStateBegan:
2593 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2595 case UIGestureRecognizerStateChanged:
2596 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2597 pointer_data.pan_x = translation.x * scale;
2598 pointer_data.pan_y = translation.y * scale;
2599 pointer_data.pan_delta_x = 0;
2600 pointer_data.pan_delta_y = 0;
2601 pointer_data.scale = 1;
2603 case UIGestureRecognizerStateEnded:
2604 case UIGestureRecognizerStateCancelled:
2605 self.scrollInertiaEventStartline =
2606 [[NSProcessInfo processInfo] systemUptime] +
2616 self.scrollInertiaEventAppKitDeadline =
2617 [[NSProcessInfo processInfo] systemUptime] +
2618 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2619 [recognizer velocityInView:self.view].y))) -
2621 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2625 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2626 (
long)recognizer.state);
2630 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2631 packet->SetPointerData(0, pointer_data);
2632 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2635 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2636 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2637 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2638 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2639 pointer_data.view_id =
self.viewIdentifier;
2640 switch (recognizer.state) {
2641 case UIGestureRecognizerStateBegan:
2642 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2644 case UIGestureRecognizerStateChanged:
2645 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2646 pointer_data.scale = recognizer.scale;
2647 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2649 case UIGestureRecognizerStateEnded:
2650 case UIGestureRecognizerStateCancelled:
2651 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2655 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2656 (
long)recognizer.state);
2660 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2661 packet->SetPointerData(0, pointer_data);
2662 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2665 #pragma mark - State Restoration
2667 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2668 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2669 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2670 length:restorationData.length
2671 forKey:kFlutterRestorationStateAppData];
2672 [
super encodeRestorableStateWithCoder:coder];
2675 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2676 NSUInteger restorationDataLength;
2677 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2678 returnedLength:&restorationDataLength];
2679 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2680 [
self.engine.restorationPlugin setRestorationData:restorationData];
2684 return self.engine.restorationPlugin;
2688 return self.engine.textInputPlugin;
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
instancetype initWithCoder
FlutterTextInputPlugin * textInputPlugin
NSNotificationName const FlutterViewControllerHideHomeIndicator
static NSString *const kFlutterRestorationStateAppData
NSNotificationName const FlutterViewControllerShowHomeIndicator
NSNotificationName const FlutterSemanticsUpdateNotification
struct MouseState MouseState
static constexpr CGFloat kScrollViewContentSize
NSNotificationName const FlutterViewControllerWillDealloc
static constexpr FLUTTER_ASSERT_ARC int kMicrosecondsPerSecond
void(^ FlutterKeyboardAnimationCallback)(fml::TimePoint)
UIPanGestureRecognizer *continuousScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPanGestureRecognizer *discreteScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPinchGestureRecognizer *pinchGestureRecognizer API_AVAILABLE(ios(13.4))
UIHoverGestureRecognizer *hoverGestureRecognizer API_AVAILABLE(ios(13.4))
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
double displayRefreshRate
The display refresh rate used for reporting purposes. The engine does not care about this for frame s...
FlutterViewController * viewController
UIApplication * application
void setUpIndirectScribbleInteraction:(id< FlutterViewResponder > viewResponder)
UIView * splashScreenView