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/fml/platform/darwin/scoped_nsobject.h"
17 #include "flutter/runtime/ptrace_check.h"
18 #include "flutter/shell/common/thread_host.h"
34 #import "flutter/shell/platform/embedder/embedder.h"
35 #import "flutter/third_party/spring_animation/spring_animation.h"
45 @"FlutterViewControllerHideHomeIndicator";
47 @"FlutterViewControllerShowHomeIndicator";
65 @property(nonatomic, readonly) int64_t viewIdentifier;
68 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
69 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
74 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
79 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
80 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
81 @property(nonatomic, retain)
VSyncClient* keyboardAnimationVSyncClient;
82 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
83 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
84 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
92 @property(nonatomic, retain)
VSyncClient* touchRateCorrectionVSyncClient;
98 @property(nonatomic, retain)
99 UIHoverGestureRecognizer* hoverGestureRecognizer
API_AVAILABLE(ios(13.4));
101 @property(nonatomic, retain)
102 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
104 @property(nonatomic, retain)
105 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
107 @property(nonatomic, retain)
108 UIPinchGestureRecognizer* pinchGestureRecognizer
API_AVAILABLE(ios(13.4));
110 @property(nonatomic, retain)
111 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
116 - (void)addInternalPlugins;
117 - (void)deregisterNotifications;
121 std::unique_ptr<fml::WeakNSObjectFactory<FlutterViewController>>
_weakFactory;
122 fml::scoped_nsobject<FlutterEngine>
_engine;
155 @synthesize displayingFlutterUI = _displayingFlutterUI;
156 @synthesize prefersStatusBarHidden = _flutterPrefersStatusBarHidden;
157 @dynamic viewIdentifier;
159 #pragma mark - Manage and override all designated initializers
162 nibName:(nullable NSString*)nibName
163 bundle:(nullable NSBundle*)nibBundle {
164 NSAssert(
engine != nil,
@"Engine is required");
165 self = [
super initWithNibName:nibName bundle:nibBundle];
168 if (
engine.viewController) {
169 FML_LOG(ERROR) <<
"The supplied FlutterEngine " << [[engine description] UTF8String]
170 <<
" is already used with FlutterViewController instance "
171 << [[engine.viewController description] UTF8String]
172 <<
". One instance of the FlutterEngine can only be attached to one "
173 "FlutterViewController at a time. Set FlutterEngine.viewController "
174 "to nil before attaching it to another FlutterViewController.";
179 opaque:
self.isViewOpaque
180 enableWideGamut:
engine.project.isWideGamutEnabled]);
181 _weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(
self);
184 [
self performCommonViewControllerInitialization];
185 [engine setViewController:self];
192 nibName:(NSString*)nibName
193 bundle:(NSBundle*)nibBundle {
194 self = [
super initWithNibName:nibName bundle:nibBundle];
196 [
self sharedSetupWithProject:project initialRoute:nil];
203 initialRoute:(NSString*)initialRoute
204 nibName:(NSString*)nibName
205 bundle:(NSBundle*)nibBundle {
206 self = [
super initWithNibName:nibName bundle:nibBundle];
208 [
self sharedSetupWithProject:project initialRoute:initialRoute];
214 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
215 return [
self initWithProject:nil nibName:nil bundle:nil];
219 self = [
super initWithCoder:aDecoder];
223 - (void)awakeFromNib {
224 [
super awakeFromNib];
226 [
self sharedSetupWithProject:nil initialRoute:nil];
230 - (instancetype)init {
231 return [
self initWithProject:nil nibName:nil bundle:nil];
235 initialRoute:(nullable NSString*)initialRoute {
242 _weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(
self);
244 initWithName:@"io.flutter"
246 allowHeadlessExecution:self.engineAllowHeadlessExecution
247 restorationEnabled:[
self restorationIdentifier] != nil]};
256 opaque:
self.isViewOpaque
258 [_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
261 [
self loadDefaultSplashScreenView];
262 [
self performCommonViewControllerInitialization];
265 - (BOOL)isViewOpaque {
269 - (void)setViewOpaque:(BOOL)value {
273 [_flutterView.get().layer setNeedsLayout];
277 #pragma mark - Common view controller initialization tasks
279 - (void)performCommonViewControllerInitialization {
289 [
self setUpNotificationCenterObservers];
296 - (fml::WeakNSObject<FlutterViewController>)getWeakNSObject {
300 - (void)setUpNotificationCenterObservers {
301 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
302 [center addObserver:self
303 selector:@selector(onOrientationPreferencesUpdated:)
304 name:@(flutter::kOrientationUpdateNotificationName)
307 [center addObserver:self
308 selector:@selector(onPreferredStatusBarStyleUpdated:)
309 name:@(flutter::kOverlayStyleUpdateNotificationName)
312 #if APPLICATION_EXTENSION_API_ONLY
313 if (@available(iOS 13.0, *)) {
314 [
self setUpSceneLifecycleNotifications:center];
316 [
self setUpApplicationLifecycleNotifications:center];
319 [
self setUpApplicationLifecycleNotifications:center];
322 [center addObserver:self
323 selector:@selector(keyboardWillChangeFrame:)
324 name:UIKeyboardWillChangeFrameNotification
327 [center addObserver:self
328 selector:@selector(keyboardWillShowNotification:)
329 name:UIKeyboardWillShowNotification
332 [center addObserver:self
333 selector:@selector(keyboardWillBeHidden:)
334 name:UIKeyboardWillHideNotification
337 [center addObserver:self
338 selector:@selector(onAccessibilityStatusChanged:)
339 name:UIAccessibilityVoiceOverStatusDidChangeNotification
342 [center addObserver:self
343 selector:@selector(onAccessibilityStatusChanged:)
344 name:UIAccessibilitySwitchControlStatusDidChangeNotification
347 [center addObserver:self
348 selector:@selector(onAccessibilityStatusChanged:)
349 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
352 [center addObserver:self
353 selector:@selector(onAccessibilityStatusChanged:)
354 name:UIAccessibilityInvertColorsStatusDidChangeNotification
357 [center addObserver:self
358 selector:@selector(onAccessibilityStatusChanged:)
359 name:UIAccessibilityReduceMotionStatusDidChangeNotification
362 [center addObserver:self
363 selector:@selector(onAccessibilityStatusChanged:)
364 name:UIAccessibilityBoldTextStatusDidChangeNotification
367 [center addObserver:self
368 selector:@selector(onAccessibilityStatusChanged:)
369 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
372 if (@available(iOS 13.0, *)) {
373 [center addObserver:self
374 selector:@selector(onAccessibilityStatusChanged:)
375 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
379 [center addObserver:self
380 selector:@selector(onUserSettingsChanged:)
381 name:UIContentSizeCategoryDidChangeNotification
384 [center addObserver:self
385 selector:@selector(onHideHomeIndicatorNotification:)
386 name:FlutterViewControllerHideHomeIndicator
389 [center addObserver:self
390 selector:@selector(onShowHomeIndicatorNotification:)
391 name:FlutterViewControllerShowHomeIndicator
395 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
396 [center addObserver:self
397 selector:@selector(sceneBecameActive:)
398 name:UISceneDidActivateNotification
401 [center addObserver:self
402 selector:@selector(sceneWillResignActive:)
403 name:UISceneWillDeactivateNotification
406 [center addObserver:self
407 selector:@selector(sceneWillDisconnect:)
408 name:UISceneDidDisconnectNotification
411 [center addObserver:self
412 selector:@selector(sceneDidEnterBackground:)
413 name:UISceneDidEnterBackgroundNotification
416 [center addObserver:self
417 selector:@selector(sceneWillEnterForeground:)
418 name:UISceneWillEnterForegroundNotification
422 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
423 [center addObserver:self
424 selector:@selector(applicationBecameActive:)
425 name:UIApplicationDidBecomeActiveNotification
428 [center addObserver:self
429 selector:@selector(applicationWillResignActive:)
430 name:UIApplicationWillResignActiveNotification
433 [center addObserver:self
434 selector:@selector(applicationWillTerminate:)
435 name:UIApplicationWillTerminateNotification
438 [center addObserver:self
439 selector:@selector(applicationDidEnterBackground:)
440 name:UIApplicationDidEnterBackgroundNotification
443 [center addObserver:self
444 selector:@selector(applicationWillEnterForeground:)
445 name:UIApplicationWillEnterForegroundNotification
449 - (void)setInitialRoute:(NSString*)route {
450 [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route];
454 [[_engine.get() navigationChannel] invokeMethod:@"popRoute" arguments:nil];
457 - (void)pushRoute:(NSString*)route {
458 [[_engine.get() navigationChannel] invokeMethod:@"pushRoute" arguments:route];
461 #pragma mark - Loading the view
463 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
465 return existing_view;
468 auto placeholder = [[[UIView alloc] init] autorelease];
470 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
471 if (@available(iOS 13.0, *)) {
472 placeholder.backgroundColor = UIColor.systemBackgroundColor;
474 placeholder.backgroundColor = UIColor.whiteColor;
476 placeholder.autoresizesSubviews = YES;
481 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
482 auto messageLabel = [[[UILabel alloc] init] autorelease];
483 messageLabel.numberOfLines = 0u;
484 messageLabel.textAlignment = NSTextAlignmentCenter;
485 messageLabel.autoresizingMask =
486 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
488 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
489 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
490 @"modes to enable launching from the home screen.";
491 [placeholder addSubview:messageLabel];
499 self.view.multipleTouchEnabled = YES;
500 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
502 [
self installSplashScreenViewIfNecessary];
503 UIScrollView* scrollView = [[UIScrollView alloc] init];
504 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
506 scrollView.backgroundColor = UIColor.whiteColor;
507 scrollView.delegate =
self;
512 [
self.view addSubview:scrollView];
516 - (
flutter::PointerData)generatePointerDataForFake {
517 flutter::PointerData pointer_data;
518 pointer_data.Clear();
519 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
527 static void SendFakeTouchEvent(UIScreen* screen,
530 flutter::PointerData::Change change) {
531 const CGFloat scale = screen.scale;
532 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
533 pointer_data.physical_x = location.x * scale;
534 pointer_data.physical_y = location.y * scale;
535 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
536 pointer_data.change = change;
537 packet->SetPointerData(0, pointer_data);
538 [engine dispatchPointerDataPacket:std::move(packet)];
541 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
545 CGPoint statusBarPoint = CGPointZero;
546 UIScreen* screen = [
self flutterScreenIfViewLoaded];
548 SendFakeTouchEvent(screen,
_engine.get(), statusBarPoint, flutter::PointerData::Change::kDown);
549 SendFakeTouchEvent(screen,
_engine.get(), statusBarPoint, flutter::PointerData::Change::kUp);
554 #pragma mark - Managing launch views
556 - (void)installSplashScreenViewIfNecessary {
559 if (
_splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
560 [_splashScreenView.get() removeFromSuperview];
566 UIView* splashScreenView =
self.splashScreenView;
567 if (splashScreenView == nil) {
570 splashScreenView.frame =
self.view.bounds;
571 [
self.view addSubview:splashScreenView];
574 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
578 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
579 if (_displayingFlutterUI != displayingFlutterUI) {
580 if (displayingFlutterUI == YES) {
581 if (!
self.viewIfLoaded.window) {
585 [
self willChangeValueForKey:@"displayingFlutterUI"];
586 _displayingFlutterUI = displayingFlutterUI;
587 [
self didChangeValueForKey:@"displayingFlutterUI"];
591 - (void)callViewRenderedCallback {
592 self.displayingFlutterUI = YES;
599 - (void)removeSplashScreenView:(dispatch_block_t _Nullable)onComplete {
601 UIView* splashScreen = [_splashScreenView.get() retain];
603 [UIView animateWithDuration:0.2
605 splashScreen.alpha = 0;
607 completion:^(BOOL finished) {
608 [splashScreen removeFromSuperview];
609 [splashScreen release];
616 - (void)installFirstFrameCallback {
621 fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = [_engine.get() platformView];
622 if (!weakPlatformView) {
627 weakPlatformView->SetNextFrameCallback([weakSelf = [
self getWeakNSObject],
628 platformTaskRunner = [
_engine.get() platformTaskRunner],
629 rasterTaskRunner = [
_engine.get() rasterTaskRunner]]() {
630 FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread());
632 platformTaskRunner->PostTask([weakSelf]() {
634 fml::scoped_nsobject<FlutterViewController> flutterViewController(
635 [(FlutterViewController*)weakSelf.get() retain]);
636 if (flutterViewController) {
637 if (flutterViewController.get()->_splashScreenView) {
638 [flutterViewController removeSplashScreenView:^{
639 [flutterViewController callViewRenderedCallback];
642 [flutterViewController callViewRenderedCallback];
650 #pragma mark - Properties
652 - (int64_t)viewIdentifier {
655 return flutter::kFlutterImplicitViewId;
658 - (UIView*)splashScreenView {
665 - (UIView*)keyboardAnimationView {
669 - (SpringAnimation*)keyboardSpringAnimation {
673 - (BOOL)loadDefaultSplashScreenView {
674 NSString* launchscreenName =
675 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
676 if (launchscreenName == nil) {
679 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
681 splashView = [
self splashScreenFromXib:launchscreenName];
686 self.splashScreenView = splashView;
690 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
691 UIStoryboard* storyboard = nil;
693 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
694 }
@catch (NSException* exception) {
698 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
699 return splashScreenViewController.view;
704 - (UIView*)splashScreenFromXib:(NSString*)name {
705 NSArray* objects = nil;
707 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
708 }
@catch (NSException* exception) {
711 if ([objects count] != 0) {
712 UIView* view = [objects objectAtIndex:0];
718 - (void)setSplashScreenView:(UIView*)view {
722 [
self removeSplashScreenView:nil];
729 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
732 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
736 #pragma mark - Surface creation and teardown updates
738 - (void)surfaceUpdated:(BOOL)appeared {
746 [
self installFirstFrameCallback];
747 [_engine.get() platformViewsController]->SetFlutterView(
_flutterView.get());
748 [_engine.get() platformViewsController]->SetFlutterViewController(
self);
749 [_engine.get() iosPlatformView]->NotifyCreated();
751 self.displayingFlutterUI = NO;
752 [_engine.get() iosPlatformView]->NotifyDestroyed();
753 [_engine.get() platformViewsController]->SetFlutterView(
nullptr);
754 [_engine.get() platformViewsController]->SetFlutterViewController(
nullptr);
758 #pragma mark - UIViewController lifecycle notifications
760 - (void)viewDidLoad {
761 TRACE_EVENT0(
"flutter",
"viewDidLoad");
764 [_engine.get() launchEngine:nil libraryURI:nil entrypointArgs:nil];
765 [_engine.get() setViewController:self];
768 [_engine.get() attachView];
772 [
self addInternalPlugins];
775 [
self createTouchRateCorrectionVSyncClientIfNeeded];
777 if (@available(iOS 13.4, *)) {
778 _hoverGestureRecognizer =
779 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
780 _hoverGestureRecognizer.delegate =
self;
781 [_flutterView.get() addGestureRecognizer:_hoverGestureRecognizer];
783 _discreteScrollingPanGestureRecognizer =
784 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
785 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
790 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
791 _discreteScrollingPanGestureRecognizer.delegate =
self;
792 [_flutterView.get() addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
793 _continuousScrollingPanGestureRecognizer =
794 [[UIPanGestureRecognizer alloc] initWithTarget:self
795 action:@selector(continuousScrollEvent:)];
796 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
797 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
798 _continuousScrollingPanGestureRecognizer.delegate =
self;
799 [_flutterView.get() addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
800 _pinchGestureRecognizer =
801 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
802 _pinchGestureRecognizer.allowedTouchTypes = @[];
803 _pinchGestureRecognizer.delegate =
self;
804 [_flutterView.get() addGestureRecognizer:_pinchGestureRecognizer];
805 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
806 _rotationGestureRecognizer.allowedTouchTypes = @[];
807 _rotationGestureRecognizer.delegate =
self;
808 [_flutterView.get() addGestureRecognizer:_rotationGestureRecognizer];
814 - (void)addInternalPlugins {
816 fml::WeakNSObject<FlutterViewController> weakSelf = [
self getWeakNSObject];
818 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
820 [weakSelf.get()->_engine.get() sendKeyEvent:event callback:callback userData:userData];
824 initWithSendEvent:sendEvent] autorelease]];
826 initWithChannel:self.engine.keyEventChannel] autorelease];
827 [
self.keyboardManager addPrimaryResponder:responder];
830 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
837 - (void)removeInternalPlugins {
838 self.keyboardManager = nil;
841 - (void)viewWillAppear:(BOOL)animated {
842 TRACE_EVENT0(
"flutter",
"viewWillAppear");
845 [
self onUserSettingsChanged:nil];
850 [
self surfaceUpdated:YES];
852 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
853 [[_engine.get() restorationPlugin] markRestorationComplete];
856 [
super viewWillAppear:animated];
859 - (void)viewDidAppear:(BOOL)animated {
860 TRACE_EVENT0(
"flutter",
"viewDidAppear");
862 [
self onUserSettingsChanged:nil];
863 [
self onAccessibilityStatusChanged:nil];
864 BOOL stateIsActive = YES;
865 #if APPLICATION_EXTENSION_API_ONLY
866 if (@available(iOS 13.0, *)) {
867 stateIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
868 UISceneActivationStateForegroundActive;
871 stateIsActive = UIApplication.sharedApplication.applicationState == UIApplicationStateActive;
874 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.resumed"];
877 [
super viewDidAppear:animated];
880 - (void)viewWillDisappear:(BOOL)animated {
881 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
883 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
885 [
super viewWillDisappear:animated];
888 - (void)viewDidDisappear:(BOOL)animated {
889 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
891 [
self invalidateKeyboardAnimationVSyncClient];
892 [
self ensureViewportMetricsIsCorrect];
893 [
self surfaceUpdated:NO];
894 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"];
895 [
self flushOngoingTouches];
896 [_engine.get() notifyLowMemory];
899 [
super viewDidDisappear:animated];
902 - (void)viewWillTransitionToSize:(CGSize)size
903 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
904 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
914 NSTimeInterval transitionDuration = coordinator.transitionDuration;
916 if (transitionDuration == 0) {
920 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
921 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
922 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
923 dispatch_get_main_queue(), ^{
926 _shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
927 [
self updateViewportMetricsIfNeeded];
931 - (void)flushOngoingTouches {
933 auto packet = std::make_unique<flutter::PointerDataPacket>(
_ongoingTouches.get().count);
934 size_t pointer_index = 0;
939 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
941 pointer_data.change = flutter::PointerData::Change::kCancel;
942 pointer_data.device = device.longLongValue;
943 pointer_data.pointer_identifier = 0;
944 pointer_data.view_id =
self.viewIdentifier;
947 pointer_data.physical_x = 0;
948 pointer_data.physical_y = 0;
949 pointer_data.physical_delta_x = 0.0;
950 pointer_data.physical_delta_y = 0.0;
951 pointer_data.pressure = 1.0;
952 pointer_data.pressure_max = 1.0;
954 packet->SetPointerData(pointer_index++, pointer_data);
957 [_ongoingTouches removeAllObjects];
958 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
962 - (void)deregisterNotifications {
963 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
966 [[NSNotificationCenter defaultCenter] removeObserver:self];
974 [
self removeInternalPlugins];
975 [
self deregisterNotifications];
977 [
self invalidateKeyboardAnimationVSyncClient];
978 [
self invalidateTouchRateCorrectionVSyncClient];
980 _hoverGestureRecognizer.delegate = nil;
981 [_hoverGestureRecognizer release];
982 _discreteScrollingPanGestureRecognizer.delegate = nil;
983 [_discreteScrollingPanGestureRecognizer release];
984 _continuousScrollingPanGestureRecognizer.delegate = nil;
985 [_continuousScrollingPanGestureRecognizer release];
986 _pinchGestureRecognizer.delegate = nil;
987 [_pinchGestureRecognizer release];
988 _rotationGestureRecognizer.delegate = nil;
989 [_rotationGestureRecognizer release];
993 #pragma mark - Application lifecycle notifications
995 - (void)applicationBecameActive:(NSNotification*)notification {
996 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
997 [
self appOrSceneBecameActive];
1000 - (void)applicationWillResignActive:(NSNotification*)notification {
1001 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1002 [
self appOrSceneWillResignActive];
1005 - (void)applicationWillTerminate:(NSNotification*)notification {
1006 [
self appOrSceneWillTerminate];
1009 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1010 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1011 [
self appOrSceneDidEnterBackground];
1014 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1015 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1016 [
self appOrSceneWillEnterForeground];
1019 #pragma mark - Scene lifecycle notifications
1021 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1022 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1023 [
self appOrSceneBecameActive];
1026 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1027 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1028 [
self appOrSceneWillResignActive];
1031 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1032 [
self appOrSceneWillTerminate];
1035 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1036 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1037 [
self appOrSceneDidEnterBackground];
1040 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1041 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1042 [
self appOrSceneWillEnterForeground];
1045 #pragma mark - Lifecycle shared
1047 - (void)appOrSceneBecameActive {
1048 self.isKeyboardInOrTransitioningFromBackground = NO;
1050 [
self surfaceUpdated:YES];
1052 [
self performSelector:@selector(goToApplicationLifecycle:)
1053 withObject:@"AppLifecycleState.resumed"
1057 - (void)appOrSceneWillResignActive {
1058 [NSObject cancelPreviousPerformRequestsWithTarget:self
1059 selector:@selector(goToApplicationLifecycle:)
1060 object:@"AppLifecycleState.resumed"];
1061 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1064 - (void)appOrSceneWillTerminate {
1065 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1066 [
self.engine destroyContext];
1069 - (void)appOrSceneDidEnterBackground {
1070 self.isKeyboardInOrTransitioningFromBackground = YES;
1071 [
self surfaceUpdated:NO];
1072 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1075 - (void)appOrSceneWillEnterForeground {
1076 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1080 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1083 if (
self.viewIfLoaded.window) {
1084 [[_engine.get() lifecycleChannel] sendMessage:state];
1088 #pragma mark - Touch event handling
1090 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1092 case UITouchPhaseBegan:
1093 return flutter::PointerData::Change::kDown;
1094 case UITouchPhaseMoved:
1095 case UITouchPhaseStationary:
1098 return flutter::PointerData::Change::kMove;
1099 case UITouchPhaseEnded:
1100 return flutter::PointerData::Change::kUp;
1101 case UITouchPhaseCancelled:
1102 return flutter::PointerData::Change::kCancel;
1105 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1109 return flutter::PointerData::Change::kCancel;
1112 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1113 switch (touch.type) {
1114 case UITouchTypeDirect:
1115 case UITouchTypeIndirect:
1116 return flutter::PointerData::DeviceKind::kTouch;
1117 case UITouchTypeStylus:
1118 return flutter::PointerData::DeviceKind::kStylus;
1119 case UITouchTypeIndirectPointer:
1120 return flutter::PointerData::DeviceKind::kMouse;
1122 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1126 return flutter::PointerData::DeviceKind::kTouch;
1133 - (void)dispatchTouches:(NSSet*)touches
1134 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1135 event:(UIEvent*)event {
1160 NSUInteger touches_to_remove_count = 0;
1161 for (UITouch* touch in touches) {
1162 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1163 touches_to_remove_count++;
1168 [
self triggerTouchRateCorrectionIfNeeded:touches];
1170 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1172 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1174 size_t pointer_index = 0;
1176 for (UITouch* touch in touches) {
1177 CGPoint windowCoordinates = [touch locationInView:self.view];
1179 flutter::PointerData pointer_data;
1180 pointer_data.Clear();
1185 pointer_data.change = overridden_change !=
nullptr
1186 ? *overridden_change
1187 : PointerDataChangeFromUITouchPhase(touch.phase);
1189 pointer_data.kind = DeviceKindFromTouchType(touch);
1191 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1193 pointer_data.view_id =
self.viewIdentifier;
1196 pointer_data.pointer_identifier = 0;
1198 pointer_data.physical_x = windowCoordinates.x * scale;
1199 pointer_data.physical_y = windowCoordinates.y * scale;
1202 pointer_data.physical_delta_x = 0.0;
1203 pointer_data.physical_delta_y = 0.0;
1205 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1208 switch (pointer_data.change) {
1209 case flutter::PointerData::Change::kDown:
1210 [_ongoingTouches addObject:deviceKey];
1212 case flutter::PointerData::Change::kCancel:
1213 case flutter::PointerData::Change::kUp:
1214 [_ongoingTouches removeObject:deviceKey];
1216 case flutter::PointerData::Change::kHover:
1217 case flutter::PointerData::Change::kMove:
1220 case flutter::PointerData::Change::kAdd:
1221 case flutter::PointerData::Change::kRemove:
1224 case flutter::PointerData::Change::kPanZoomStart:
1225 case flutter::PointerData::Change::kPanZoomUpdate:
1226 case flutter::PointerData::Change::kPanZoomEnd:
1232 pointer_data.pressure = touch.force;
1233 pointer_data.pressure_max = touch.maximumPossibleForce;
1234 pointer_data.radius_major = touch.majorRadius;
1235 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1236 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1251 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1271 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1273 if (@available(iOS 13.4, *)) {
1274 if (event !=
nullptr) {
1275 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1276 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1278 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1279 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1284 packet->SetPointerData(pointer_index++, pointer_data);
1286 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1287 flutter::PointerData remove_pointer_data = pointer_data;
1288 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1289 packet->SetPointerData(pointer_index++, remove_pointer_data);
1293 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
1296 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1297 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1300 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1301 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1304 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1305 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1308 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1309 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1312 - (void)forceTouchesCancelled:(NSSet*)touches {
1313 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1314 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1317 #pragma mark - Touch events rate correction
1319 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1320 if (_touchRateCorrectionVSyncClient != nil) {
1325 const double epsilon = 0.1;
1326 if (displayRefreshRate < 60.0 + epsilon) {
1334 flutter::Shell& shell = [_engine.get() shell];
1335 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1338 _touchRateCorrectionVSyncClient =
1339 [[
VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
1341 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1344 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1345 if (_touchRateCorrectionVSyncClient == nil) {
1353 BOOL isUserInteracting = NO;
1354 for (UITouch* touch in touches) {
1355 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1356 isUserInteracting = YES;
1362 [_touchRateCorrectionVSyncClient await];
1364 [_touchRateCorrectionVSyncClient pause];
1368 - (void)invalidateTouchRateCorrectionVSyncClient {
1369 [_touchRateCorrectionVSyncClient invalidate];
1370 [_touchRateCorrectionVSyncClient release];
1371 _touchRateCorrectionVSyncClient = nil;
1374 #pragma mark - Handle view resizing
1376 - (void)updateViewportMetricsIfNeeded {
1377 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1381 [_engine.get() updateViewportMetrics:_viewportMetrics];
1385 - (void)viewDidLayoutSubviews {
1386 CGRect viewBounds =
self.view.bounds;
1387 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1390 _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1396 [
self setViewportMetricsSize];
1397 [
self setViewportMetricsPaddings];
1398 [
self updateViewportMetricsIfNeeded];
1403 bool applicationOrSceneIsActive = YES;
1404 #if APPLICATION_EXTENSION_API_ONLY
1405 if (@available(iOS 13.0, *)) {
1406 applicationOrSceneIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
1407 UISceneActivationStateForegroundActive;
1410 applicationOrSceneIsActive =
1411 [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
1416 if (firstViewBoundsUpdate && applicationOrSceneIsActive &&
_engine) {
1417 [
self surfaceUpdated:YES];
1419 flutter::Shell& shell = [_engine.get() shell];
1420 fml::TimeDelta waitTime =
1421 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1422 fml::TimeDelta::FromMilliseconds(200);
1424 fml::TimeDelta::FromMilliseconds(100);
1426 if (shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded) {
1427 FML_LOG(INFO) <<
"Timeout waiting for the first frame to render. This may happen in "
1428 <<
"unoptimized builds. If this is a release build, you should load a less "
1429 <<
"complex frame to avoid the timeout.";
1434 - (void)viewSafeAreaInsetsDidChange {
1435 [
self setViewportMetricsPaddings];
1436 [
self updateViewportMetricsIfNeeded];
1437 [
super viewSafeAreaInsetsDidChange];
1441 - (void)setViewportMetricsSize {
1442 UIScreen* screen = [
self flutterScreenIfViewLoaded];
1447 CGFloat scale = screen.scale;
1455 - (void)setViewportMetricsPaddings {
1456 UIScreen* screen = [
self flutterScreenIfViewLoaded];
1461 CGFloat scale = screen.scale;
1462 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1463 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1464 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1465 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1468 #pragma mark - Keyboard events
1470 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1475 [
self handleKeyboardNotification:notification];
1478 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1483 [
self handleKeyboardNotification:notification];
1486 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1490 [
self handleKeyboardNotification:notification];
1493 - (void)handleKeyboardNotification:(NSNotification*)notification {
1496 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1500 NSDictionary* info = notification.userInfo;
1501 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1502 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1503 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1504 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1507 if (
self.targetViewInsetBottom == calculatedInset) {
1511 self.targetViewInsetBottom = calculatedInset;
1512 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1519 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1520 BOOL keyboardAnimationIsCompounding =
1521 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1524 self.keyboardAnimationIsShowing = keyboardWillShow;
1526 if (!keyboardAnimationIsCompounding) {
1527 [
self startKeyBoardAnimation:duration];
1528 }
else if ([
self keyboardSpringAnimation]) {
1529 [
self keyboardSpringAnimation].toValue =
self.targetViewInsetBottom;
1533 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1538 if (notification.name == UIKeyboardWillHideNotification) {
1547 NSDictionary* info = notification.userInfo;
1548 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1549 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1550 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1556 if (CGRectIsEmpty(keyboardFrame)) {
1561 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1565 if (@available(iOS 13.0, *)) {
1573 if (
self.isKeyboardInOrTransitioningFromBackground) {
1581 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1582 NSDictionary* info = notification.userInfo;
1586 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1587 if (isLocal && ![isLocal boolValue]) {
1597 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1605 NSDictionary* info = notification.userInfo;
1606 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1608 if (notification.name == UIKeyboardWillHideNotification) {
1609 return FlutterKeyboardModeHidden;
1614 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1615 return FlutterKeyboardModeFloating;
1618 if (CGRectIsEmpty(keyboardFrame)) {
1619 return FlutterKeyboardModeHidden;
1622 CGRect screenRect = [
self flutterScreenIfViewLoaded].bounds;
1623 CGRect adjustedKeyboardFrame = keyboardFrame;
1624 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1625 keyboardFrame:keyboardFrame];
1630 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1631 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1632 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1633 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1635 CGFloat screenHeight = CGRectGetHeight(screenRect);
1636 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1637 if (round(adjustedKeyboardBottom) < screenHeight) {
1638 return FlutterKeyboardModeFloating;
1640 return FlutterKeyboardModeDocked;
1642 return FlutterKeyboardModeHidden;
1645 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1649 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1650 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1651 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1652 CGFloat screenHeight = CGRectGetHeight(screenRect);
1653 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1657 if (screenHeight == keyboardBottom) {
1660 CGRect viewRectRelativeToScreen =
1661 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1662 toCoordinateSpace:[
self flutterScreenIfViewLoaded].coordinateSpace];
1663 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1664 CGFloat offset = screenHeight - viewBottom;
1672 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1674 if (keyboardMode == FlutterKeyboardModeDocked) {
1676 CGRect viewRectRelativeToScreen =
1677 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1678 toCoordinateSpace:[
self flutterScreenIfViewLoaded].coordinateSpace];
1679 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1680 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1685 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1686 return portionOfKeyboardInView * scale;
1691 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1693 if (
_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1699 if ([
self keyboardAnimationView] == nil) {
1700 UIView* keyboardAnimationView = [[UIView alloc] init];
1701 [keyboardAnimationView setHidden:YES];
1705 if ([
self keyboardAnimationView].superview == nil) {
1706 [
self.view addSubview:[
self keyboardAnimationView]];
1710 [[
self keyboardAnimationView].layer removeAllAnimations];
1713 [
self keyboardAnimationView].frame =
1715 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1716 self.originalViewInsetBottom =
_viewportMetrics.physical_view_inset_bottom;
1719 [
self invalidateKeyboardAnimationVSyncClient];
1721 fml::WeakNSObject<FlutterViewController> weakSelf = [
self getWeakNSObject];
1723 fml::TimePoint keyboardAnimationTargetTime) {
1727 fml::scoped_nsobject<FlutterViewController> flutterViewController(
1729 if (!flutterViewController) {
1734 if (!flutterViewController.get().isViewLoaded) {
1739 if ([flutterViewController keyboardAnimationView] == nil) {
1744 if (flutterViewController.get().keyboardAnimationVSyncClient == nil) {
1748 if ([flutterViewController keyboardAnimationView].superview == nil) {
1750 [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
1753 if ([flutterViewController keyboardSpringAnimation] == nil) {
1754 if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
1755 flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1756 flutterViewController.get()
1757 .keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1758 [flutterViewController updateViewportMetricsIfNeeded];
1761 fml::TimeDelta timeElapsed =
1762 keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime;
1763 flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1764 [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
1765 [flutterViewController updateViewportMetricsIfNeeded];
1768 [
self setUpKeyboardAnimationVsyncClient:keyboardAnimationCallback];
1769 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1771 [UIView animateWithDuration:duration
1774 [
self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1777 CAAnimation* keyboardAnimation =
1778 [[
self keyboardAnimationView].layer animationForKey:@"position"];
1779 [
self setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1781 completion:^(BOOL finished) {
1782 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1786 [
self invalidateKeyboardAnimationVSyncClient];
1787 [
self removeKeyboardAnimationView];
1788 [
self ensureViewportMetricsIsCorrect];
1793 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1795 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1801 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1803 initWithStiffness:keyboardCASpringAnimation.stiffness
1804 damping:keyboardCASpringAnimation.damping
1805 mass:keyboardCASpringAnimation.mass
1806 initialVelocity:keyboardCASpringAnimation.initialVelocity
1807 fromValue:
self.originalViewInsetBottom
1808 toValue:
self.targetViewInsetBottom]);
1811 - (void)setUpKeyboardAnimationVsyncClient:
1813 if (!keyboardAnimationCallback) {
1816 NSAssert(_keyboardAnimationVSyncClient == nil,
1817 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1820 fml::scoped_nsprotocol<FlutterKeyboardAnimationCallback> animationCallback(
1821 [keyboardAnimationCallback copy]);
1822 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1823 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1824 fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval;
1825 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1826 animationCallback.get()(keyboardAnimationTargetTime);
1830 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner]
1831 callback:uiCallback];
1832 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1833 [_keyboardAnimationVSyncClient await];
1836 - (void)invalidateKeyboardAnimationVSyncClient {
1837 [_keyboardAnimationVSyncClient invalidate];
1838 [_keyboardAnimationVSyncClient release];
1839 _keyboardAnimationVSyncClient = nil;
1842 - (void)removeKeyboardAnimationView {
1843 if ([
self keyboardAnimationView].superview != nil) {
1844 [[
self keyboardAnimationView] removeFromSuperview];
1848 - (void)ensureViewportMetricsIsCorrect {
1849 if (
_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1852 [
self updateViewportMetricsIfNeeded];
1857 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1858 if (@available(iOS 13.4, *)) {
1863 [
self.keyboardManager handlePress:press nextAction:next];
1866 - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(
void (^)(BOOL success))completion {
1868 waitForFirstFrame:3.0
1869 callback:^(BOOL didTimeout) {
1871 FML_LOG(ERROR) << "Timeout waiting for the first frame when launching an URL.";
1875 [[_engine.get() navigationChannel]
1876 invokeMethod:@"pushRouteInformation"
1878 @"location" : url.absoluteString ?: [NSNull null],
1880 result:^(id _Nullable result) {
1882 [result isKindOfClass:[NSNumber class]] && [result boolValue];
1885 FML_LOG(ERROR) << "Failed to handle route information in Flutter.";
1887 completion(success);
1908 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1909 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1910 if (@available(iOS 13.4, *)) {
1911 for (UIPress* press in presses) {
1913 withEvent:event] autorelease]
1915 [
super pressesBegan:[NSSet setWithObject:press] withEvent:event];
1919 [
super pressesBegan:presses withEvent:event];
1923 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1924 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1925 if (@available(iOS 13.4, *)) {
1926 for (UIPress* press in presses) {
1928 withEvent:event] autorelease]
1930 [
super pressesChanged:[NSSet setWithObject:press] withEvent:event];
1934 [
super pressesChanged:presses withEvent:event];
1938 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1939 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1940 if (@available(iOS 13.4, *)) {
1941 for (UIPress* press in presses) {
1943 withEvent:event] autorelease]
1945 [
super pressesEnded:[NSSet setWithObject:press] withEvent:event];
1949 [
super pressesEnded:presses withEvent:event];
1953 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
1954 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1955 if (@available(iOS 13.4, *)) {
1956 for (UIPress* press in presses) {
1958 withEvent:event] autorelease]
1960 [
super pressesCancelled:[NSSet setWithObject:press] withEvent:event];
1964 [
super pressesCancelled:presses withEvent:event];
1968 #pragma mark - Orientation updates
1970 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1972 dispatch_async(dispatch_get_main_queue(), ^{
1973 NSDictionary* info = notification.userInfo;
1975 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1977 if (update == nil) {
1980 [
self performOrientationUpdate:update.unsignedIntegerValue];
1984 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1985 API_AVAILABLE(ios(16.0)) {
1986 for (UIScene* windowScene in windowScenes) {
1987 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
1988 UIWindowSceneGeometryPreferencesIOS* preference = [[[UIWindowSceneGeometryPreferencesIOS alloc]
1989 initWithInterfaceOrientations:_orientationPreferences] autorelease];
1990 [(UIWindowScene*)windowScene
1991 requestGeometryUpdateWithPreferences:preference
1992 errorHandler:^(NSError* error) {
1993 os_log_error(OS_LOG_DEFAULT,
1994 "Failed to change device orientation: %@", error);
1996 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2000 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2004 if (@available(iOS 16.0, *)) {
2005 NSSet<UIScene*>* scenes =
2006 #if APPLICATION_EXTENSION_API_ONLY
2007 self.flutterWindowSceneIfViewLoaded
2008 ? [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded]
2011 [UIApplication.sharedApplication.connectedScenes
2012 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2013 id scene, NSDictionary* bindings) {
2014 return [scene isKindOfClass:[UIWindowScene class]];
2017 [
self requestGeometryUpdateForWindowScenes:scenes];
2019 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2020 if (@available(iOS 13.0, *)) {
2021 UIWindowScene* windowScene = [
self flutterWindowSceneIfViewLoaded];
2024 <<
"Accessing the interface orientation when the window scene is unavailable.";
2027 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2029 #if APPLICATION_EXTENSION_API_ONLY
2030 FML_LOG(ERROR) <<
"Application based status bar orentiation update is not supported in "
2031 "app extension. Orientation: "
2032 << currentInterfaceOrientation;
2034 currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation];
2038 [UIViewController attemptRotationToDeviceOrientation];
2044 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2045 forKey:@"orientation"];
2047 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2048 forKey:@"orientation"];
2050 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2051 forKey:@"orientation"];
2053 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2054 forKey:@"orientation"];
2061 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2062 self.isHomeIndicatorHidden = YES;
2065 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2066 self.isHomeIndicatorHidden = NO;
2069 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2070 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2071 _isHomeIndicatorHidden = hideHomeIndicator;
2072 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2076 - (BOOL)prefersHomeIndicatorAutoHidden {
2077 return self.isHomeIndicatorHidden;
2080 - (BOOL)shouldAutorotate {
2084 - (NSUInteger)supportedInterfaceOrientations {
2088 #pragma mark - Accessibility
2090 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2094 auto platformView = [_engine.get() platformView];
2095 int32_t flags = [
self accessibilityFlags];
2096 #if TARGET_OS_SIMULATOR
2100 platformView->SetSemanticsEnabled(
true);
2101 platformView->SetAccessibilityFeatures(flags);
2103 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2104 bool enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2106 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2108 platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
2109 platformView->SetAccessibilityFeatures(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 [[_engine.get() settingsChannel] sendMessage:@{
2160 @"textScaleFactor" : @([
self textScaleFactor]),
2162 @"platformBrightness" : [
self brightnessMode],
2163 @"platformContrast" : [
self contrastMode],
2164 @"nativeSpellCheckServiceDefined" : @true,
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 {
2275 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2277 dispatch_async(dispatch_get_main_queue(), ^{
2278 NSDictionary* info = notification.userInfo;
2280 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2282 if (update == nil) {
2286 NSInteger style = update.integerValue;
2290 [
self setNeedsStatusBarAppearanceUpdate];
2295 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2296 if (hidden != _flutterPrefersStatusBarHidden) {
2297 _flutterPrefersStatusBarHidden = hidden;
2298 [
self setNeedsStatusBarAppearanceUpdate];
2302 - (BOOL)prefersStatusBarHidden {
2303 return _flutterPrefersStatusBarHidden;
2306 #pragma mark - Platform views
2308 - (std::shared_ptr<flutter::PlatformViewsController>&)platformViewsController {
2309 return [_engine.get() platformViewsController];
2313 return _engine.get().binaryMessenger;
2316 #pragma mark - FlutterBinaryMessenger
2318 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2319 [_engine.get().binaryMessenger sendOnChannel:channel message:message];
2322 - (void)sendOnChannel:(NSString*)channel
2323 message:(NSData*)message
2325 NSAssert(channel,
@"The channel must not be null");
2326 [_engine.get().binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2330 return [_engine.get().binaryMessenger makeBackgroundTaskQueue];
2334 binaryMessageHandler:
2336 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2340 setMessageHandlerOnChannel:(NSString*)channel
2343 NSAssert(channel,
@"The channel must not be null");
2344 return [_engine.get().binaryMessenger setMessageHandlerOnChannel:channel
2345 binaryMessageHandler:handler
2346 taskQueue:taskQueue];
2350 [_engine.get().binaryMessenger cleanUpConnection:connection];
2353 #pragma mark - FlutterTextureRegistry
2356 return [_engine.get().textureRegistry registerTexture:texture];
2359 - (void)unregisterTexture:(int64_t)textureId {
2360 [_engine.get().textureRegistry unregisterTexture:textureId];
2363 - (void)textureFrameAvailable:(int64_t)textureId {
2364 [_engine.get().textureRegistry textureFrameAvailable:textureId];
2367 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2371 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2375 - (id<FlutterPluginRegistry>)pluginRegistry {
2379 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2380 return UIAccessibilityIsVoiceOverRunning();
2383 #pragma mark - FlutterPluginRegistry
2386 return [_engine.get() registrarForPlugin:pluginKey];
2389 - (BOOL)hasPlugin:(NSString*)pluginKey {
2390 return [_engine.get() hasPlugin:pluginKey];
2393 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2394 return [_engine.get() valuePublishedByPlugin:pluginKey];
2397 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2399 completion:(
void (^)(
void))completion {
2400 self.isPresentingViewControllerAnimating = YES;
2401 [
super presentViewController:viewControllerToPresent
2404 self.isPresentingViewControllerAnimating = NO;
2411 - (BOOL)isPresentingViewController {
2412 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2415 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2416 API_AVAILABLE(ios(13.4)) {
2417 CGPoint location = [gestureRecognizer locationInView:self.view];
2418 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2420 flutter::PointerData pointer_data;
2421 pointer_data.Clear();
2425 return pointer_data;
2428 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2429 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2430 API_AVAILABLE(ios(13.4)) {
2434 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2435 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2436 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2437 event.type == UIEventTypeScroll) {
2439 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2440 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2441 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2442 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2443 pointer_data.view_id =
self.viewIdentifier;
2448 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2449 packet->SetPointerData(0, pointer_data);
2450 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2458 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2461 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2462 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2463 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2464 pointer_data.view_id =
self.viewIdentifier;
2466 switch (_hoverGestureRecognizer.state) {
2467 case UIGestureRecognizerStateBegan:
2468 pointer_data.change = flutter::PointerData::Change::kAdd;
2470 case UIGestureRecognizerStateChanged:
2471 pointer_data.change = flutter::PointerData::Change::kHover;
2473 case UIGestureRecognizerStateEnded:
2474 case UIGestureRecognizerStateCancelled:
2475 pointer_data.change = flutter::PointerData::Change::kRemove;
2480 pointer_data.change = flutter::PointerData::Change::kHover;
2484 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2485 BOOL isRunningOnMac = NO;
2486 if (@available(iOS 14.0, *)) {
2490 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2497 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2498 packet->SetPointerData(0, pointer_data);
2499 flutter::PointerData inertia_cancel = pointer_data;
2500 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2501 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2502 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2503 inertia_cancel.view_id =
self.viewIdentifier;
2504 packet->SetPointerData(1, inertia_cancel);
2505 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2508 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2509 packet->SetPointerData(0, pointer_data);
2510 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2514 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2515 CGPoint translation = [recognizer translationInView:self.view];
2516 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2518 translation.x *= scale;
2519 translation.y *= scale;
2521 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2522 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2523 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2524 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2525 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2526 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2527 pointer_data.view_id =
self.viewIdentifier;
2533 if (recognizer.state != UIGestureRecognizerStateEnded) {
2539 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2540 packet->SetPointerData(0, pointer_data);
2541 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2544 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2545 CGPoint translation = [recognizer translationInView:self.view];
2546 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2548 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2549 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2550 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2551 pointer_data.view_id =
self.viewIdentifier;
2552 switch (recognizer.state) {
2553 case UIGestureRecognizerStateBegan:
2554 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2556 case UIGestureRecognizerStateChanged:
2557 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2558 pointer_data.pan_x = translation.x * scale;
2559 pointer_data.pan_y = translation.y * scale;
2560 pointer_data.pan_delta_x = 0;
2561 pointer_data.pan_delta_y = 0;
2562 pointer_data.scale = 1;
2564 case UIGestureRecognizerStateEnded:
2565 case UIGestureRecognizerStateCancelled:
2567 [[NSProcessInfo processInfo] systemUptime] +
2578 [[NSProcessInfo processInfo] systemUptime] +
2579 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2580 [recognizer velocityInView:self.view].y))) -
2582 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2586 NSAssert(
false,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2587 (
long)recognizer.state);
2591 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2592 packet->SetPointerData(0, pointer_data);
2593 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2596 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2597 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2598 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2599 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2600 pointer_data.view_id =
self.viewIdentifier;
2601 switch (recognizer.state) {
2602 case UIGestureRecognizerStateBegan:
2603 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2605 case UIGestureRecognizerStateChanged:
2606 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2607 pointer_data.scale = recognizer.scale;
2608 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2610 case UIGestureRecognizerStateEnded:
2611 case UIGestureRecognizerStateCancelled:
2612 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2616 NSAssert(
false,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2617 (
long)recognizer.state);
2621 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2622 packet->SetPointerData(0, pointer_data);
2623 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2626 #pragma mark - State Restoration
2628 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2629 NSData* restorationData = [[_engine.get() restorationPlugin] restorationData];
2630 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2631 length:restorationData.length
2632 forKey:kFlutterRestorationStateAppData];
2633 [
super encodeRestorableStateWithCoder:coder];
2636 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2637 NSUInteger restorationDataLength;
2638 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2639 returnedLength:&restorationDataLength];
2640 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2641 [[_engine.get() restorationPlugin] setRestorationData:restorationData];
2645 return [_engine.get() restorationPlugin];