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 {
261 allowHeadlessExecution:self.engineAllowHeadlessExecution
262 restorationEnabled:self.restorationIdentifier != nil];
269 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
271 enableWideGamut:project.isWideGamutEnabled];
272 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
273 _engineNeedsLaunch = YES;
274 _ongoingTouches = [[NSMutableSet alloc] init];
278 [
self loadDefaultSplashScreenView];
279 [
self performCommonViewControllerInitialization];
282 - (BOOL)isViewOpaque {
286 - (void)setViewOpaque:(BOOL)value {
288 if (
self.flutterView.layer.opaque != value) {
289 self.flutterView.layer.opaque = value;
290 [
self.flutterView.layer setNeedsLayout];
294 #pragma mark - Common view controller initialization tasks
296 - (void)performCommonViewControllerInitialization {
302 _orientationPreferences = UIInterfaceOrientationMaskAll;
303 _statusBarStyle = UIStatusBarStyleDefault;
307 [
self setUpNotificationCenterObservers];
310 - (void)setUpNotificationCenterObservers {
311 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
312 [center addObserver:self
313 selector:@selector(onOrientationPreferencesUpdated:)
314 name:@(flutter::kOrientationUpdateNotificationName)
317 [center addObserver:self
318 selector:@selector(onPreferredStatusBarStyleUpdated:)
319 name:@(flutter::kOverlayStyleUpdateNotificationName)
322 #if APPLICATION_EXTENSION_API_ONLY
323 if (@available(iOS 13.0, *)) {
324 [
self setUpSceneLifecycleNotifications:center];
326 [
self setUpApplicationLifecycleNotifications:center];
329 [
self setUpApplicationLifecycleNotifications:center];
332 [center addObserver:self
333 selector:@selector(keyboardWillChangeFrame:)
334 name:UIKeyboardWillChangeFrameNotification
337 [center addObserver:self
338 selector:@selector(keyboardWillShowNotification:)
339 name:UIKeyboardWillShowNotification
342 [center addObserver:self
343 selector:@selector(keyboardWillBeHidden:)
344 name:UIKeyboardWillHideNotification
347 [center addObserver:self
348 selector:@selector(onAccessibilityStatusChanged:)
349 name:UIAccessibilityVoiceOverStatusDidChangeNotification
352 [center addObserver:self
353 selector:@selector(onAccessibilityStatusChanged:)
354 name:UIAccessibilitySwitchControlStatusDidChangeNotification
357 [center addObserver:self
358 selector:@selector(onAccessibilityStatusChanged:)
359 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
362 [center addObserver:self
363 selector:@selector(onAccessibilityStatusChanged:)
364 name:UIAccessibilityInvertColorsStatusDidChangeNotification
367 [center addObserver:self
368 selector:@selector(onAccessibilityStatusChanged:)
369 name:UIAccessibilityReduceMotionStatusDidChangeNotification
372 [center addObserver:self
373 selector:@selector(onAccessibilityStatusChanged:)
374 name:UIAccessibilityBoldTextStatusDidChangeNotification
377 [center addObserver:self
378 selector:@selector(onAccessibilityStatusChanged:)
379 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
382 if (@available(iOS 13.0, *)) {
383 [center addObserver:self
384 selector:@selector(onAccessibilityStatusChanged:)
385 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
389 [center addObserver:self
390 selector:@selector(onUserSettingsChanged:)
391 name:UIContentSizeCategoryDidChangeNotification
394 [center addObserver:self
395 selector:@selector(onHideHomeIndicatorNotification:)
396 name:FlutterViewControllerHideHomeIndicator
399 [center addObserver:self
400 selector:@selector(onShowHomeIndicatorNotification:)
401 name:FlutterViewControllerShowHomeIndicator
405 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
406 [center addObserver:self
407 selector:@selector(sceneBecameActive:)
408 name:UISceneDidActivateNotification
411 [center addObserver:self
412 selector:@selector(sceneWillResignActive:)
413 name:UISceneWillDeactivateNotification
416 [center addObserver:self
417 selector:@selector(sceneWillDisconnect:)
418 name:UISceneDidDisconnectNotification
421 [center addObserver:self
422 selector:@selector(sceneDidEnterBackground:)
423 name:UISceneDidEnterBackgroundNotification
426 [center addObserver:self
427 selector:@selector(sceneWillEnterForeground:)
428 name:UISceneWillEnterForegroundNotification
432 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
433 [center addObserver:self
434 selector:@selector(applicationBecameActive:)
435 name:UIApplicationDidBecomeActiveNotification
438 [center addObserver:self
439 selector:@selector(applicationWillResignActive:)
440 name:UIApplicationWillResignActiveNotification
443 [center addObserver:self
444 selector:@selector(applicationWillTerminate:)
445 name:UIApplicationWillTerminateNotification
448 [center addObserver:self
449 selector:@selector(applicationDidEnterBackground:)
450 name:UIApplicationDidEnterBackgroundNotification
453 [center addObserver:self
454 selector:@selector(applicationWillEnterForeground:)
455 name:UIApplicationWillEnterForegroundNotification
459 - (void)setInitialRoute:(NSString*)route {
460 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
464 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
467 - (void)pushRoute:(NSString*)route {
468 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
471 #pragma mark - Loading the view
473 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
475 return existing_view;
478 auto placeholder = [[UIView alloc] init];
480 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
481 if (@available(iOS 13.0, *)) {
482 placeholder.backgroundColor = UIColor.systemBackgroundColor;
484 placeholder.backgroundColor = UIColor.whiteColor;
486 placeholder.autoresizesSubviews = YES;
491 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
492 auto messageLabel = [[UILabel alloc] init];
493 messageLabel.numberOfLines = 0u;
494 messageLabel.textAlignment = NSTextAlignmentCenter;
495 messageLabel.autoresizingMask =
496 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
498 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
499 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
500 @"modes to enable launching from the home screen.";
501 [placeholder addSubview:messageLabel];
508 self.view = GetViewOrPlaceholder(
self.flutterView);
509 self.view.multipleTouchEnabled = YES;
510 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
512 [
self installSplashScreenViewIfNecessary];
515 UIScrollView* scrollView = [[UIScrollView alloc] init];
516 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
518 scrollView.backgroundColor = UIColor.whiteColor;
519 scrollView.delegate =
self;
525 [
self.view addSubview:scrollView];
526 self.scrollView = scrollView;
529 - (
flutter::PointerData)generatePointerDataForFake {
530 flutter::PointerData pointer_data;
531 pointer_data.Clear();
532 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
540 static void SendFakeTouchEvent(UIScreen* screen,
543 flutter::PointerData::Change change) {
544 const CGFloat scale = screen.scale;
545 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
546 pointer_data.physical_x = location.x * scale;
547 pointer_data.physical_y = location.y * scale;
548 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
549 pointer_data.change = change;
550 packet->SetPointerData(0, pointer_data);
551 [engine dispatchPointerDataPacket:std::move(packet)];
554 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
558 CGPoint statusBarPoint = CGPointZero;
559 UIScreen* screen =
self.flutterScreenIfViewLoaded;
561 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
562 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
567 #pragma mark - Managing launch views
569 - (void)installSplashScreenViewIfNecessary {
572 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
573 [
self.splashScreenView removeFromSuperview];
574 self.splashScreenView = nil;
579 UIView* splashScreenView =
self.splashScreenView;
580 if (splashScreenView == nil) {
583 splashScreenView.frame =
self.view.bounds;
584 [
self.view addSubview:splashScreenView];
587 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
591 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
592 if (_displayingFlutterUI != displayingFlutterUI) {
593 if (displayingFlutterUI == YES) {
594 if (!
self.viewIfLoaded.window) {
598 [
self willChangeValueForKey:@"displayingFlutterUI"];
599 _displayingFlutterUI = displayingFlutterUI;
600 [
self didChangeValueForKey:@"displayingFlutterUI"];
604 - (void)callViewRenderedCallback {
605 self.displayingFlutterUI = YES;
606 if (
self.flutterViewRenderedCallback) {
607 self.flutterViewRenderedCallback();
608 self.flutterViewRenderedCallback = nil;
612 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
613 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
614 UIView* splashScreen =
self.splashScreenView;
616 _splashScreenView = nil;
617 [UIView animateWithDuration:0.2
619 splashScreen.alpha = 0;
621 completion:^(BOOL finished) {
622 [splashScreen removeFromSuperview];
629 - (void)onFirstFrameRendered {
630 if (
self.splashScreenView) {
632 [
self removeSplashScreenWithCompletion:^{
633 [weakSelf callViewRenderedCallback];
636 [
self callViewRenderedCallback];
640 - (void)installFirstFrameCallback {
645 [
self.engine installFirstFrameCallback:^{
646 [weakSelf onFirstFrameRendered];
650 #pragma mark - Properties
652 - (int64_t)viewIdentifier {
655 return flutter::kFlutterImplicitViewId;
658 - (BOOL)loadDefaultSplashScreenView {
659 NSString* launchscreenName =
660 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
661 if (launchscreenName == nil) {
664 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
666 splashView = [
self splashScreenFromXib:launchscreenName];
675 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
676 UIStoryboard* storyboard = nil;
678 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
679 }
@catch (NSException* exception) {
683 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
684 return splashScreenViewController.view;
689 - (UIView*)splashScreenFromXib:(NSString*)name {
690 NSArray* objects = nil;
692 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
693 }
@catch (NSException* exception) {
696 if ([objects count] != 0) {
697 UIView* view = [objects objectAtIndex:0];
703 - (void)setSplashScreenView:(UIView*)view {
704 if (view == _splashScreenView) {
710 if (_splashScreenView) {
711 [
self removeSplashScreenWithCompletion:nil];
716 _splashScreenView = view;
717 _splashScreenView.autoresizingMask =
718 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
721 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
722 _flutterViewRenderedCallback = callback;
725 #pragma mark - Surface creation and teardown updates
727 - (void)surfaceUpdated:(BOOL)appeared {
735 [
self installFirstFrameCallback];
736 self.platformViewsController.flutterView =
self.flutterView;
737 self.platformViewsController.flutterViewController =
self;
738 [
self.engine notifyViewCreated];
740 self.displayingFlutterUI = NO;
741 [
self.engine notifyViewDestroyed];
742 self.platformViewsController.flutterView = nil;
743 self.platformViewsController.flutterViewController = nil;
747 #pragma mark - UIViewController lifecycle notifications
749 - (void)viewDidLoad {
750 TRACE_EVENT0(
"flutter",
"viewDidLoad");
752 if (
self.
engine &&
self.engineNeedsLaunch) {
753 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
754 [
self.engine setViewController:self];
755 self.engineNeedsLaunch = NO;
756 }
else if (
self.
engine.viewController ==
self) {
757 [
self.engine attachView];
761 [
self addInternalPlugins];
764 [
self createTouchRateCorrectionVSyncClientIfNeeded];
766 if (@available(iOS 13.4, *)) {
767 _hoverGestureRecognizer =
768 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
769 _hoverGestureRecognizer.delegate =
self;
770 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
772 _discreteScrollingPanGestureRecognizer =
773 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
774 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
779 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
780 _discreteScrollingPanGestureRecognizer.delegate =
self;
781 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
782 _continuousScrollingPanGestureRecognizer =
783 [[UIPanGestureRecognizer alloc] initWithTarget:self
784 action:@selector(continuousScrollEvent:)];
785 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
786 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
787 _continuousScrollingPanGestureRecognizer.delegate =
self;
788 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
789 _pinchGestureRecognizer =
790 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
791 _pinchGestureRecognizer.allowedTouchTypes = @[];
792 _pinchGestureRecognizer.delegate =
self;
793 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
794 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
795 _rotationGestureRecognizer.allowedTouchTypes = @[];
796 _rotationGestureRecognizer.delegate =
self;
797 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
803 - (void)addInternalPlugins {
807 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
808 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
810 [
self.keyboardManager
814 [
self.keyboardManager addPrimaryResponder:responder];
817 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
819 if (
self.
engine.viewController ==
self) {
824 - (void)removeInternalPlugins {
825 self.keyboardManager = nil;
828 - (void)viewWillAppear:(BOOL)animated {
829 TRACE_EVENT0(
"flutter",
"viewWillAppear");
830 if (
self.
engine.viewController ==
self) {
832 [
self onUserSettingsChanged:nil];
836 if (_viewportMetrics.physical_width) {
837 [
self surfaceUpdated:YES];
839 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
840 [
self.engine.restorationPlugin markRestorationComplete];
843 [
super viewWillAppear:animated];
846 - (void)viewDidAppear:(BOOL)animated {
847 TRACE_EVENT0(
"flutter",
"viewDidAppear");
848 if (
self.
engine.viewController ==
self) {
849 [
self onUserSettingsChanged:nil];
850 [
self onAccessibilityStatusChanged:nil];
851 BOOL stateIsActive = YES;
852 #if APPLICATION_EXTENSION_API_ONLY
853 if (@available(iOS 13.0, *)) {
854 stateIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
855 UISceneActivationStateForegroundActive;
858 stateIsActive = UIApplication.sharedApplication.applicationState == UIApplicationStateActive;
861 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
864 [
super viewDidAppear:animated];
867 - (void)viewWillDisappear:(BOOL)animated {
868 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
869 if (
self.
engine.viewController ==
self) {
870 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
872 [
super viewWillDisappear:animated];
875 - (void)viewDidDisappear:(BOOL)animated {
876 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
877 if (
self.
engine.viewController ==
self) {
878 [
self invalidateKeyboardAnimationVSyncClient];
879 [
self ensureViewportMetricsIsCorrect];
880 [
self surfaceUpdated:NO];
881 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
882 [
self flushOngoingTouches];
883 [
self.engine notifyLowMemory];
886 [
super viewDidDisappear:animated];
889 - (void)viewWillTransitionToSize:(CGSize)size
890 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
891 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
901 NSTimeInterval transitionDuration = coordinator.transitionDuration;
903 if (transitionDuration == 0) {
908 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
909 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
910 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
911 dispatch_get_main_queue(), ^{
919 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
920 [strongSelf updateViewportMetricsIfNeeded];
924 - (void)flushOngoingTouches {
925 if (
self.
engine &&
self.ongoingTouches.count > 0) {
926 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
927 size_t pointer_index = 0;
930 for (NSNumber* device in
self.ongoingTouches) {
932 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
934 pointer_data.change = flutter::PointerData::Change::kCancel;
935 pointer_data.device = device.longLongValue;
936 pointer_data.pointer_identifier = 0;
937 pointer_data.view_id =
self.viewIdentifier;
940 pointer_data.physical_x = 0;
941 pointer_data.physical_y = 0;
942 pointer_data.physical_delta_x = 0.0;
943 pointer_data.physical_delta_y = 0.0;
944 pointer_data.pressure = 1.0;
945 pointer_data.pressure_max = 1.0;
947 packet->SetPointerData(pointer_index++, pointer_data);
950 [
self.ongoingTouches removeAllObjects];
951 [
self.engine dispatchPointerDataPacket:std::move(packet)];
955 - (void)deregisterNotifications {
956 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
959 [[NSNotificationCenter defaultCenter] removeObserver:self];
965 [
self removeInternalPlugins];
966 [
self deregisterNotifications];
968 [
self invalidateKeyboardAnimationVSyncClient];
969 [
self invalidateTouchRateCorrectionVSyncClient];
973 _scrollView.delegate = nil;
974 _hoverGestureRecognizer.delegate = nil;
975 _discreteScrollingPanGestureRecognizer.delegate = nil;
976 _continuousScrollingPanGestureRecognizer.delegate = nil;
977 _pinchGestureRecognizer.delegate = nil;
978 _rotationGestureRecognizer.delegate = nil;
981 #pragma mark - Application lifecycle notifications
983 - (void)applicationBecameActive:(NSNotification*)notification {
984 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
985 [
self appOrSceneBecameActive];
988 - (void)applicationWillResignActive:(NSNotification*)notification {
989 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
990 [
self appOrSceneWillResignActive];
993 - (void)applicationWillTerminate:(NSNotification*)notification {
994 [
self appOrSceneWillTerminate];
997 - (void)applicationDidEnterBackground:(NSNotification*)notification {
998 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
999 [
self appOrSceneDidEnterBackground];
1002 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1003 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1004 [
self appOrSceneWillEnterForeground];
1007 #pragma mark - Scene lifecycle notifications
1009 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1010 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1011 [
self appOrSceneBecameActive];
1014 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1015 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1016 [
self appOrSceneWillResignActive];
1019 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1020 [
self appOrSceneWillTerminate];
1023 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1024 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1025 [
self appOrSceneDidEnterBackground];
1028 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1029 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1030 [
self appOrSceneWillEnterForeground];
1033 #pragma mark - Lifecycle shared
1035 - (void)appOrSceneBecameActive {
1036 self.isKeyboardInOrTransitioningFromBackground = NO;
1037 if (_viewportMetrics.physical_width) {
1038 [
self surfaceUpdated:YES];
1040 [
self performSelector:@selector(goToApplicationLifecycle:)
1041 withObject:@"AppLifecycleState.resumed"
1045 - (void)appOrSceneWillResignActive {
1046 [NSObject cancelPreviousPerformRequestsWithTarget:self
1047 selector:@selector(goToApplicationLifecycle:)
1048 object:@"AppLifecycleState.resumed"];
1049 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1052 - (void)appOrSceneWillTerminate {
1053 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1054 [
self.engine destroyContext];
1057 - (void)appOrSceneDidEnterBackground {
1058 self.isKeyboardInOrTransitioningFromBackground = YES;
1059 [
self surfaceUpdated:NO];
1060 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1063 - (void)appOrSceneWillEnterForeground {
1064 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1068 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1071 if (
self.viewIfLoaded.window) {
1072 [
self.engine.lifecycleChannel sendMessage:state];
1076 #pragma mark - Touch event handling
1078 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1080 case UITouchPhaseBegan:
1081 return flutter::PointerData::Change::kDown;
1082 case UITouchPhaseMoved:
1083 case UITouchPhaseStationary:
1086 return flutter::PointerData::Change::kMove;
1087 case UITouchPhaseEnded:
1088 return flutter::PointerData::Change::kUp;
1089 case UITouchPhaseCancelled:
1090 return flutter::PointerData::Change::kCancel;
1093 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1097 return flutter::PointerData::Change::kCancel;
1100 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1101 switch (touch.type) {
1102 case UITouchTypeDirect:
1103 case UITouchTypeIndirect:
1104 return flutter::PointerData::DeviceKind::kTouch;
1105 case UITouchTypeStylus:
1106 return flutter::PointerData::DeviceKind::kStylus;
1107 case UITouchTypeIndirectPointer:
1108 return flutter::PointerData::DeviceKind::kMouse;
1110 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1114 return flutter::PointerData::DeviceKind::kTouch;
1121 - (void)dispatchTouches:(NSSet*)touches
1122 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1123 event:(UIEvent*)event {
1148 NSUInteger touches_to_remove_count = 0;
1149 for (UITouch* touch in touches) {
1150 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1151 touches_to_remove_count++;
1156 [
self triggerTouchRateCorrectionIfNeeded:touches];
1158 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1160 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1162 size_t pointer_index = 0;
1164 for (UITouch* touch in touches) {
1165 CGPoint windowCoordinates = [touch locationInView:self.view];
1167 flutter::PointerData pointer_data;
1168 pointer_data.Clear();
1173 pointer_data.change = overridden_change !=
nullptr
1174 ? *overridden_change
1175 : PointerDataChangeFromUITouchPhase(touch.phase);
1177 pointer_data.kind = DeviceKindFromTouchType(touch);
1179 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1181 pointer_data.view_id =
self.viewIdentifier;
1184 pointer_data.pointer_identifier = 0;
1186 pointer_data.physical_x = windowCoordinates.x * scale;
1187 pointer_data.physical_y = windowCoordinates.y * scale;
1190 pointer_data.physical_delta_x = 0.0;
1191 pointer_data.physical_delta_y = 0.0;
1193 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1196 switch (pointer_data.change) {
1197 case flutter::PointerData::Change::kDown:
1198 [
self.ongoingTouches addObject:deviceKey];
1200 case flutter::PointerData::Change::kCancel:
1201 case flutter::PointerData::Change::kUp:
1202 [
self.ongoingTouches removeObject:deviceKey];
1204 case flutter::PointerData::Change::kHover:
1205 case flutter::PointerData::Change::kMove:
1208 case flutter::PointerData::Change::kAdd:
1209 case flutter::PointerData::Change::kRemove:
1212 case flutter::PointerData::Change::kPanZoomStart:
1213 case flutter::PointerData::Change::kPanZoomUpdate:
1214 case flutter::PointerData::Change::kPanZoomEnd:
1220 pointer_data.pressure = touch.force;
1221 pointer_data.pressure_max = touch.maximumPossibleForce;
1222 pointer_data.radius_major = touch.majorRadius;
1223 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1224 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1239 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1259 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1261 if (@available(iOS 13.4, *)) {
1262 if (event !=
nullptr) {
1263 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1264 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1266 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1267 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1272 packet->SetPointerData(pointer_index++, pointer_data);
1274 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1275 flutter::PointerData remove_pointer_data = pointer_data;
1276 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1277 packet->SetPointerData(pointer_index++, remove_pointer_data);
1281 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1284 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1285 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1288 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1289 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1292 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1293 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1296 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1297 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1300 - (void)forceTouchesCancelled:(NSSet*)touches {
1301 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1302 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1305 #pragma mark - Touch events rate correction
1307 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1308 if (_touchRateCorrectionVSyncClient != nil) {
1313 const double epsilon = 0.1;
1314 if (displayRefreshRate < 60.0 + epsilon) {
1322 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1325 _touchRateCorrectionVSyncClient =
1326 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1327 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1330 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1331 if (_touchRateCorrectionVSyncClient == nil) {
1339 BOOL isUserInteracting = NO;
1340 for (UITouch* touch in touches) {
1341 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1342 isUserInteracting = YES;
1347 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1348 [_touchRateCorrectionVSyncClient await];
1350 [_touchRateCorrectionVSyncClient pause];
1354 - (void)invalidateTouchRateCorrectionVSyncClient {
1355 [_touchRateCorrectionVSyncClient invalidate];
1356 _touchRateCorrectionVSyncClient = nil;
1359 #pragma mark - Handle view resizing
1361 - (void)updateViewportMetricsIfNeeded {
1362 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1365 if (
self.
engine.viewController ==
self) {
1366 [
self.engine updateViewportMetrics:_viewportMetrics];
1370 - (void)viewDidLayoutSubviews {
1371 CGRect viewBounds =
self.view.bounds;
1372 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1375 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1379 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1380 _viewportMetrics.device_pixel_ratio = scale;
1381 [
self setViewportMetricsSize];
1382 [
self setViewportMetricsPaddings];
1383 [
self updateViewportMetricsIfNeeded];
1388 bool applicationOrSceneIsActive = YES;
1389 #if APPLICATION_EXTENSION_API_ONLY
1390 if (@available(iOS 13.0, *)) {
1391 applicationOrSceneIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
1392 UISceneActivationStateForegroundActive;
1395 applicationOrSceneIsActive =
1396 [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
1401 if (firstViewBoundsUpdate && applicationOrSceneIsActive &&
self.
engine) {
1402 [
self surfaceUpdated:YES];
1403 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1404 NSTimeInterval timeout = 0.2;
1406 NSTimeInterval timeout = 0.1;
1409 waitForFirstFrameSync:timeout
1410 callback:^(BOOL didTimeout) {
1413 << "Timeout waiting for the first frame to render. This may happen in "
1414 "unoptimized builds. If this is a release build, you should load a "
1415 "less complex frame to avoid the timeout.";
1421 - (void)viewSafeAreaInsetsDidChange {
1422 [
self setViewportMetricsPaddings];
1423 [
self updateViewportMetricsIfNeeded];
1424 [
super viewSafeAreaInsetsDidChange];
1428 - (void)setViewportMetricsSize {
1429 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1434 CGFloat scale = screen.scale;
1435 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1436 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1442 - (void)setViewportMetricsPaddings {
1443 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1448 CGFloat scale = screen.scale;
1449 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1450 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1451 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1452 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1455 #pragma mark - Keyboard events
1457 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1462 [
self handleKeyboardNotification:notification];
1465 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1470 [
self handleKeyboardNotification:notification];
1473 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1477 [
self handleKeyboardNotification:notification];
1480 - (void)handleKeyboardNotification:(NSNotification*)notification {
1483 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1487 NSDictionary* info = notification.userInfo;
1488 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1489 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1490 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1491 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1494 if (
self.targetViewInsetBottom == calculatedInset) {
1498 self.targetViewInsetBottom = calculatedInset;
1499 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1506 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1507 BOOL keyboardAnimationIsCompounding =
1508 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1511 self.keyboardAnimationIsShowing = keyboardWillShow;
1513 if (!keyboardAnimationIsCompounding) {
1514 [
self startKeyBoardAnimation:duration];
1515 }
else if (
self.keyboardSpringAnimation) {
1516 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1520 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1525 if (notification.name == UIKeyboardWillHideNotification) {
1534 NSDictionary* info = notification.userInfo;
1535 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1536 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1537 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1543 if (CGRectIsEmpty(keyboardFrame)) {
1548 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1552 if (@available(iOS 13.0, *)) {
1560 if (
self.isKeyboardInOrTransitioningFromBackground) {
1568 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1569 NSDictionary* info = notification.userInfo;
1573 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1574 if (isLocal && ![isLocal boolValue]) {
1577 return self.engine.viewController !=
self;
1580 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1588 NSDictionary* info = notification.userInfo;
1589 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1591 if (notification.name == UIKeyboardWillHideNotification) {
1592 return FlutterKeyboardModeHidden;
1597 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1598 return FlutterKeyboardModeFloating;
1601 if (CGRectIsEmpty(keyboardFrame)) {
1602 return FlutterKeyboardModeHidden;
1605 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1606 CGRect adjustedKeyboardFrame = keyboardFrame;
1607 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1608 keyboardFrame:keyboardFrame];
1613 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1614 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1615 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1616 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1618 CGFloat screenHeight = CGRectGetHeight(screenRect);
1619 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1620 if (round(adjustedKeyboardBottom) < screenHeight) {
1621 return FlutterKeyboardModeFloating;
1623 return FlutterKeyboardModeDocked;
1625 return FlutterKeyboardModeHidden;
1628 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1632 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1633 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1634 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1635 CGFloat screenHeight = CGRectGetHeight(screenRect);
1636 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1640 if (screenHeight == keyboardBottom) {
1643 CGRect viewRectRelativeToScreen =
1644 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1645 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1646 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1647 CGFloat offset = screenHeight - viewBottom;
1655 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1657 if (keyboardMode == FlutterKeyboardModeDocked) {
1659 CGRect viewRectRelativeToScreen =
1660 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1661 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1662 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1663 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1668 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1669 return portionOfKeyboardInView * scale;
1674 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1676 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1682 if (!
self.keyboardAnimationView) {
1683 UIView* keyboardAnimationView = [[UIView alloc] init];
1684 keyboardAnimationView.hidden = YES;
1685 self.keyboardAnimationView = keyboardAnimationView;
1688 if (!
self.keyboardAnimationView.superview) {
1689 [
self.view addSubview:self.keyboardAnimationView];
1693 [
self.keyboardAnimationView.layer removeAllAnimations];
1696 self.keyboardAnimationView.frame =
1697 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1698 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1699 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1702 [
self invalidateKeyboardAnimationVSyncClient];
1705 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1706 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1708 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1710 [UIView animateWithDuration:duration
1718 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1721 CAAnimation* keyboardAnimation =
1722 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1723 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1725 completion:^(BOOL finished) {
1726 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1735 [strongSelf invalidateKeyboardAnimationVSyncClient];
1736 [strongSelf removeKeyboardAnimationView];
1737 [strongSelf ensureViewportMetricsIsCorrect];
1742 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1744 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1745 _keyboardSpringAnimation = nil;
1750 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1751 _keyboardSpringAnimation =
1752 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1753 damping:keyboardCASpringAnimation.damping
1754 mass:keyboardCASpringAnimation.mass
1755 initialVelocity:keyboardCASpringAnimation.initialVelocity
1756 fromValue:self.originalViewInsetBottom
1757 toValue:self.targetViewInsetBottom];
1760 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1762 if (!
self.isViewLoaded) {
1767 if (!
self.keyboardAnimationView) {
1772 if (!
self.keyboardAnimationVSyncClient) {
1776 if (!
self.keyboardAnimationView.superview) {
1778 [
self.view addSubview:self.keyboardAnimationView];
1781 if (!
self.keyboardSpringAnimation) {
1782 if (
self.keyboardAnimationView.layer.presentationLayer) {
1783 self->_viewportMetrics.physical_view_inset_bottom =
1784 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1785 [
self updateViewportMetricsIfNeeded];
1788 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1789 self->_viewportMetrics.physical_view_inset_bottom =
1790 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1791 [
self updateViewportMetricsIfNeeded];
1795 - (void)setUpKeyboardAnimationVsyncClient:
1797 if (!keyboardAnimationCallback) {
1800 NSAssert(_keyboardAnimationVSyncClient == nil,
1801 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1805 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1806 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1807 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1808 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1809 animationCallback(targetTime);
1813 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1814 callback:uiCallback];
1815 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1816 [_keyboardAnimationVSyncClient await];
1819 - (void)invalidateKeyboardAnimationVSyncClient {
1820 [_keyboardAnimationVSyncClient invalidate];
1821 _keyboardAnimationVSyncClient = nil;
1824 - (void)removeKeyboardAnimationView {
1825 if (
self.keyboardAnimationView.superview != nil) {
1826 [
self.keyboardAnimationView removeFromSuperview];
1830 - (void)ensureViewportMetricsIsCorrect {
1831 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1833 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1834 [
self updateViewportMetricsIfNeeded];
1839 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1840 if (@available(iOS 13.4, *)) {
1845 [
self.keyboardManager handlePress:press nextAction:next];
1848 - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(
void (^)(BOOL success))completion {
1851 waitForFirstFrame:3.0
1852 callback:^(BOOL didTimeout) {
1854 FML_LOG(ERROR) << "Timeout waiting for the first frame when launching an URL.";
1858 [weakSelf.engine.navigationChannel
1859 invokeMethod:@"pushRouteInformation"
1861 @"location" : url.absoluteString ?: [NSNull null],
1863 result:^(id _Nullable result) {
1865 [result isKindOfClass:[NSNumber class]] && [result boolValue];
1868 FML_LOG(ERROR) << "Failed to handle route information in Flutter.";
1870 completion(success);
1889 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1890 [
super pressesBegan:presses withEvent:event];
1893 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1894 [
super pressesChanged:presses withEvent:event];
1897 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1898 [
super pressesEnded:presses withEvent:event];
1901 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1902 [
super pressesCancelled:presses withEvent:event];
1910 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1911 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1912 if (@available(iOS 13.4, *)) {
1914 for (UIPress* press in presses) {
1917 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1921 [
super pressesBegan:presses withEvent:event];
1925 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1926 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1927 if (@available(iOS 13.4, *)) {
1929 for (UIPress* press in presses) {
1932 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1936 [
super pressesChanged:presses withEvent:event];
1940 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1941 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1942 if (@available(iOS 13.4, *)) {
1944 for (UIPress* press in presses) {
1947 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1951 [
super pressesEnded:presses withEvent:event];
1955 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
1956 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1957 if (@available(iOS 13.4, *)) {
1959 for (UIPress* press in presses) {
1962 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1966 [
super pressesCancelled:presses withEvent:event];
1970 #pragma mark - Orientation updates
1972 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1975 dispatch_async(dispatch_get_main_queue(), ^{
1976 NSDictionary* info = notification.userInfo;
1977 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1978 if (update == nil) {
1981 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1985 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1986 API_AVAILABLE(ios(16.0)) {
1987 for (UIScene* windowScene in windowScenes) {
1988 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
1989 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1990 initWithInterfaceOrientations:self.orientationPreferences];
1991 [(UIWindowScene*)windowScene
1992 requestGeometryUpdateWithPreferences:preference
1993 errorHandler:^(NSError* error) {
1994 os_log_error(OS_LOG_DEFAULT,
1995 "Failed to change device orientation: %@", error);
1997 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2001 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2002 if (new_preferences !=
self.orientationPreferences) {
2003 self.orientationPreferences = new_preferences;
2005 if (@available(iOS 16.0, *)) {
2006 NSSet<UIScene*>* scenes =
2007 #if APPLICATION_EXTENSION_API_ONLY
2008 self.flutterWindowSceneIfViewLoaded
2009 ? [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded]
2012 [UIApplication.sharedApplication.connectedScenes
2013 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2014 id scene, NSDictionary* bindings) {
2015 return [scene isKindOfClass:[UIWindowScene class]];
2018 [
self requestGeometryUpdateForWindowScenes:scenes];
2020 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2021 if (@available(iOS 13.0, *)) {
2022 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2025 <<
"Accessing the interface orientation when the window scene is unavailable.";
2028 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2030 #if APPLICATION_EXTENSION_API_ONLY
2031 FML_LOG(ERROR) <<
"Application based status bar orentiation update is not supported in "
2032 "app extension. Orientation: "
2033 << currentInterfaceOrientation;
2035 currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation];
2038 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2039 [UIViewController attemptRotationToDeviceOrientation];
2041 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2045 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2046 forKey:@"orientation"];
2047 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2048 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2049 forKey:@"orientation"];
2050 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2051 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2052 forKey:@"orientation"];
2053 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2054 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2055 forKey:@"orientation"];
2062 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2063 self.isHomeIndicatorHidden = YES;
2066 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2067 self.isHomeIndicatorHidden = NO;
2070 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2071 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2072 _isHomeIndicatorHidden = hideHomeIndicator;
2073 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2077 - (BOOL)prefersHomeIndicatorAutoHidden {
2078 return self.isHomeIndicatorHidden;
2081 - (BOOL)shouldAutorotate {
2085 - (NSUInteger)supportedInterfaceOrientations {
2086 return self.orientationPreferences;
2089 #pragma mark - Accessibility
2091 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2096 int32_t flags =
self.accessibilityFlags;
2097 #if TARGET_OS_SIMULATOR
2103 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2104 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2106 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2108 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2110 [
self.engine enableSemantics:enabled withFlags:flags];
2113 - (int32_t)accessibilityFlags {
2115 if (UIAccessibilityIsInvertColorsEnabled()) {
2116 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2118 if (UIAccessibilityIsReduceMotionEnabled()) {
2119 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2121 if (UIAccessibilityIsBoldTextEnabled()) {
2122 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2124 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2125 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2128 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2134 - (BOOL)accessibilityPerformEscape {
2136 if (navigationChannel) {
2143 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2144 if (@available(iOS 13, *)) {
2145 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2151 #pragma mark - Set user settings
2153 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2154 [
super traitCollectionDidChange:previousTraitCollection];
2155 [
self onUserSettingsChanged:nil];
2158 - (void)onUserSettingsChanged:(NSNotification*)notification {
2159 [
self.engine.settingsChannel sendMessage:@{
2160 @"textScaleFactor" : @(
self.textScaleFactor),
2162 @"platformBrightness" :
self.brightnessMode,
2163 @"platformContrast" : self.contrastMode,
2164 @"nativeSpellCheckServiceDefined" : @YES,
2165 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2169 - (CGFloat)textScaleFactor {
2170 #if APPLICATION_EXTENSION_API_ONLY
2171 FML_LOG(WARNING) <<
"Dynamic content size update is not supported in app extension.";
2174 UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
2180 const CGFloat xs = 14;
2181 const CGFloat s = 15;
2182 const CGFloat m = 16;
2183 const CGFloat l = 17;
2184 const CGFloat xl = 19;
2185 const CGFloat xxl = 21;
2186 const CGFloat xxxl = 23;
2189 const CGFloat ax1 = 28;
2190 const CGFloat ax2 = 33;
2191 const CGFloat ax3 = 40;
2192 const CGFloat ax4 = 47;
2193 const CGFloat ax5 = 53;
2197 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2199 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2201 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2203 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2205 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2207 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2209 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2211 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2213 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2215 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2217 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2219 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2227 - (BOOL)supportsShowingSystemContextMenu {
2228 if (@available(iOS 16.0, *)) {
2238 - (NSString*)brightnessMode {
2239 if (@available(iOS 13, *)) {
2240 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2242 if (style == UIUserInterfaceStyleDark) {
2255 - (NSString*)contrastMode {
2256 if (@available(iOS 13, *)) {
2257 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2259 if (contrast == UIAccessibilityContrastHigh) {
2269 #pragma mark - Status bar style
2271 - (UIStatusBarStyle)preferredStatusBarStyle {
2272 return self.statusBarStyle;
2275 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2278 dispatch_async(dispatch_get_main_queue(), ^{
2284 NSDictionary* info = notification.userInfo;
2285 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2286 if (update == nil) {
2290 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2291 if (style != strongSelf.statusBarStyle) {
2292 strongSelf.statusBarStyle = style;
2293 [strongSelf setNeedsStatusBarAppearanceUpdate];
2298 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2299 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2300 self.flutterPrefersStatusBarHidden = hidden;
2301 [
self setNeedsStatusBarAppearanceUpdate];
2305 - (BOOL)prefersStatusBarHidden {
2306 return self.flutterPrefersStatusBarHidden;
2309 #pragma mark - Platform views
2312 return self.engine.platformViewsController;
2316 return self.engine.binaryMessenger;
2319 #pragma mark - FlutterBinaryMessenger
2321 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2322 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2325 - (void)sendOnChannel:(NSString*)channel
2326 message:(NSData*)message
2328 NSAssert(channel,
@"The channel must not be null");
2329 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2333 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2337 binaryMessageHandler:
2339 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2343 setMessageHandlerOnChannel:(NSString*)channel
2346 NSAssert(channel,
@"The channel must not be null");
2347 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2348 binaryMessageHandler:handler
2349 taskQueue:taskQueue];
2353 [
self.engine.binaryMessenger cleanUpConnection:connection];
2356 #pragma mark - FlutterTextureRegistry
2359 return [
self.engine.textureRegistry registerTexture:texture];
2362 - (void)unregisterTexture:(int64_t)textureId {
2363 [
self.engine.textureRegistry unregisterTexture:textureId];
2366 - (void)textureFrameAvailable:(int64_t)textureId {
2367 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2370 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2374 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2378 - (id<FlutterPluginRegistry>)pluginRegistry {
2382 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2383 return UIAccessibilityIsVoiceOverRunning();
2386 #pragma mark - FlutterPluginRegistry
2389 return [
self.engine registrarForPlugin:pluginKey];
2392 - (BOOL)hasPlugin:(NSString*)pluginKey {
2393 return [
self.engine hasPlugin:pluginKey];
2396 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2397 return [
self.engine valuePublishedByPlugin:pluginKey];
2400 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2402 completion:(
void (^)(
void))completion {
2403 self.isPresentingViewControllerAnimating = YES;
2405 [
super presentViewController:viewControllerToPresent
2408 weakSelf.isPresentingViewControllerAnimating = NO;
2415 - (BOOL)isPresentingViewController {
2416 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2419 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2420 API_AVAILABLE(ios(13.4)) {
2421 CGPoint location = [gestureRecognizer locationInView:self.view];
2422 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2424 flutter::PointerData pointer_data;
2425 pointer_data.Clear();
2429 return pointer_data;
2432 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2433 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2434 API_AVAILABLE(ios(13.4)) {
2438 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2439 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2440 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2441 event.type == UIEventTypeScroll) {
2443 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2444 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2445 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2446 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2447 pointer_data.view_id =
self.viewIdentifier;
2449 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2452 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2453 packet->SetPointerData(0, pointer_data);
2454 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2455 self.scrollInertiaEventAppKitDeadline = 0;
2462 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2465 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2466 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2467 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2468 pointer_data.view_id =
self.viewIdentifier;
2470 switch (_hoverGestureRecognizer.state) {
2471 case UIGestureRecognizerStateBegan:
2472 pointer_data.change = flutter::PointerData::Change::kAdd;
2474 case UIGestureRecognizerStateChanged:
2475 pointer_data.change = flutter::PointerData::Change::kHover;
2477 case UIGestureRecognizerStateEnded:
2478 case UIGestureRecognizerStateCancelled:
2479 pointer_data.change = flutter::PointerData::Change::kRemove;
2484 pointer_data.change = flutter::PointerData::Change::kHover;
2488 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2489 BOOL isRunningOnMac = NO;
2490 if (@available(iOS 14.0, *)) {
2494 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2497 time >
self.scrollInertiaEventStartline) {
2501 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2502 packet->SetPointerData(0, pointer_data);
2503 flutter::PointerData inertia_cancel = pointer_data;
2504 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2505 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2506 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2507 inertia_cancel.view_id =
self.viewIdentifier;
2508 packet->SetPointerData(1, inertia_cancel);
2509 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2510 self.scrollInertiaEventStartline = DBL_MAX;
2512 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2513 packet->SetPointerData(0, pointer_data);
2514 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2518 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2519 CGPoint translation = [recognizer translationInView:self.view];
2520 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2522 translation.x *= scale;
2523 translation.y *= scale;
2525 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2526 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2527 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2528 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2529 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2530 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2531 pointer_data.view_id =
self.viewIdentifier;
2537 if (recognizer.state != UIGestureRecognizerStateEnded) {
2543 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2544 packet->SetPointerData(0, pointer_data);
2545 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2548 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2549 CGPoint translation = [recognizer translationInView:self.view];
2550 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2552 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2553 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2554 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2555 pointer_data.view_id =
self.viewIdentifier;
2556 switch (recognizer.state) {
2557 case UIGestureRecognizerStateBegan:
2558 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2560 case UIGestureRecognizerStateChanged:
2561 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2562 pointer_data.pan_x = translation.x * scale;
2563 pointer_data.pan_y = translation.y * scale;
2564 pointer_data.pan_delta_x = 0;
2565 pointer_data.pan_delta_y = 0;
2566 pointer_data.scale = 1;
2568 case UIGestureRecognizerStateEnded:
2569 case UIGestureRecognizerStateCancelled:
2570 self.scrollInertiaEventStartline =
2571 [[NSProcessInfo processInfo] systemUptime] +
2581 self.scrollInertiaEventAppKitDeadline =
2582 [[NSProcessInfo processInfo] systemUptime] +
2583 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2584 [recognizer velocityInView:self.view].y))) -
2586 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2590 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2591 (
long)recognizer.state);
2595 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2596 packet->SetPointerData(0, pointer_data);
2597 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2600 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2601 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2602 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2603 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2604 pointer_data.view_id =
self.viewIdentifier;
2605 switch (recognizer.state) {
2606 case UIGestureRecognizerStateBegan:
2607 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2609 case UIGestureRecognizerStateChanged:
2610 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2611 pointer_data.scale = recognizer.scale;
2612 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2614 case UIGestureRecognizerStateEnded:
2615 case UIGestureRecognizerStateCancelled:
2616 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2620 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2621 (
long)recognizer.state);
2625 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2626 packet->SetPointerData(0, pointer_data);
2627 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2630 #pragma mark - State Restoration
2632 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2633 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2634 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2635 length:restorationData.length
2636 forKey:kFlutterRestorationStateAppData];
2637 [
super encodeRestorableStateWithCoder:coder];
2640 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2641 NSUInteger restorationDataLength;
2642 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2643 returnedLength:&restorationDataLength];
2644 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2645 [
self.engine.restorationPlugin setRestorationData:restorationData];
2649 return self.engine.restorationPlugin;