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"
33 #import "flutter/shell/platform/embedder/embedder.h"
34 #import "flutter/third_party/spring_animation/spring_animation.h"
46 @"FlutterViewControllerHideHomeIndicator";
48 @"FlutterViewControllerShowHomeIndicator";
66 @property(nonatomic, readonly) int64_t viewIdentifier;
71 @property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
73 @property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
74 @property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
75 @property(nonatomic, assign) BOOL initialized;
76 @property(nonatomic, assign) BOOL engineNeedsLaunch;
79 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
80 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
83 @property(nonatomic, assign) BOOL flutterPrefersStatusBarHidden;
85 @property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
90 @property(nonatomic, strong) UIScrollView* scrollView;
91 @property(nonatomic, strong) UIView* keyboardAnimationView;
92 @property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation;
97 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
102 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
103 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
104 @property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
105 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
106 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
107 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
110 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
118 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
126 @property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
132 @property(nonatomic, strong)
133 UIHoverGestureRecognizer* hoverGestureRecognizer
API_AVAILABLE(ios(13.4));
135 @property(nonatomic, strong)
136 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
138 @property(nonatomic, strong)
139 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
141 @property(nonatomic, strong)
142 UIPinchGestureRecognizer* pinchGestureRecognizer
API_AVAILABLE(ios(13.4));
144 @property(nonatomic, strong)
145 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
148 - (void)addInternalPlugins;
149 - (void)deregisterNotifications;
152 - (void)onFirstFrameRendered;
155 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime;
159 flutter::ViewportMetrics _viewportMetrics;
164 @synthesize viewOpaque = _viewOpaque;
165 @synthesize displayingFlutterUI = _displayingFlutterUI;
170 @dynamic viewIdentifier;
172 #pragma mark - Manage and override all designated initializers
175 nibName:(nullable NSString*)nibName
176 bundle:(nullable NSBundle*)nibBundle {
177 NSAssert(
engine != nil,
@"Engine is required");
178 self = [
super initWithNibName:nibName bundle:nibBundle];
181 if (
engine.viewController) {
182 FML_LOG(ERROR) <<
"The supplied FlutterEngine " << [[engine description] UTF8String]
183 <<
" is already used with FlutterViewController instance "
184 << [[engine.viewController description] UTF8String]
185 <<
". One instance of the FlutterEngine can only be attached to one "
186 "FlutterViewController at a time. Set FlutterEngine.viewController "
187 "to nil before attaching it to another FlutterViewController.";
190 _engineNeedsLaunch = NO;
191 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
192 opaque:self.isViewOpaque
193 enableWideGamut:engine.project.isWideGamutEnabled];
194 _ongoingTouches = [[NSMutableSet alloc] init];
198 [
self performCommonViewControllerInitialization];
199 [engine setViewController:self];
206 nibName:(NSString*)nibName
207 bundle:(NSBundle*)nibBundle {
208 self = [
super initWithNibName:nibName bundle:nibBundle];
212 [
self sharedSetupWithProject:project initialRoute:nil];
219 initialRoute:(NSString*)initialRoute
220 nibName:(NSString*)nibName
221 bundle:(NSBundle*)nibBundle {
222 self = [
super initWithNibName:nibName bundle:nibBundle];
226 [
self sharedSetupWithProject:project initialRoute:initialRoute];
232 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
233 return [
self initWithProject:nil nibName:nil bundle:nil];
237 self = [
super initWithCoder:aDecoder];
241 - (void)awakeFromNib {
242 [
super awakeFromNib];
244 [
self sharedSetupWithProject:nil initialRoute:nil];
248 - (instancetype)init {
249 return [
self initWithProject:nil nibName:nil bundle:nil];
253 initialRoute:(nullable NSString*)initialRoute {
262 allowHeadlessExecution:self.engineAllowHeadlessExecution
263 restorationEnabled:self.restorationIdentifier != nil];
270 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
272 enableWideGamut:project.isWideGamutEnabled];
273 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
274 _engineNeedsLaunch = YES;
275 _ongoingTouches = [[NSMutableSet alloc] init];
279 [
self loadDefaultSplashScreenView];
280 [
self performCommonViewControllerInitialization];
283 - (BOOL)isViewOpaque {
287 - (void)setViewOpaque:(BOOL)value {
289 if (
self.flutterView.layer.opaque != value) {
290 self.flutterView.layer.opaque = value;
291 [
self.flutterView.layer setNeedsLayout];
295 #pragma mark - Common view controller initialization tasks
297 - (void)performCommonViewControllerInitialization {
303 _orientationPreferences = UIInterfaceOrientationMaskAll;
304 _statusBarStyle = UIStatusBarStyleDefault;
308 [
self setUpNotificationCenterObservers];
311 - (void)setUpNotificationCenterObservers {
312 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
313 [center addObserver:self
314 selector:@selector(onOrientationPreferencesUpdated:)
315 name:@(flutter::kOrientationUpdateNotificationName)
318 [center addObserver:self
319 selector:@selector(onPreferredStatusBarStyleUpdated:)
320 name:@(flutter::kOverlayStyleUpdateNotificationName)
323 #if APPLICATION_EXTENSION_API_ONLY
324 if (@available(iOS 13.0, *)) {
325 [
self setUpSceneLifecycleNotifications:center];
327 [
self setUpApplicationLifecycleNotifications:center];
330 [
self setUpApplicationLifecycleNotifications:center];
333 [center addObserver:self
334 selector:@selector(keyboardWillChangeFrame:)
335 name:UIKeyboardWillChangeFrameNotification
338 [center addObserver:self
339 selector:@selector(keyboardWillShowNotification:)
340 name:UIKeyboardWillShowNotification
343 [center addObserver:self
344 selector:@selector(keyboardWillBeHidden:)
345 name:UIKeyboardWillHideNotification
348 [center addObserver:self
349 selector:@selector(onAccessibilityStatusChanged:)
350 name:UIAccessibilityVoiceOverStatusDidChangeNotification
353 [center addObserver:self
354 selector:@selector(onAccessibilityStatusChanged:)
355 name:UIAccessibilitySwitchControlStatusDidChangeNotification
358 [center addObserver:self
359 selector:@selector(onAccessibilityStatusChanged:)
360 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
363 [center addObserver:self
364 selector:@selector(onAccessibilityStatusChanged:)
365 name:UIAccessibilityInvertColorsStatusDidChangeNotification
368 [center addObserver:self
369 selector:@selector(onAccessibilityStatusChanged:)
370 name:UIAccessibilityReduceMotionStatusDidChangeNotification
373 [center addObserver:self
374 selector:@selector(onAccessibilityStatusChanged:)
375 name:UIAccessibilityBoldTextStatusDidChangeNotification
378 [center addObserver:self
379 selector:@selector(onAccessibilityStatusChanged:)
380 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
383 if (@available(iOS 13.0, *)) {
384 [center addObserver:self
385 selector:@selector(onAccessibilityStatusChanged:)
386 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
390 [center addObserver:self
391 selector:@selector(onUserSettingsChanged:)
392 name:UIContentSizeCategoryDidChangeNotification
395 [center addObserver:self
396 selector:@selector(onHideHomeIndicatorNotification:)
397 name:FlutterViewControllerHideHomeIndicator
400 [center addObserver:self
401 selector:@selector(onShowHomeIndicatorNotification:)
402 name:FlutterViewControllerShowHomeIndicator
406 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
407 [center addObserver:self
408 selector:@selector(sceneBecameActive:)
409 name:UISceneDidActivateNotification
412 [center addObserver:self
413 selector:@selector(sceneWillResignActive:)
414 name:UISceneWillDeactivateNotification
417 [center addObserver:self
418 selector:@selector(sceneWillDisconnect:)
419 name:UISceneDidDisconnectNotification
422 [center addObserver:self
423 selector:@selector(sceneDidEnterBackground:)
424 name:UISceneDidEnterBackgroundNotification
427 [center addObserver:self
428 selector:@selector(sceneWillEnterForeground:)
429 name:UISceneWillEnterForegroundNotification
433 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
434 [center addObserver:self
435 selector:@selector(applicationBecameActive:)
436 name:UIApplicationDidBecomeActiveNotification
439 [center addObserver:self
440 selector:@selector(applicationWillResignActive:)
441 name:UIApplicationWillResignActiveNotification
444 [center addObserver:self
445 selector:@selector(applicationWillTerminate:)
446 name:UIApplicationWillTerminateNotification
449 [center addObserver:self
450 selector:@selector(applicationDidEnterBackground:)
451 name:UIApplicationDidEnterBackgroundNotification
454 [center addObserver:self
455 selector:@selector(applicationWillEnterForeground:)
456 name:UIApplicationWillEnterForegroundNotification
460 - (void)setInitialRoute:(NSString*)route {
461 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
465 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
468 - (void)pushRoute:(NSString*)route {
469 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
472 #pragma mark - Loading the view
474 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
476 return existing_view;
479 auto placeholder = [[UIView alloc] init];
481 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
482 if (@available(iOS 13.0, *)) {
483 placeholder.backgroundColor = UIColor.systemBackgroundColor;
485 placeholder.backgroundColor = UIColor.whiteColor;
487 placeholder.autoresizesSubviews = YES;
492 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
493 auto messageLabel = [[UILabel alloc] init];
494 messageLabel.numberOfLines = 0u;
495 messageLabel.textAlignment = NSTextAlignmentCenter;
496 messageLabel.autoresizingMask =
497 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
499 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
500 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
501 @"modes to enable launching from the home screen.";
502 [placeholder addSubview:messageLabel];
509 self.view = GetViewOrPlaceholder(
self.flutterView);
510 self.view.multipleTouchEnabled = YES;
511 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
513 [
self installSplashScreenViewIfNecessary];
516 UIScrollView* scrollView = [[UIScrollView alloc] init];
517 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
519 scrollView.backgroundColor = UIColor.whiteColor;
520 scrollView.delegate =
self;
526 [
self.view addSubview:scrollView];
527 self.scrollView = scrollView;
530 - (
flutter::PointerData)generatePointerDataForFake {
531 flutter::PointerData pointer_data;
532 pointer_data.Clear();
533 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
541 static void SendFakeTouchEvent(UIScreen* screen,
544 flutter::PointerData::Change change) {
545 const CGFloat scale = screen.scale;
546 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
547 pointer_data.physical_x = location.x * scale;
548 pointer_data.physical_y = location.y * scale;
549 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
550 pointer_data.change = change;
551 packet->SetPointerData(0, pointer_data);
552 [engine dispatchPointerDataPacket:std::move(packet)];
555 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
559 CGPoint statusBarPoint = CGPointZero;
560 UIScreen* screen =
self.flutterScreenIfViewLoaded;
562 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
563 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
568 #pragma mark - Managing launch views
570 - (void)installSplashScreenViewIfNecessary {
573 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
574 [
self.splashScreenView removeFromSuperview];
575 self.splashScreenView = nil;
580 UIView* splashScreenView =
self.splashScreenView;
581 if (splashScreenView == nil) {
584 splashScreenView.frame =
self.view.bounds;
585 [
self.view addSubview:splashScreenView];
588 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
592 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
593 if (_displayingFlutterUI != displayingFlutterUI) {
594 if (displayingFlutterUI == YES) {
595 if (!
self.viewIfLoaded.window) {
599 [
self willChangeValueForKey:@"displayingFlutterUI"];
600 _displayingFlutterUI = displayingFlutterUI;
601 [
self didChangeValueForKey:@"displayingFlutterUI"];
605 - (void)callViewRenderedCallback {
606 self.displayingFlutterUI = YES;
607 if (
self.flutterViewRenderedCallback) {
608 self.flutterViewRenderedCallback();
609 self.flutterViewRenderedCallback = nil;
613 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
614 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
615 UIView* splashScreen =
self.splashScreenView;
617 _splashScreenView = nil;
618 [UIView animateWithDuration:0.2
620 splashScreen.alpha = 0;
622 completion:^(BOOL finished) {
623 [splashScreen removeFromSuperview];
630 - (void)onFirstFrameRendered {
631 if (
self.splashScreenView) {
633 [
self removeSplashScreenWithCompletion:^{
634 [weakSelf callViewRenderedCallback];
637 [
self callViewRenderedCallback];
641 - (void)installFirstFrameCallback {
646 [
self.engine installFirstFrameCallback:^{
647 [weakSelf onFirstFrameRendered];
651 #pragma mark - Properties
653 - (int64_t)viewIdentifier {
656 return flutter::kFlutterImplicitViewId;
659 - (BOOL)loadDefaultSplashScreenView {
660 NSString* launchscreenName =
661 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
662 if (launchscreenName == nil) {
665 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
667 splashView = [
self splashScreenFromXib:launchscreenName];
676 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
677 UIStoryboard* storyboard = nil;
679 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
680 }
@catch (NSException* exception) {
684 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
685 return splashScreenViewController.view;
690 - (UIView*)splashScreenFromXib:(NSString*)name {
691 NSArray* objects = nil;
693 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
694 }
@catch (NSException* exception) {
697 if ([objects count] != 0) {
698 UIView* view = [objects objectAtIndex:0];
704 - (void)setSplashScreenView:(UIView*)view {
705 if (view == _splashScreenView) {
711 if (_splashScreenView) {
712 [
self removeSplashScreenWithCompletion:nil];
717 _splashScreenView = view;
718 _splashScreenView.autoresizingMask =
719 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
722 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
723 _flutterViewRenderedCallback = callback;
726 #pragma mark - Surface creation and teardown updates
728 - (void)surfaceUpdated:(BOOL)appeared {
736 [
self installFirstFrameCallback];
737 self.platformViewsController.flutterView =
self.flutterView;
738 self.platformViewsController.flutterViewController =
self;
739 [
self.engine notifyViewCreated];
741 self.displayingFlutterUI = NO;
742 [
self.engine notifyViewDestroyed];
743 self.platformViewsController.flutterView = nil;
744 self.platformViewsController.flutterViewController = nil;
748 #pragma mark - UIViewController lifecycle notifications
750 - (void)viewDidLoad {
751 TRACE_EVENT0(
"flutter",
"viewDidLoad");
753 if (
self.
engine &&
self.engineNeedsLaunch) {
754 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
755 [
self.engine setViewController:self];
756 self.engineNeedsLaunch = NO;
757 }
else if (
self.
engine.viewController ==
self) {
758 [
self.engine attachView];
762 [
self addInternalPlugins];
765 [
self createTouchRateCorrectionVSyncClientIfNeeded];
767 if (@available(iOS 13.4, *)) {
768 _hoverGestureRecognizer =
769 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
770 _hoverGestureRecognizer.delegate =
self;
771 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
773 _discreteScrollingPanGestureRecognizer =
774 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
775 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
780 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
781 _discreteScrollingPanGestureRecognizer.delegate =
self;
782 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
783 _continuousScrollingPanGestureRecognizer =
784 [[UIPanGestureRecognizer alloc] initWithTarget:self
785 action:@selector(continuousScrollEvent:)];
786 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
787 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
788 _continuousScrollingPanGestureRecognizer.delegate =
self;
789 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
790 _pinchGestureRecognizer =
791 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
792 _pinchGestureRecognizer.allowedTouchTypes = @[];
793 _pinchGestureRecognizer.delegate =
self;
794 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
795 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
796 _rotationGestureRecognizer.allowedTouchTypes = @[];
797 _rotationGestureRecognizer.delegate =
self;
798 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
804 - (void)addInternalPlugins {
808 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
809 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
811 [
self.keyboardManager
815 [
self.keyboardManager addPrimaryResponder:responder];
818 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
820 if (
self.
engine.viewController ==
self) {
825 - (void)removeInternalPlugins {
826 self.keyboardManager = nil;
829 - (void)viewWillAppear:(BOOL)animated {
830 TRACE_EVENT0(
"flutter",
"viewWillAppear");
831 if (
self.
engine.viewController ==
self) {
833 [
self onUserSettingsChanged:nil];
837 if (_viewportMetrics.physical_width) {
838 [
self surfaceUpdated:YES];
840 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
841 [
self.engine.restorationPlugin markRestorationComplete];
844 [
super viewWillAppear:animated];
847 - (void)viewDidAppear:(BOOL)animated {
848 TRACE_EVENT0(
"flutter",
"viewDidAppear");
849 if (
self.
engine.viewController ==
self) {
850 [
self onUserSettingsChanged:nil];
851 [
self onAccessibilityStatusChanged:nil];
852 BOOL stateIsActive = YES;
853 #if APPLICATION_EXTENSION_API_ONLY
854 if (@available(iOS 13.0, *)) {
855 stateIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
856 UISceneActivationStateForegroundActive;
859 stateIsActive = UIApplication.sharedApplication.applicationState == UIApplicationStateActive;
862 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
865 [
super viewDidAppear:animated];
868 - (void)viewWillDisappear:(BOOL)animated {
869 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
870 if (
self.
engine.viewController ==
self) {
871 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
873 [
super viewWillDisappear:animated];
876 - (void)viewDidDisappear:(BOOL)animated {
877 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
878 if (
self.
engine.viewController ==
self) {
879 [
self invalidateKeyboardAnimationVSyncClient];
880 [
self ensureViewportMetricsIsCorrect];
881 [
self surfaceUpdated:NO];
882 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
883 [
self flushOngoingTouches];
884 [
self.engine notifyLowMemory];
887 [
super viewDidDisappear:animated];
890 - (void)viewWillTransitionToSize:(CGSize)size
891 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
892 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
902 NSTimeInterval transitionDuration = coordinator.transitionDuration;
904 if (transitionDuration == 0) {
909 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
910 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
911 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
912 dispatch_get_main_queue(), ^{
920 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
921 [strongSelf updateViewportMetricsIfNeeded];
925 - (void)flushOngoingTouches {
926 if (
self.
engine &&
self.ongoingTouches.count > 0) {
927 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
928 size_t pointer_index = 0;
931 for (NSNumber* device in
self.ongoingTouches) {
933 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
935 pointer_data.change = flutter::PointerData::Change::kCancel;
936 pointer_data.device = device.longLongValue;
937 pointer_data.pointer_identifier = 0;
938 pointer_data.view_id =
self.viewIdentifier;
941 pointer_data.physical_x = 0;
942 pointer_data.physical_y = 0;
943 pointer_data.physical_delta_x = 0.0;
944 pointer_data.physical_delta_y = 0.0;
945 pointer_data.pressure = 1.0;
946 pointer_data.pressure_max = 1.0;
948 packet->SetPointerData(pointer_index++, pointer_data);
951 [
self.ongoingTouches removeAllObjects];
952 [
self.engine dispatchPointerDataPacket:std::move(packet)];
956 - (void)deregisterNotifications {
957 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
960 [[NSNotificationCenter defaultCenter] removeObserver:self];
966 [
self removeInternalPlugins];
967 [
self deregisterNotifications];
969 [
self invalidateKeyboardAnimationVSyncClient];
970 [
self invalidateTouchRateCorrectionVSyncClient];
974 _scrollView.delegate = nil;
975 _hoverGestureRecognizer.delegate = nil;
976 _discreteScrollingPanGestureRecognizer.delegate = nil;
977 _continuousScrollingPanGestureRecognizer.delegate = nil;
978 _pinchGestureRecognizer.delegate = nil;
979 _rotationGestureRecognizer.delegate = nil;
982 #pragma mark - Application lifecycle notifications
984 - (void)applicationBecameActive:(NSNotification*)notification {
985 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
986 [
self appOrSceneBecameActive];
989 - (void)applicationWillResignActive:(NSNotification*)notification {
990 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
991 [
self appOrSceneWillResignActive];
994 - (void)applicationWillTerminate:(NSNotification*)notification {
995 [
self appOrSceneWillTerminate];
998 - (void)applicationDidEnterBackground:(NSNotification*)notification {
999 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1000 [
self appOrSceneDidEnterBackground];
1003 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1004 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1005 [
self appOrSceneWillEnterForeground];
1008 #pragma mark - Scene lifecycle notifications
1010 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1011 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1012 [
self appOrSceneBecameActive];
1015 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1016 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1017 [
self appOrSceneWillResignActive];
1020 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1021 [
self appOrSceneWillTerminate];
1024 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1025 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1026 [
self appOrSceneDidEnterBackground];
1029 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1030 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1031 [
self appOrSceneWillEnterForeground];
1034 #pragma mark - Lifecycle shared
1036 - (void)appOrSceneBecameActive {
1037 self.isKeyboardInOrTransitioningFromBackground = NO;
1038 if (_viewportMetrics.physical_width) {
1039 [
self surfaceUpdated:YES];
1041 [
self performSelector:@selector(goToApplicationLifecycle:)
1042 withObject:@"AppLifecycleState.resumed"
1046 - (void)appOrSceneWillResignActive {
1047 [NSObject cancelPreviousPerformRequestsWithTarget:self
1048 selector:@selector(goToApplicationLifecycle:)
1049 object:@"AppLifecycleState.resumed"];
1050 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1053 - (void)appOrSceneWillTerminate {
1054 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1055 [
self.engine destroyContext];
1058 - (void)appOrSceneDidEnterBackground {
1059 self.isKeyboardInOrTransitioningFromBackground = YES;
1060 [
self surfaceUpdated:NO];
1061 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1064 - (void)appOrSceneWillEnterForeground {
1065 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1069 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1072 if (
self.viewIfLoaded.window) {
1073 [
self.engine.lifecycleChannel sendMessage:state];
1077 #pragma mark - Touch event handling
1079 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1081 case UITouchPhaseBegan:
1082 return flutter::PointerData::Change::kDown;
1083 case UITouchPhaseMoved:
1084 case UITouchPhaseStationary:
1087 return flutter::PointerData::Change::kMove;
1088 case UITouchPhaseEnded:
1089 return flutter::PointerData::Change::kUp;
1090 case UITouchPhaseCancelled:
1091 return flutter::PointerData::Change::kCancel;
1094 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1098 return flutter::PointerData::Change::kCancel;
1101 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1102 switch (touch.type) {
1103 case UITouchTypeDirect:
1104 case UITouchTypeIndirect:
1105 return flutter::PointerData::DeviceKind::kTouch;
1106 case UITouchTypeStylus:
1107 return flutter::PointerData::DeviceKind::kStylus;
1108 case UITouchTypeIndirectPointer:
1109 return flutter::PointerData::DeviceKind::kMouse;
1111 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1115 return flutter::PointerData::DeviceKind::kTouch;
1122 - (void)dispatchTouches:(NSSet*)touches
1123 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1124 event:(UIEvent*)event {
1149 NSUInteger touches_to_remove_count = 0;
1150 for (UITouch* touch in touches) {
1151 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1152 touches_to_remove_count++;
1157 [
self triggerTouchRateCorrectionIfNeeded:touches];
1159 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1161 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1163 size_t pointer_index = 0;
1165 for (UITouch* touch in touches) {
1166 CGPoint windowCoordinates = [touch locationInView:self.view];
1168 flutter::PointerData pointer_data;
1169 pointer_data.Clear();
1174 pointer_data.change = overridden_change !=
nullptr
1175 ? *overridden_change
1176 : PointerDataChangeFromUITouchPhase(touch.phase);
1178 pointer_data.kind = DeviceKindFromTouchType(touch);
1180 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1182 pointer_data.view_id =
self.viewIdentifier;
1185 pointer_data.pointer_identifier = 0;
1187 pointer_data.physical_x = windowCoordinates.x * scale;
1188 pointer_data.physical_y = windowCoordinates.y * scale;
1191 pointer_data.physical_delta_x = 0.0;
1192 pointer_data.physical_delta_y = 0.0;
1194 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1197 switch (pointer_data.change) {
1198 case flutter::PointerData::Change::kDown:
1199 [
self.ongoingTouches addObject:deviceKey];
1201 case flutter::PointerData::Change::kCancel:
1202 case flutter::PointerData::Change::kUp:
1203 [
self.ongoingTouches removeObject:deviceKey];
1205 case flutter::PointerData::Change::kHover:
1206 case flutter::PointerData::Change::kMove:
1209 case flutter::PointerData::Change::kAdd:
1210 case flutter::PointerData::Change::kRemove:
1213 case flutter::PointerData::Change::kPanZoomStart:
1214 case flutter::PointerData::Change::kPanZoomUpdate:
1215 case flutter::PointerData::Change::kPanZoomEnd:
1221 pointer_data.pressure = touch.force;
1222 pointer_data.pressure_max = touch.maximumPossibleForce;
1223 pointer_data.radius_major = touch.majorRadius;
1224 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1225 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1240 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1260 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1262 if (@available(iOS 13.4, *)) {
1263 if (event !=
nullptr) {
1264 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1265 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1267 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1268 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1273 packet->SetPointerData(pointer_index++, pointer_data);
1275 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1276 flutter::PointerData remove_pointer_data = pointer_data;
1277 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1278 packet->SetPointerData(pointer_index++, remove_pointer_data);
1282 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1285 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1286 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1289 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1290 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1293 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1294 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1297 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1298 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1301 - (void)forceTouchesCancelled:(NSSet*)touches {
1302 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1303 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1306 #pragma mark - Touch events rate correction
1308 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1309 if (_touchRateCorrectionVSyncClient != nil) {
1314 const double epsilon = 0.1;
1315 if (displayRefreshRate < 60.0 + epsilon) {
1323 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1326 _touchRateCorrectionVSyncClient =
1327 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1328 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1331 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1332 if (_touchRateCorrectionVSyncClient == nil) {
1340 BOOL isUserInteracting = NO;
1341 for (UITouch* touch in touches) {
1342 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1343 isUserInteracting = YES;
1348 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1349 [_touchRateCorrectionVSyncClient await];
1351 [_touchRateCorrectionVSyncClient pause];
1355 - (void)invalidateTouchRateCorrectionVSyncClient {
1356 [_touchRateCorrectionVSyncClient invalidate];
1357 _touchRateCorrectionVSyncClient = nil;
1360 #pragma mark - Handle view resizing
1362 - (void)updateViewportMetricsIfNeeded {
1363 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1366 if (
self.
engine.viewController ==
self) {
1367 [
self.engine updateViewportMetrics:_viewportMetrics];
1371 - (void)viewDidLayoutSubviews {
1372 CGRect viewBounds =
self.view.bounds;
1373 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1376 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1380 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1381 _viewportMetrics.device_pixel_ratio = scale;
1382 [
self setViewportMetricsSize];
1383 [
self setViewportMetricsPaddings];
1384 [
self updateViewportMetricsIfNeeded];
1389 bool applicationOrSceneIsActive = YES;
1390 #if APPLICATION_EXTENSION_API_ONLY
1391 if (@available(iOS 13.0, *)) {
1392 applicationOrSceneIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
1393 UISceneActivationStateForegroundActive;
1396 applicationOrSceneIsActive =
1397 [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
1402 if (firstViewBoundsUpdate && applicationOrSceneIsActive &&
self.
engine) {
1403 [
self surfaceUpdated:YES];
1404 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1405 NSTimeInterval timeout = 0.2;
1407 NSTimeInterval timeout = 0.1;
1410 waitForFirstFrameSync:timeout
1411 callback:^(BOOL didTimeout) {
1414 << "Timeout waiting for the first frame to render. This may happen in "
1415 "unoptimized builds. If this is a release build, you should load a "
1416 "less complex frame to avoid the timeout.";
1422 - (void)viewSafeAreaInsetsDidChange {
1423 [
self setViewportMetricsPaddings];
1424 [
self updateViewportMetricsIfNeeded];
1425 [
super viewSafeAreaInsetsDidChange];
1429 - (void)setViewportMetricsSize {
1430 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1435 CGFloat scale = screen.scale;
1436 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1437 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1443 - (void)setViewportMetricsPaddings {
1444 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1449 CGFloat scale = screen.scale;
1450 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1451 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1452 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1453 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1456 #pragma mark - Keyboard events
1458 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1463 [
self handleKeyboardNotification:notification];
1466 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1471 [
self handleKeyboardNotification:notification];
1474 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1478 [
self handleKeyboardNotification:notification];
1481 - (void)handleKeyboardNotification:(NSNotification*)notification {
1484 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1488 NSDictionary* info = notification.userInfo;
1489 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1490 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1491 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1492 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1495 if (
self.targetViewInsetBottom == calculatedInset) {
1499 self.targetViewInsetBottom = calculatedInset;
1500 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1507 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1508 BOOL keyboardAnimationIsCompounding =
1509 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1512 self.keyboardAnimationIsShowing = keyboardWillShow;
1514 if (!keyboardAnimationIsCompounding) {
1515 [
self startKeyBoardAnimation:duration];
1516 }
else if (
self.keyboardSpringAnimation) {
1517 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1521 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1526 if (notification.name == UIKeyboardWillHideNotification) {
1535 NSDictionary* info = notification.userInfo;
1536 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1537 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1538 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1544 if (CGRectIsEmpty(keyboardFrame)) {
1549 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1553 if (@available(iOS 13.0, *)) {
1561 if (
self.isKeyboardInOrTransitioningFromBackground) {
1569 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1570 NSDictionary* info = notification.userInfo;
1574 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1575 if (isLocal && ![isLocal boolValue]) {
1578 return self.engine.viewController !=
self;
1581 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1589 NSDictionary* info = notification.userInfo;
1590 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1592 if (notification.name == UIKeyboardWillHideNotification) {
1593 return FlutterKeyboardModeHidden;
1598 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1599 return FlutterKeyboardModeFloating;
1602 if (CGRectIsEmpty(keyboardFrame)) {
1603 return FlutterKeyboardModeHidden;
1606 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1607 CGRect adjustedKeyboardFrame = keyboardFrame;
1608 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1609 keyboardFrame:keyboardFrame];
1614 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1615 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1616 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1617 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1619 CGFloat screenHeight = CGRectGetHeight(screenRect);
1620 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1621 if (round(adjustedKeyboardBottom) < screenHeight) {
1622 return FlutterKeyboardModeFloating;
1624 return FlutterKeyboardModeDocked;
1626 return FlutterKeyboardModeHidden;
1629 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1633 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1634 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1635 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1636 CGFloat screenHeight = CGRectGetHeight(screenRect);
1637 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1641 if (screenHeight == keyboardBottom) {
1644 CGRect viewRectRelativeToScreen =
1645 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1646 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1647 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1648 CGFloat offset = screenHeight - viewBottom;
1656 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1658 if (keyboardMode == FlutterKeyboardModeDocked) {
1660 CGRect viewRectRelativeToScreen =
1661 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1662 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1663 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1664 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1669 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1670 return portionOfKeyboardInView * scale;
1675 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1677 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1683 if (!
self.keyboardAnimationView) {
1684 UIView* keyboardAnimationView = [[UIView alloc] init];
1685 keyboardAnimationView.hidden = YES;
1686 self.keyboardAnimationView = keyboardAnimationView;
1689 if (!
self.keyboardAnimationView.superview) {
1690 [
self.view addSubview:self.keyboardAnimationView];
1694 [
self.keyboardAnimationView.layer removeAllAnimations];
1697 self.keyboardAnimationView.frame =
1698 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1699 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1700 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1703 [
self invalidateKeyboardAnimationVSyncClient];
1706 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1707 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1709 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1711 [UIView animateWithDuration:duration
1719 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1722 CAAnimation* keyboardAnimation =
1723 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1724 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1726 completion:^(BOOL finished) {
1727 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1736 [strongSelf invalidateKeyboardAnimationVSyncClient];
1737 [strongSelf removeKeyboardAnimationView];
1738 [strongSelf ensureViewportMetricsIsCorrect];
1743 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1745 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1746 _keyboardSpringAnimation = nil;
1751 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1752 _keyboardSpringAnimation =
1753 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1754 damping:keyboardCASpringAnimation.damping
1755 mass:keyboardCASpringAnimation.mass
1756 initialVelocity:keyboardCASpringAnimation.initialVelocity
1757 fromValue:self.originalViewInsetBottom
1758 toValue:self.targetViewInsetBottom];
1761 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1763 if (!
self.isViewLoaded) {
1768 if (!
self.keyboardAnimationView) {
1773 if (!
self.keyboardAnimationVSyncClient) {
1777 if (!
self.keyboardAnimationView.superview) {
1779 [
self.view addSubview:self.keyboardAnimationView];
1782 if (!
self.keyboardSpringAnimation) {
1783 if (
self.keyboardAnimationView.layer.presentationLayer) {
1784 self->_viewportMetrics.physical_view_inset_bottom =
1785 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1786 [
self updateViewportMetricsIfNeeded];
1789 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1790 self->_viewportMetrics.physical_view_inset_bottom =
1791 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1792 [
self updateViewportMetricsIfNeeded];
1796 - (void)setUpKeyboardAnimationVsyncClient:
1798 if (!keyboardAnimationCallback) {
1801 NSAssert(_keyboardAnimationVSyncClient == nil,
1802 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1806 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1807 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1808 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1809 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1810 animationCallback(targetTime);
1814 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1815 callback:uiCallback];
1816 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1817 [_keyboardAnimationVSyncClient await];
1820 - (void)invalidateKeyboardAnimationVSyncClient {
1821 [_keyboardAnimationVSyncClient invalidate];
1822 _keyboardAnimationVSyncClient = nil;
1825 - (void)removeKeyboardAnimationView {
1826 if (
self.keyboardAnimationView.superview != nil) {
1827 [
self.keyboardAnimationView removeFromSuperview];
1831 - (void)ensureViewportMetricsIsCorrect {
1832 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1834 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1835 [
self updateViewportMetricsIfNeeded];
1840 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1841 if (@available(iOS 13.4, *)) {
1846 [
self.keyboardManager handlePress:press nextAction:next];
1849 - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(
void (^)(BOOL success))completion {
1852 waitForFirstFrame:3.0
1853 callback:^(BOOL didTimeout) {
1855 FML_LOG(ERROR) << "Timeout waiting for the first frame when launching an URL.";
1859 [weakSelf.engine.navigationChannel
1860 invokeMethod:@"pushRouteInformation"
1862 @"location" : url.absoluteString ?: [NSNull null],
1864 result:^(id _Nullable result) {
1866 [result isKindOfClass:[NSNumber class]] && [result boolValue];
1869 FML_LOG(ERROR) << "Failed to handle route information in Flutter.";
1871 completion(success);
1890 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1891 [
super pressesBegan:presses withEvent:event];
1894 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1895 [
super pressesChanged:presses withEvent:event];
1898 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1899 [
super pressesEnded:presses withEvent:event];
1902 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1903 [
super pressesCancelled:presses withEvent:event];
1911 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1912 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1913 if (@available(iOS 13.4, *)) {
1915 for (UIPress* press in presses) {
1918 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1922 [
super pressesBegan:presses withEvent:event];
1926 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1927 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1928 if (@available(iOS 13.4, *)) {
1930 for (UIPress* press in presses) {
1933 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1937 [
super pressesChanged:presses withEvent:event];
1941 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1942 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1943 if (@available(iOS 13.4, *)) {
1945 for (UIPress* press in presses) {
1948 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1952 [
super pressesEnded:presses withEvent:event];
1956 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
1957 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1958 if (@available(iOS 13.4, *)) {
1960 for (UIPress* press in presses) {
1963 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1967 [
super pressesCancelled:presses withEvent:event];
1971 #pragma mark - Orientation updates
1973 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1976 dispatch_async(dispatch_get_main_queue(), ^{
1977 NSDictionary* info = notification.userInfo;
1978 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1979 if (update == nil) {
1982 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1986 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1987 API_AVAILABLE(ios(16.0)) {
1988 for (UIScene* windowScene in windowScenes) {
1989 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
1990 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1991 initWithInterfaceOrientations:self.orientationPreferences];
1992 [(UIWindowScene*)windowScene
1993 requestGeometryUpdateWithPreferences:preference
1994 errorHandler:^(NSError* error) {
1995 os_log_error(OS_LOG_DEFAULT,
1996 "Failed to change device orientation: %@", error);
1998 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2002 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2003 if (new_preferences !=
self.orientationPreferences) {
2004 self.orientationPreferences = new_preferences;
2006 if (@available(iOS 16.0, *)) {
2007 NSSet<UIScene*>* scenes =
2008 #if APPLICATION_EXTENSION_API_ONLY
2009 self.flutterWindowSceneIfViewLoaded
2010 ? [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded]
2013 [UIApplication.sharedApplication.connectedScenes
2014 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2015 id scene, NSDictionary* bindings) {
2016 return [scene isKindOfClass:[UIWindowScene class]];
2019 [
self requestGeometryUpdateForWindowScenes:scenes];
2021 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2022 if (@available(iOS 13.0, *)) {
2023 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2026 <<
"Accessing the interface orientation when the window scene is unavailable.";
2029 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2031 #if APPLICATION_EXTENSION_API_ONLY
2032 FML_LOG(ERROR) <<
"Application based status bar orentiation update is not supported in "
2033 "app extension. Orientation: "
2034 << currentInterfaceOrientation;
2036 currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation];
2039 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2040 [UIViewController attemptRotationToDeviceOrientation];
2042 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2046 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2047 forKey:@"orientation"];
2048 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2049 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2050 forKey:@"orientation"];
2051 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2052 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2053 forKey:@"orientation"];
2054 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2055 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2056 forKey:@"orientation"];
2063 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2064 self.isHomeIndicatorHidden = YES;
2067 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2068 self.isHomeIndicatorHidden = NO;
2071 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2072 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2073 _isHomeIndicatorHidden = hideHomeIndicator;
2074 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2078 - (BOOL)prefersHomeIndicatorAutoHidden {
2079 return self.isHomeIndicatorHidden;
2082 - (BOOL)shouldAutorotate {
2086 - (NSUInteger)supportedInterfaceOrientations {
2087 return self.orientationPreferences;
2090 #pragma mark - Accessibility
2092 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2097 int32_t flags =
self.accessibilityFlags;
2098 #if TARGET_OS_SIMULATOR
2104 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2105 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2107 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2109 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2111 [
self.engine enableSemantics:enabled withFlags:flags];
2114 - (int32_t)accessibilityFlags {
2116 if (UIAccessibilityIsInvertColorsEnabled()) {
2117 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2119 if (UIAccessibilityIsReduceMotionEnabled()) {
2120 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2122 if (UIAccessibilityIsBoldTextEnabled()) {
2123 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2125 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2126 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2129 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2135 - (BOOL)accessibilityPerformEscape {
2137 if (navigationChannel) {
2144 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2145 if (@available(iOS 13, *)) {
2146 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2152 #pragma mark - Set user settings
2154 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2155 [
super traitCollectionDidChange:previousTraitCollection];
2156 [
self onUserSettingsChanged:nil];
2159 - (void)onUserSettingsChanged:(NSNotification*)notification {
2160 [
self.engine.settingsChannel sendMessage:@{
2161 @"textScaleFactor" : @(
self.textScaleFactor),
2163 @"platformBrightness" :
self.brightnessMode,
2164 @"platformContrast" : self.contrastMode,
2165 @"nativeSpellCheckServiceDefined" : @YES,
2166 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2170 - (CGFloat)textScaleFactor {
2171 #if APPLICATION_EXTENSION_API_ONLY
2172 FML_LOG(WARNING) <<
"Dynamic content size update is not supported in app extension.";
2175 UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
2181 const CGFloat xs = 14;
2182 const CGFloat s = 15;
2183 const CGFloat m = 16;
2184 const CGFloat l = 17;
2185 const CGFloat xl = 19;
2186 const CGFloat xxl = 21;
2187 const CGFloat xxxl = 23;
2190 const CGFloat ax1 = 28;
2191 const CGFloat ax2 = 33;
2192 const CGFloat ax3 = 40;
2193 const CGFloat ax4 = 47;
2194 const CGFloat ax5 = 53;
2198 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2200 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2202 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2204 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2206 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2208 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2210 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2212 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2214 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2216 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2218 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2220 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2228 - (BOOL)supportsShowingSystemContextMenu {
2229 if (@available(iOS 16.0, *)) {
2239 - (NSString*)brightnessMode {
2240 if (@available(iOS 13, *)) {
2241 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2243 if (style == UIUserInterfaceStyleDark) {
2256 - (NSString*)contrastMode {
2257 if (@available(iOS 13, *)) {
2258 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2260 if (contrast == UIAccessibilityContrastHigh) {
2270 #pragma mark - Status bar style
2272 - (UIStatusBarStyle)preferredStatusBarStyle {
2273 return self.statusBarStyle;
2276 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2279 dispatch_async(dispatch_get_main_queue(), ^{
2285 NSDictionary* info = notification.userInfo;
2286 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2287 if (update == nil) {
2291 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2292 if (style != strongSelf.statusBarStyle) {
2293 strongSelf.statusBarStyle = style;
2294 [strongSelf setNeedsStatusBarAppearanceUpdate];
2299 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2300 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2301 self.flutterPrefersStatusBarHidden = hidden;
2302 [
self setNeedsStatusBarAppearanceUpdate];
2306 - (BOOL)prefersStatusBarHidden {
2307 return self.flutterPrefersStatusBarHidden;
2310 #pragma mark - Platform views
2313 return self.engine.platformViewsController;
2317 return self.engine.binaryMessenger;
2320 #pragma mark - FlutterBinaryMessenger
2322 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2323 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2326 - (void)sendOnChannel:(NSString*)channel
2327 message:(NSData*)message
2329 NSAssert(channel,
@"The channel must not be null");
2330 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2334 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2338 binaryMessageHandler:
2340 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2344 setMessageHandlerOnChannel:(NSString*)channel
2347 NSAssert(channel,
@"The channel must not be null");
2348 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2349 binaryMessageHandler:handler
2350 taskQueue:taskQueue];
2354 [
self.engine.binaryMessenger cleanUpConnection:connection];
2357 #pragma mark - FlutterTextureRegistry
2360 return [
self.engine.textureRegistry registerTexture:texture];
2363 - (void)unregisterTexture:(int64_t)textureId {
2364 [
self.engine.textureRegistry unregisterTexture:textureId];
2367 - (void)textureFrameAvailable:(int64_t)textureId {
2368 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2371 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2375 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2379 - (id<FlutterPluginRegistry>)pluginRegistry {
2383 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2384 return UIAccessibilityIsVoiceOverRunning();
2387 #pragma mark - FlutterPluginRegistry
2390 return [
self.engine registrarForPlugin:pluginKey];
2393 - (BOOL)hasPlugin:(NSString*)pluginKey {
2394 return [
self.engine hasPlugin:pluginKey];
2397 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2398 return [
self.engine valuePublishedByPlugin:pluginKey];
2401 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2403 completion:(
void (^)(
void))completion {
2404 self.isPresentingViewControllerAnimating = YES;
2406 [
super presentViewController:viewControllerToPresent
2409 weakSelf.isPresentingViewControllerAnimating = NO;
2416 - (BOOL)isPresentingViewController {
2417 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2420 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2421 API_AVAILABLE(ios(13.4)) {
2422 CGPoint location = [gestureRecognizer locationInView:self.view];
2423 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2425 flutter::PointerData pointer_data;
2426 pointer_data.Clear();
2430 return pointer_data;
2433 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2434 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2435 API_AVAILABLE(ios(13.4)) {
2439 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2440 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2441 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2442 event.type == UIEventTypeScroll) {
2444 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2445 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2446 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2447 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2448 pointer_data.view_id =
self.viewIdentifier;
2450 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2453 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2454 packet->SetPointerData(0, pointer_data);
2455 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2456 self.scrollInertiaEventAppKitDeadline = 0;
2463 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2466 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2467 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2468 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2469 pointer_data.view_id =
self.viewIdentifier;
2471 switch (_hoverGestureRecognizer.state) {
2472 case UIGestureRecognizerStateBegan:
2473 pointer_data.change = flutter::PointerData::Change::kAdd;
2475 case UIGestureRecognizerStateChanged:
2476 pointer_data.change = flutter::PointerData::Change::kHover;
2478 case UIGestureRecognizerStateEnded:
2479 case UIGestureRecognizerStateCancelled:
2480 pointer_data.change = flutter::PointerData::Change::kRemove;
2485 pointer_data.change = flutter::PointerData::Change::kHover;
2489 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2490 BOOL isRunningOnMac = NO;
2491 if (@available(iOS 14.0, *)) {
2495 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2498 time >
self.scrollInertiaEventStartline) {
2502 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2503 packet->SetPointerData(0, pointer_data);
2504 flutter::PointerData inertia_cancel = pointer_data;
2505 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2506 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2507 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2508 inertia_cancel.view_id =
self.viewIdentifier;
2509 packet->SetPointerData(1, inertia_cancel);
2510 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2511 self.scrollInertiaEventStartline = DBL_MAX;
2513 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2514 packet->SetPointerData(0, pointer_data);
2515 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2519 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2520 CGPoint translation = [recognizer translationInView:self.view];
2521 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2523 translation.x *= scale;
2524 translation.y *= scale;
2526 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2527 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2528 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2529 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2530 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2531 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2532 pointer_data.view_id =
self.viewIdentifier;
2538 if (recognizer.state != UIGestureRecognizerStateEnded) {
2544 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2545 packet->SetPointerData(0, pointer_data);
2546 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2549 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2550 CGPoint translation = [recognizer translationInView:self.view];
2551 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2553 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2554 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2555 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2556 pointer_data.view_id =
self.viewIdentifier;
2557 switch (recognizer.state) {
2558 case UIGestureRecognizerStateBegan:
2559 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2561 case UIGestureRecognizerStateChanged:
2562 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2563 pointer_data.pan_x = translation.x * scale;
2564 pointer_data.pan_y = translation.y * scale;
2565 pointer_data.pan_delta_x = 0;
2566 pointer_data.pan_delta_y = 0;
2567 pointer_data.scale = 1;
2569 case UIGestureRecognizerStateEnded:
2570 case UIGestureRecognizerStateCancelled:
2571 self.scrollInertiaEventStartline =
2572 [[NSProcessInfo processInfo] systemUptime] +
2582 self.scrollInertiaEventAppKitDeadline =
2583 [[NSProcessInfo processInfo] systemUptime] +
2584 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2585 [recognizer velocityInView:self.view].y))) -
2587 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2591 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2592 (
long)recognizer.state);
2596 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2597 packet->SetPointerData(0, pointer_data);
2598 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2601 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2602 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2603 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2604 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2605 pointer_data.view_id =
self.viewIdentifier;
2606 switch (recognizer.state) {
2607 case UIGestureRecognizerStateBegan:
2608 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2610 case UIGestureRecognizerStateChanged:
2611 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2612 pointer_data.scale = recognizer.scale;
2613 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2615 case UIGestureRecognizerStateEnded:
2616 case UIGestureRecognizerStateCancelled:
2617 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2621 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2622 (
long)recognizer.state);
2626 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2627 packet->SetPointerData(0, pointer_data);
2628 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2631 #pragma mark - State Restoration
2633 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2634 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2635 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2636 length:restorationData.length
2637 forKey:kFlutterRestorationStateAppData];
2638 [
super encodeRestorableStateWithCoder:coder];
2641 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2642 NSUInteger restorationDataLength;
2643 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2644 returnedLength:&restorationDataLength];
2645 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2646 [
self.engine.restorationPlugin setRestorationData:restorationData];
2650 return self.engine.restorationPlugin;