5 #define FML_USED_ON_EMBEDDER
12 #include "flutter/fml/memory/weak_ptr.h"
13 #include "flutter/fml/message_loop.h"
14 #include "flutter/fml/platform/darwin/platform_version.h"
15 #include "flutter/fml/platform/darwin/scoped_nsobject.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"
44 @"FlutterViewControllerHideHomeIndicator";
46 @"FlutterViewControllerShowHomeIndicator";
62 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
63 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
68 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
73 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
74 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
75 @property(nonatomic, retain)
VSyncClient* keyboardAnimationVSyncClient;
76 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
77 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
78 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
86 @property(nonatomic, retain)
VSyncClient* touchRateCorrectionVSyncClient;
92 @property(nonatomic, retain)
93 UIHoverGestureRecognizer* hoverGestureRecognizer
API_AVAILABLE(ios(13.4));
95 @property(nonatomic, retain)
96 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
98 @property(nonatomic, retain)
99 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
101 @property(nonatomic, retain)
102 UIPinchGestureRecognizer* pinchGestureRecognizer
API_AVAILABLE(ios(13.4));
104 @property(nonatomic, retain)
105 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
110 - (void)addInternalPlugins;
111 - (void)deregisterNotifications;
115 std::unique_ptr<fml::WeakPtrFactory<FlutterViewController>>
_weakFactory;
116 fml::scoped_nsobject<FlutterEngine>
_engine;
149 @synthesize displayingFlutterUI = _displayingFlutterUI;
150 @synthesize prefersStatusBarHidden = _flutterPrefersStatusBarHidden;
152 #pragma mark - Manage and override all designated initializers
155 nibName:(nullable NSString*)nibName
156 bundle:(nullable NSBundle*)nibBundle {
157 NSAssert(
engine != nil,
@"Engine is required");
158 self = [
super initWithNibName:nibName bundle:nibBundle];
161 if (
engine.viewController) {
162 FML_LOG(ERROR) <<
"The supplied FlutterEngine " << [[engine description] UTF8String]
163 <<
" is already used with FlutterViewController instance "
164 << [[engine.viewController description] UTF8String]
165 <<
". One instance of the FlutterEngine can only be attached to one "
166 "FlutterViewController at a time. Set FlutterEngine.viewController "
167 "to nil before attaching it to another FlutterViewController.";
172 opaque:
self.isViewOpaque
173 enableWideGamut:
engine.project.isWideGamutEnabled]);
174 _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(
self);
177 [
self performCommonViewControllerInitialization];
178 [engine setViewController:self];
185 nibName:(NSString*)nibName
186 bundle:(NSBundle*)nibBundle {
187 self = [
super initWithNibName:nibName bundle:nibBundle];
189 [
self sharedSetupWithProject:project initialRoute:nil];
196 initialRoute:(NSString*)initialRoute
197 nibName:(NSString*)nibName
198 bundle:(NSBundle*)nibBundle {
199 self = [
super initWithNibName:nibName bundle:nibBundle];
201 [
self sharedSetupWithProject:project initialRoute:initialRoute];
207 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
208 return [
self initWithProject:nil nibName:nil bundle:nil];
212 self = [
super initWithCoder:aDecoder];
216 - (void)awakeFromNib {
217 [
super awakeFromNib];
219 [
self sharedSetupWithProject:nil initialRoute:nil];
223 - (instancetype)init {
224 return [
self initWithProject:nil nibName:nil bundle:nil];
228 initialRoute:(nullable NSString*)initialRoute {
235 _weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(
self);
237 initWithName:@"io.flutter"
239 allowHeadlessExecution:self.engineAllowHeadlessExecution
240 restorationEnabled:[
self restorationIdentifier] != nil]};
249 opaque:
self.isViewOpaque
251 [_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
254 [
self loadDefaultSplashScreenView];
255 [
self performCommonViewControllerInitialization];
258 - (BOOL)isViewOpaque {
262 - (void)setViewOpaque:(BOOL)value {
266 [_flutterView.get().layer setNeedsLayout];
270 #pragma mark - Common view controller initialization tasks
272 - (void)performCommonViewControllerInitialization {
282 [
self setUpNotificationCenterObservers];
289 - (fml::WeakPtr<FlutterViewController>)getWeakPtr {
293 - (void)setUpNotificationCenterObservers {
294 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
295 [center addObserver:self
296 selector:@selector(onOrientationPreferencesUpdated:)
297 name:@(flutter::kOrientationUpdateNotificationName)
300 [center addObserver:self
301 selector:@selector(onPreferredStatusBarStyleUpdated:)
302 name:@(flutter::kOverlayStyleUpdateNotificationName)
305 #if APPLICATION_EXTENSION_API_ONLY
306 if (@available(iOS 13.0, *)) {
307 [
self setUpSceneLifecycleNotifications:center];
309 [
self setUpApplicationLifecycleNotifications:center];
312 [
self setUpApplicationLifecycleNotifications:center];
315 [center addObserver:self
316 selector:@selector(keyboardWillChangeFrame:)
317 name:UIKeyboardWillChangeFrameNotification
320 [center addObserver:self
321 selector:@selector(keyboardWillShowNotification:)
322 name:UIKeyboardWillShowNotification
325 [center addObserver:self
326 selector:@selector(keyboardWillBeHidden:)
327 name:UIKeyboardWillHideNotification
330 [center addObserver:self
331 selector:@selector(onAccessibilityStatusChanged:)
332 name:UIAccessibilityVoiceOverStatusDidChangeNotification
335 [center addObserver:self
336 selector:@selector(onAccessibilityStatusChanged:)
337 name:UIAccessibilitySwitchControlStatusDidChangeNotification
340 [center addObserver:self
341 selector:@selector(onAccessibilityStatusChanged:)
342 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
345 [center addObserver:self
346 selector:@selector(onAccessibilityStatusChanged:)
347 name:UIAccessibilityInvertColorsStatusDidChangeNotification
350 [center addObserver:self
351 selector:@selector(onAccessibilityStatusChanged:)
352 name:UIAccessibilityReduceMotionStatusDidChangeNotification
355 [center addObserver:self
356 selector:@selector(onAccessibilityStatusChanged:)
357 name:UIAccessibilityBoldTextStatusDidChangeNotification
360 [center addObserver:self
361 selector:@selector(onAccessibilityStatusChanged:)
362 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
365 if (@available(iOS 13.0, *)) {
366 [center addObserver:self
367 selector:@selector(onAccessibilityStatusChanged:)
368 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
372 [center addObserver:self
373 selector:@selector(onUserSettingsChanged:)
374 name:UIContentSizeCategoryDidChangeNotification
377 [center addObserver:self
378 selector:@selector(onHideHomeIndicatorNotification:)
379 name:FlutterViewControllerHideHomeIndicator
382 [center addObserver:self
383 selector:@selector(onShowHomeIndicatorNotification:)
384 name:FlutterViewControllerShowHomeIndicator
388 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
389 [center addObserver:self
390 selector:@selector(sceneBecameActive:)
391 name:UISceneDidActivateNotification
394 [center addObserver:self
395 selector:@selector(sceneWillResignActive:)
396 name:UISceneWillDeactivateNotification
399 [center addObserver:self
400 selector:@selector(sceneWillDisconnect:)
401 name:UISceneDidDisconnectNotification
404 [center addObserver:self
405 selector:@selector(sceneDidEnterBackground:)
406 name:UISceneDidEnterBackgroundNotification
409 [center addObserver:self
410 selector:@selector(sceneWillEnterForeground:)
411 name:UISceneWillEnterForegroundNotification
415 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
416 [center addObserver:self
417 selector:@selector(applicationBecameActive:)
418 name:UIApplicationDidBecomeActiveNotification
421 [center addObserver:self
422 selector:@selector(applicationWillResignActive:)
423 name:UIApplicationWillResignActiveNotification
426 [center addObserver:self
427 selector:@selector(applicationWillTerminate:)
428 name:UIApplicationWillTerminateNotification
431 [center addObserver:self
432 selector:@selector(applicationDidEnterBackground:)
433 name:UIApplicationDidEnterBackgroundNotification
436 [center addObserver:self
437 selector:@selector(applicationWillEnterForeground:)
438 name:UIApplicationWillEnterForegroundNotification
442 - (void)setInitialRoute:(NSString*)route {
443 [[_engine.get() navigationChannel] invokeMethod:@"setInitialRoute" arguments:route];
447 [[_engine.get() navigationChannel] invokeMethod:@"popRoute" arguments:nil];
450 - (void)pushRoute:(NSString*)route {
451 [[_engine.get() navigationChannel] invokeMethod:@"pushRoute" arguments:route];
454 #pragma mark - Loading the view
456 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
458 return existing_view;
461 auto placeholder = [[[UIView alloc] init] autorelease];
463 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
464 if (@available(iOS 13.0, *)) {
465 placeholder.backgroundColor = UIColor.systemBackgroundColor;
467 placeholder.backgroundColor = UIColor.whiteColor;
469 placeholder.autoresizesSubviews = YES;
474 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
475 auto messageLabel = [[[UILabel alloc] init] autorelease];
476 messageLabel.numberOfLines = 0u;
477 messageLabel.textAlignment = NSTextAlignmentCenter;
478 messageLabel.autoresizingMask =
479 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
481 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
482 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
483 @"modes to enable launching from the home screen.";
484 [placeholder addSubview:messageLabel];
492 self.view.multipleTouchEnabled = YES;
493 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
495 [
self installSplashScreenViewIfNecessary];
496 UIScrollView* scrollView = [[UIScrollView alloc] init];
497 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
499 scrollView.backgroundColor = UIColor.whiteColor;
500 scrollView.delegate =
self;
505 [
self.view addSubview:scrollView];
509 - (
flutter::PointerData)generatePointerDataForFake {
510 flutter::PointerData pointer_data;
511 pointer_data.Clear();
512 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
520 static void SendFakeTouchEvent(UIScreen* screen,
523 flutter::PointerData::Change change) {
524 const CGFloat scale = screen.scale;
525 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
526 pointer_data.physical_x = location.x * scale;
527 pointer_data.physical_y = location.y * scale;
528 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
529 pointer_data.change = change;
530 packet->SetPointerData(0, pointer_data);
531 [engine dispatchPointerDataPacket:std::move(packet)];
534 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
538 CGPoint statusBarPoint = CGPointZero;
539 UIScreen* screen = [
self flutterScreenIfViewLoaded];
541 SendFakeTouchEvent(screen,
_engine.get(), statusBarPoint, flutter::PointerData::Change::kDown);
542 SendFakeTouchEvent(screen,
_engine.get(), statusBarPoint, flutter::PointerData::Change::kUp);
547 #pragma mark - Managing launch views
549 - (void)installSplashScreenViewIfNecessary {
552 if (
_splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
553 [_splashScreenView.get() removeFromSuperview];
559 UIView* splashScreenView =
self.splashScreenView;
560 if (splashScreenView == nil) {
563 splashScreenView.frame =
self.view.bounds;
564 [
self.view addSubview:splashScreenView];
567 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
571 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
572 if (_displayingFlutterUI != displayingFlutterUI) {
573 if (displayingFlutterUI == YES) {
574 if (!
self.viewIfLoaded.window) {
578 [
self willChangeValueForKey:@"displayingFlutterUI"];
579 _displayingFlutterUI = displayingFlutterUI;
580 [
self didChangeValueForKey:@"displayingFlutterUI"];
584 - (void)callViewRenderedCallback {
585 self.displayingFlutterUI = YES;
592 - (void)removeSplashScreenView:(dispatch_block_t _Nullable)onComplete {
594 UIView* splashScreen = [_splashScreenView.get() retain];
596 [UIView animateWithDuration:0.2
598 splashScreen.alpha = 0;
600 completion:^(BOOL finished) {
601 [splashScreen removeFromSuperview];
602 [splashScreen release];
609 - (void)installFirstFrameCallback {
614 fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = [_engine.get() platformView];
615 if (!weakPlatformView) {
620 weakPlatformView->SetNextFrameCallback([weakSelf = [
self getWeakPtr],
621 platformTaskRunner = [
_engine.get() platformTaskRunner],
622 rasterTaskRunner = [
_engine.get() rasterTaskRunner]]() {
623 FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread());
625 platformTaskRunner->PostTask([weakSelf]() {
627 fml::scoped_nsobject<FlutterViewController> flutterViewController(
628 [(FlutterViewController*)weakSelf.get() retain]);
629 if (flutterViewController) {
630 if (flutterViewController.get()->_splashScreenView) {
631 [flutterViewController removeSplashScreenView:^{
632 [flutterViewController callViewRenderedCallback];
635 [flutterViewController callViewRenderedCallback];
643 #pragma mark - Properties
645 - (UIView*)splashScreenView {
652 - (UIView*)keyboardAnimationView {
656 - (SpringAnimation*)keyboardSpringAnimation {
660 - (BOOL)loadDefaultSplashScreenView {
661 NSString* launchscreenName =
662 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
663 if (launchscreenName == nil) {
666 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
668 splashView = [
self splashScreenFromXib:launchscreenName];
673 self.splashScreenView = splashView;
677 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
678 UIStoryboard* storyboard = nil;
680 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
681 }
@catch (NSException* exception) {
685 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
686 return splashScreenViewController.view;
691 - (UIView*)splashScreenFromXib:(NSString*)name {
692 NSArray* objects = nil;
694 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
695 }
@catch (NSException* exception) {
698 if ([objects count] != 0) {
699 UIView* view = [objects objectAtIndex:0];
705 - (void)setSplashScreenView:(UIView*)view {
709 [
self removeSplashScreenView:nil];
716 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
719 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
723 #pragma mark - Surface creation and teardown updates
725 - (void)surfaceUpdated:(BOOL)appeared {
733 [
self installFirstFrameCallback];
734 [_engine.get() platformViewsController]->SetFlutterView(
_flutterView.get());
735 [_engine.get() platformViewsController]->SetFlutterViewController(
self);
736 [_engine.get() iosPlatformView]->NotifyCreated();
738 self.displayingFlutterUI = NO;
739 [_engine.get() iosPlatformView]->NotifyDestroyed();
740 [_engine.get() platformViewsController]->SetFlutterView(
nullptr);
741 [_engine.get() platformViewsController]->SetFlutterViewController(
nullptr);
745 #pragma mark - UIViewController lifecycle notifications
747 - (void)viewDidLoad {
748 TRACE_EVENT0(
"flutter",
"viewDidLoad");
751 [_engine.get() launchEngine:nil libraryURI:nil entrypointArgs:nil];
752 [_engine.get() setViewController:self];
755 [_engine.get() attachView];
759 [
self addInternalPlugins];
762 [
self createTouchRateCorrectionVSyncClientIfNeeded];
764 if (@available(iOS 13.4, *)) {
765 _hoverGestureRecognizer =
766 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
767 _hoverGestureRecognizer.delegate =
self;
768 [_flutterView.get() addGestureRecognizer:_hoverGestureRecognizer];
770 _discreteScrollingPanGestureRecognizer =
771 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
772 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
777 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
778 _discreteScrollingPanGestureRecognizer.delegate =
self;
779 [_flutterView.get() addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
780 _continuousScrollingPanGestureRecognizer =
781 [[UIPanGestureRecognizer alloc] initWithTarget:self
782 action:@selector(continuousScrollEvent:)];
783 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
784 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
785 _continuousScrollingPanGestureRecognizer.delegate =
self;
786 [_flutterView.get() addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
787 _pinchGestureRecognizer =
788 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
789 _pinchGestureRecognizer.allowedTouchTypes = @[];
790 _pinchGestureRecognizer.delegate =
self;
791 [_flutterView.get() addGestureRecognizer:_pinchGestureRecognizer];
792 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
793 _rotationGestureRecognizer.allowedTouchTypes = @[];
794 _rotationGestureRecognizer.delegate =
self;
795 [_flutterView.get() addGestureRecognizer:_rotationGestureRecognizer];
801 - (void)addInternalPlugins {
803 fml::WeakPtr<FlutterViewController> weakSelf = [
self getWeakPtr];
805 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
807 [weakSelf.get()->_engine.get() sendKeyEvent:event callback:callback userData:userData];
811 initWithSendEvent:sendEvent] autorelease]];
813 initWithChannel:self.engine.keyEventChannel] autorelease];
814 [
self.keyboardManager addPrimaryResponder:responder];
817 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
824 - (void)removeInternalPlugins {
825 self.keyboardManager = nil;
828 - (void)viewWillAppear:(BOOL)animated {
829 TRACE_EVENT0(
"flutter",
"viewWillAppear");
832 [
self onUserSettingsChanged:nil];
837 [
self surfaceUpdated:YES];
839 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
840 [[_engine.get() restorationPlugin] markRestorationComplete];
843 [
super viewWillAppear:animated];
846 - (void)viewDidAppear:(BOOL)animated {
847 TRACE_EVENT0(
"flutter",
"viewDidAppear");
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 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.resumed"];
864 [
super viewDidAppear:animated];
867 - (void)viewWillDisappear:(BOOL)animated {
868 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
870 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
872 [
super viewWillDisappear:animated];
875 - (void)viewDidDisappear:(BOOL)animated {
876 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
878 [
self invalidateKeyboardAnimationVSyncClient];
879 [
self ensureViewportMetricsIsCorrect];
880 [
self surfaceUpdated:NO];
881 [[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.paused"];
882 [
self flushOngoingTouches];
883 [_engine.get() 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) {
907 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
908 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
909 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
910 dispatch_get_main_queue(), ^{
913 _shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
914 [
self updateViewportMetricsIfNeeded];
918 - (void)flushOngoingTouches {
920 auto packet = std::make_unique<flutter::PointerDataPacket>(
_ongoingTouches.get().count);
921 size_t pointer_index = 0;
926 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
928 pointer_data.change = flutter::PointerData::Change::kCancel;
929 pointer_data.device = device.longLongValue;
930 pointer_data.pointer_identifier = 0;
933 pointer_data.physical_x = 0;
934 pointer_data.physical_y = 0;
935 pointer_data.physical_delta_x = 0.0;
936 pointer_data.physical_delta_y = 0.0;
937 pointer_data.pressure = 1.0;
938 pointer_data.pressure_max = 1.0;
940 packet->SetPointerData(pointer_index++, pointer_data);
943 [_ongoingTouches removeAllObjects];
944 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
948 - (void)deregisterNotifications {
949 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
952 [[NSNotificationCenter defaultCenter] removeObserver:self];
960 [
self removeInternalPlugins];
961 [
self deregisterNotifications];
963 [
self invalidateKeyboardAnimationVSyncClient];
964 [
self invalidateTouchRateCorrectionVSyncClient];
966 _hoverGestureRecognizer.delegate = nil;
967 [_hoverGestureRecognizer release];
968 _discreteScrollingPanGestureRecognizer.delegate = nil;
969 [_discreteScrollingPanGestureRecognizer release];
970 _continuousScrollingPanGestureRecognizer.delegate = nil;
971 [_continuousScrollingPanGestureRecognizer release];
972 _pinchGestureRecognizer.delegate = nil;
973 [_pinchGestureRecognizer release];
974 _rotationGestureRecognizer.delegate = nil;
975 [_rotationGestureRecognizer release];
979 #pragma mark - Application lifecycle notifications
981 - (void)applicationBecameActive:(NSNotification*)notification {
982 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
983 [
self appOrSceneBecameActive];
986 - (void)applicationWillResignActive:(NSNotification*)notification {
987 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
988 [
self appOrSceneWillResignActive];
991 - (void)applicationWillTerminate:(NSNotification*)notification {
992 [
self appOrSceneWillTerminate];
995 - (void)applicationDidEnterBackground:(NSNotification*)notification {
996 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
997 [
self appOrSceneDidEnterBackground];
1000 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1001 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1002 [
self appOrSceneWillEnterForeground];
1005 #pragma mark - Scene lifecycle notifications
1007 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1008 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1009 [
self appOrSceneBecameActive];
1012 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1013 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1014 [
self appOrSceneWillResignActive];
1017 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1018 [
self appOrSceneWillTerminate];
1021 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1022 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1023 [
self appOrSceneDidEnterBackground];
1026 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1027 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1028 [
self appOrSceneWillEnterForeground];
1031 #pragma mark - Lifecycle shared
1033 - (void)appOrSceneBecameActive {
1034 self.isKeyboardInOrTransitioningFromBackground = NO;
1036 [
self surfaceUpdated:YES];
1038 [
self performSelector:@selector(goToApplicationLifecycle:)
1039 withObject:@"AppLifecycleState.resumed"
1043 - (void)appOrSceneWillResignActive {
1044 [NSObject cancelPreviousPerformRequestsWithTarget:self
1045 selector:@selector(goToApplicationLifecycle:)
1046 object:@"AppLifecycleState.resumed"];
1047 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1050 - (void)appOrSceneWillTerminate {
1051 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1052 [
self.engine destroyContext];
1055 - (void)appOrSceneDidEnterBackground {
1056 self.isKeyboardInOrTransitioningFromBackground = YES;
1057 [
self surfaceUpdated:NO];
1058 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1061 - (void)appOrSceneWillEnterForeground {
1062 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1066 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1069 if (
self.viewIfLoaded.window) {
1070 [[_engine.get() lifecycleChannel] sendMessage:state];
1074 #pragma mark - Touch event handling
1076 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1078 case UITouchPhaseBegan:
1079 return flutter::PointerData::Change::kDown;
1080 case UITouchPhaseMoved:
1081 case UITouchPhaseStationary:
1084 return flutter::PointerData::Change::kMove;
1085 case UITouchPhaseEnded:
1086 return flutter::PointerData::Change::kUp;
1087 case UITouchPhaseCancelled:
1088 return flutter::PointerData::Change::kCancel;
1091 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1095 return flutter::PointerData::Change::kCancel;
1098 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1099 switch (touch.type) {
1100 case UITouchTypeDirect:
1101 case UITouchTypeIndirect:
1102 return flutter::PointerData::DeviceKind::kTouch;
1103 case UITouchTypeStylus:
1104 return flutter::PointerData::DeviceKind::kStylus;
1105 case UITouchTypeIndirectPointer:
1106 return flutter::PointerData::DeviceKind::kMouse;
1108 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1112 return flutter::PointerData::DeviceKind::kTouch;
1119 - (void)dispatchTouches:(NSSet*)touches
1120 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1121 event:(UIEvent*)event {
1146 NSUInteger touches_to_remove_count = 0;
1147 for (UITouch* touch in touches) {
1148 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1149 touches_to_remove_count++;
1154 [
self triggerTouchRateCorrectionIfNeeded:touches];
1156 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1158 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1160 size_t pointer_index = 0;
1162 for (UITouch* touch in touches) {
1163 CGPoint windowCoordinates = [touch locationInView:self.view];
1165 flutter::PointerData pointer_data;
1166 pointer_data.Clear();
1171 pointer_data.change = overridden_change !=
nullptr
1172 ? *overridden_change
1173 : PointerDataChangeFromUITouchPhase(touch.phase);
1175 pointer_data.kind = DeviceKindFromTouchType(touch);
1177 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1180 pointer_data.pointer_identifier = 0;
1182 pointer_data.physical_x = windowCoordinates.x * scale;
1183 pointer_data.physical_y = windowCoordinates.y * scale;
1186 pointer_data.physical_delta_x = 0.0;
1187 pointer_data.physical_delta_y = 0.0;
1189 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1192 switch (pointer_data.change) {
1193 case flutter::PointerData::Change::kDown:
1194 [_ongoingTouches addObject:deviceKey];
1196 case flutter::PointerData::Change::kCancel:
1197 case flutter::PointerData::Change::kUp:
1198 [_ongoingTouches removeObject:deviceKey];
1200 case flutter::PointerData::Change::kHover:
1201 case flutter::PointerData::Change::kMove:
1204 case flutter::PointerData::Change::kAdd:
1205 case flutter::PointerData::Change::kRemove:
1208 case flutter::PointerData::Change::kPanZoomStart:
1209 case flutter::PointerData::Change::kPanZoomUpdate:
1210 case flutter::PointerData::Change::kPanZoomEnd:
1216 pointer_data.pressure = touch.force;
1217 pointer_data.pressure_max = touch.maximumPossibleForce;
1218 pointer_data.radius_major = touch.majorRadius;
1219 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1220 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1235 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1255 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1257 if (@available(iOS 13.4, *)) {
1258 if (event !=
nullptr) {
1259 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1260 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1262 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1263 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1268 packet->SetPointerData(pointer_index++, pointer_data);
1270 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1271 flutter::PointerData remove_pointer_data = pointer_data;
1272 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1273 packet->SetPointerData(pointer_index++, remove_pointer_data);
1277 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
1280 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1281 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1284 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1285 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1288 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1289 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1292 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1293 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1296 - (void)forceTouchesCancelled:(NSSet*)touches {
1297 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1298 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1301 #pragma mark - Touch events rate correction
1303 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1304 if (_touchRateCorrectionVSyncClient != nil) {
1309 const double epsilon = 0.1;
1310 if (displayRefreshRate < 60.0 + epsilon) {
1318 flutter::Shell& shell = [_engine.get() shell];
1319 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1322 _touchRateCorrectionVSyncClient =
1323 [[
VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
1325 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1328 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1329 if (_touchRateCorrectionVSyncClient == nil) {
1337 BOOL isUserInteracting = NO;
1338 for (UITouch* touch in touches) {
1339 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1340 isUserInteracting = YES;
1346 [_touchRateCorrectionVSyncClient await];
1348 [_touchRateCorrectionVSyncClient pause];
1352 - (void)invalidateTouchRateCorrectionVSyncClient {
1353 [_touchRateCorrectionVSyncClient invalidate];
1354 [_touchRateCorrectionVSyncClient release];
1355 _touchRateCorrectionVSyncClient = nil;
1358 #pragma mark - Handle view resizing
1360 - (void)updateViewportMetricsIfNeeded {
1361 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1365 [_engine.get() updateViewportMetrics:_viewportMetrics];
1369 - (void)viewDidLayoutSubviews {
1370 CGRect viewBounds =
self.view.bounds;
1371 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1374 _scrollView.get().frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1380 [
self setViewportMetricsSize];
1381 [
self setViewportMetricsPaddings];
1382 [
self updateViewportMetricsIfNeeded];
1387 bool applicationOrSceneIsActive = YES;
1388 #if APPLICATION_EXTENSION_API_ONLY
1389 if (@available(iOS 13.0, *)) {
1390 applicationOrSceneIsActive =
self.flutterWindowSceneIfViewLoaded.activationState ==
1391 UISceneActivationStateForegroundActive;
1394 applicationOrSceneIsActive =
1395 [UIApplication sharedApplication].applicationState == UIApplicationStateActive;
1400 if (firstViewBoundsUpdate && applicationOrSceneIsActive &&
_engine) {
1401 [
self surfaceUpdated:YES];
1403 flutter::Shell& shell = [_engine.get() shell];
1404 fml::TimeDelta waitTime =
1405 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1406 fml::TimeDelta::FromMilliseconds(200);
1408 fml::TimeDelta::FromMilliseconds(100);
1410 if (shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded) {
1411 FML_LOG(INFO) <<
"Timeout waiting for the first frame to render. This may happen in "
1412 <<
"unoptimized builds. If this is a release build, you should load a less "
1413 <<
"complex frame to avoid the timeout.";
1418 - (void)viewSafeAreaInsetsDidChange {
1419 [
self setViewportMetricsPaddings];
1420 [
self updateViewportMetricsIfNeeded];
1421 [
super viewSafeAreaInsetsDidChange];
1425 - (void)setViewportMetricsSize {
1426 UIScreen* screen = [
self flutterScreenIfViewLoaded];
1431 CGFloat scale = screen.scale;
1439 - (void)setViewportMetricsPaddings {
1440 UIScreen* screen = [
self flutterScreenIfViewLoaded];
1445 CGFloat scale = screen.scale;
1446 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1447 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1448 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1449 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1452 #pragma mark - Keyboard events
1454 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1459 [
self handleKeyboardNotification:notification];
1462 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1467 [
self handleKeyboardNotification:notification];
1470 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1474 [
self handleKeyboardNotification:notification];
1477 - (void)handleKeyboardNotification:(NSNotification*)notification {
1480 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1484 NSDictionary* info = notification.userInfo;
1485 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1486 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1487 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1488 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1491 if (
self.targetViewInsetBottom == calculatedInset) {
1495 self.targetViewInsetBottom = calculatedInset;
1496 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1503 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1504 BOOL keyboardAnimationIsCompounding =
1505 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1508 self.keyboardAnimationIsShowing = keyboardWillShow;
1510 if (!keyboardAnimationIsCompounding) {
1511 [
self startKeyBoardAnimation:duration];
1512 }
else if ([
self keyboardSpringAnimation]) {
1513 [
self keyboardSpringAnimation].toValue =
self.targetViewInsetBottom;
1517 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1522 if (notification.name == UIKeyboardWillHideNotification) {
1531 NSDictionary* info = notification.userInfo;
1532 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1533 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1534 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1540 if (CGRectIsEmpty(keyboardFrame)) {
1545 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1549 if (@available(iOS 13.0, *)) {
1557 if (
self.isKeyboardInOrTransitioningFromBackground) {
1565 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1566 NSDictionary* info = notification.userInfo;
1570 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1571 if (isLocal && ![isLocal boolValue]) {
1581 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1589 NSDictionary* info = notification.userInfo;
1590 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1592 if (notification.name == UIKeyboardWillHideNotification) {
1593 return FlutterKeyboardModeHidden;
1598 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1599 return FlutterKeyboardModeFloating;
1602 if (CGRectIsEmpty(keyboardFrame)) {
1603 return FlutterKeyboardModeHidden;
1606 CGRect screenRect = [
self flutterScreenIfViewLoaded].bounds;
1607 CGRect adjustedKeyboardFrame = keyboardFrame;
1608 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1609 keyboardFrame:keyboardFrame];
1614 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1615 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1616 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1617 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1619 CGFloat screenHeight = CGRectGetHeight(screenRect);
1620 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1621 if (round(adjustedKeyboardBottom) < screenHeight) {
1622 return FlutterKeyboardModeFloating;
1624 return FlutterKeyboardModeDocked;
1626 return FlutterKeyboardModeHidden;
1629 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1633 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1634 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1635 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1636 CGFloat screenHeight = CGRectGetHeight(screenRect);
1637 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1641 if (screenHeight == keyboardBottom) {
1644 CGRect viewRectRelativeToScreen =
1645 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1646 toCoordinateSpace:[
self flutterScreenIfViewLoaded].coordinateSpace];
1647 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1648 CGFloat offset = screenHeight - viewBottom;
1656 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1658 if (keyboardMode == FlutterKeyboardModeDocked) {
1660 CGRect viewRectRelativeToScreen =
1661 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1662 toCoordinateSpace:[
self flutterScreenIfViewLoaded].coordinateSpace];
1663 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1664 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1669 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
1670 return portionOfKeyboardInView * scale;
1675 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1677 if (
_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1683 if ([
self keyboardAnimationView] == nil) {
1684 UIView* keyboardAnimationView = [[UIView alloc] init];
1685 [keyboardAnimationView setHidden:YES];
1689 if ([
self keyboardAnimationView].superview == nil) {
1690 [
self.view addSubview:[
self keyboardAnimationView]];
1694 [[
self keyboardAnimationView].layer removeAllAnimations];
1697 [
self keyboardAnimationView].frame =
1699 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1700 self.originalViewInsetBottom =
_viewportMetrics.physical_view_inset_bottom;
1703 [
self invalidateKeyboardAnimationVSyncClient];
1705 fml::WeakPtr<FlutterViewController> weakSelf = [
self getWeakPtr];
1707 fml::TimePoint keyboardAnimationTargetTime) {
1711 fml::scoped_nsobject<FlutterViewController> flutterViewController(
1713 if (!flutterViewController) {
1718 if (!flutterViewController.get().isViewLoaded) {
1723 if ([flutterViewController keyboardAnimationView] == nil) {
1728 if (flutterViewController.get().keyboardAnimationVSyncClient == nil) {
1732 if ([flutterViewController keyboardAnimationView].superview == nil) {
1734 [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
1737 if ([flutterViewController keyboardSpringAnimation] == nil) {
1738 if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
1739 flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1740 flutterViewController.get()
1741 .keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1742 [flutterViewController updateViewportMetricsIfNeeded];
1745 fml::TimeDelta timeElapsed =
1746 keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime;
1747 flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1748 [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
1749 [flutterViewController updateViewportMetricsIfNeeded];
1752 [
self setUpKeyboardAnimationVsyncClient:keyboardAnimationCallback];
1753 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1755 [UIView animateWithDuration:duration
1758 [
self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1761 CAAnimation* keyboardAnimation =
1762 [[
self keyboardAnimationView].layer animationForKey:@"position"];
1763 [
self setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1765 completion:^(BOOL finished) {
1766 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1770 [
self invalidateKeyboardAnimationVSyncClient];
1771 [
self removeKeyboardAnimationView];
1772 [
self ensureViewportMetricsIsCorrect];
1777 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1779 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1785 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1787 initWithStiffness:keyboardCASpringAnimation.stiffness
1788 damping:keyboardCASpringAnimation.damping
1789 mass:keyboardCASpringAnimation.mass
1790 initialVelocity:keyboardCASpringAnimation.initialVelocity
1791 fromValue:
self.originalViewInsetBottom
1792 toValue:
self.targetViewInsetBottom]);
1795 - (void)setUpKeyboardAnimationVsyncClient:
1797 if (!keyboardAnimationCallback) {
1800 NSAssert(_keyboardAnimationVSyncClient == nil,
1801 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1804 fml::scoped_nsprotocol<FlutterKeyboardAnimationCallback> animationCallback(
1805 [keyboardAnimationCallback copy]);
1806 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1807 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1808 fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval;
1809 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1810 animationCallback.get()(keyboardAnimationTargetTime);
1814 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner]
1815 callback:uiCallback];
1816 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1817 [_keyboardAnimationVSyncClient await];
1820 - (void)invalidateKeyboardAnimationVSyncClient {
1821 [_keyboardAnimationVSyncClient invalidate];
1822 [_keyboardAnimationVSyncClient release];
1823 _keyboardAnimationVSyncClient = nil;
1826 - (void)removeKeyboardAnimationView {
1827 if ([
self keyboardAnimationView].superview != nil) {
1828 [[
self keyboardAnimationView] removeFromSuperview];
1832 - (void)ensureViewportMetricsIsCorrect {
1833 if (
_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1836 [
self updateViewportMetricsIfNeeded];
1841 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1842 if (@available(iOS 13.4, *)) {
1847 [
self.keyboardManager handlePress:press nextAction:next];
1865 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1866 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1867 if (@available(iOS 13.4, *)) {
1868 for (UIPress* press in presses) {
1870 withEvent:event] autorelease]
1872 [
super pressesBegan:[NSSet setWithObject:press] withEvent:event];
1876 [
super pressesBegan:presses withEvent:event];
1880 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1881 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1882 if (@available(iOS 13.4, *)) {
1883 for (UIPress* press in presses) {
1885 withEvent:event] autorelease]
1887 [
super pressesChanged:[NSSet setWithObject:press] withEvent:event];
1891 [
super pressesChanged:presses withEvent:event];
1895 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1896 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1897 if (@available(iOS 13.4, *)) {
1898 for (UIPress* press in presses) {
1900 withEvent:event] autorelease]
1902 [
super pressesEnded:[NSSet setWithObject:press] withEvent:event];
1906 [
super pressesEnded:presses withEvent:event];
1910 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
1911 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1912 if (@available(iOS 13.4, *)) {
1913 for (UIPress* press in presses) {
1915 withEvent:event] autorelease]
1917 [
super pressesCancelled:[NSSet setWithObject:press] withEvent:event];
1921 [
super pressesCancelled:presses withEvent:event];
1925 #pragma mark - Orientation updates
1927 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1929 dispatch_async(dispatch_get_main_queue(), ^{
1930 NSDictionary* info = notification.userInfo;
1932 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1934 if (update == nil) {
1937 [
self performOrientationUpdate:update.unsignedIntegerValue];
1941 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1942 API_AVAILABLE(ios(16.0)) {
1943 for (UIScene* windowScene in windowScenes) {
1944 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
1945 UIWindowSceneGeometryPreferencesIOS* preference = [[[UIWindowSceneGeometryPreferencesIOS alloc]
1946 initWithInterfaceOrientations:_orientationPreferences] autorelease];
1947 [(UIWindowScene*)windowScene
1948 requestGeometryUpdateWithPreferences:preference
1949 errorHandler:^(NSError* error) {
1950 os_log_error(OS_LOG_DEFAULT,
1951 "Failed to change device orientation: %@", error);
1953 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
1957 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
1961 if (@available(iOS 16.0, *)) {
1962 NSSet<UIScene*>* scenes =
1963 #if APPLICATION_EXTENSION_API_ONLY
1964 self.flutterWindowSceneIfViewLoaded
1965 ? [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded]
1968 [UIApplication.sharedApplication.connectedScenes
1969 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
1970 id scene, NSDictionary* bindings) {
1971 return [scene isKindOfClass:[UIWindowScene class]];
1974 [
self requestGeometryUpdateForWindowScenes:scenes];
1976 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
1977 if (@available(iOS 13.0, *)) {
1978 UIWindowScene* windowScene = [
self flutterWindowSceneIfViewLoaded];
1981 <<
"Accessing the interface orientation when the window scene is unavailable.";
1984 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
1986 #if APPLICATION_EXTENSION_API_ONLY
1987 FML_LOG(ERROR) <<
"Application based status bar orentiation update is not supported in "
1988 "app extension. Orientation: "
1989 << currentInterfaceOrientation;
1991 currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation];
1995 [UIViewController attemptRotationToDeviceOrientation];
2001 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2002 forKey:@"orientation"];
2004 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2005 forKey:@"orientation"];
2007 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2008 forKey:@"orientation"];
2010 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2011 forKey:@"orientation"];
2018 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2019 self.isHomeIndicatorHidden = YES;
2022 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2023 self.isHomeIndicatorHidden = NO;
2026 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2027 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2028 _isHomeIndicatorHidden = hideHomeIndicator;
2029 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2033 - (BOOL)prefersHomeIndicatorAutoHidden {
2034 return self.isHomeIndicatorHidden;
2037 - (BOOL)shouldAutorotate {
2041 - (NSUInteger)supportedInterfaceOrientations {
2045 #pragma mark - Accessibility
2047 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2051 auto platformView = [_engine.get() platformView];
2052 int32_t flags = [
self accessibilityFlags];
2053 #if TARGET_OS_SIMULATOR
2057 platformView->SetSemanticsEnabled(
true);
2058 platformView->SetAccessibilityFeatures(flags);
2060 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2061 bool enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2063 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2065 platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());
2066 platformView->SetAccessibilityFeatures(flags);
2070 - (int32_t)accessibilityFlags {
2072 if (UIAccessibilityIsInvertColorsEnabled()) {
2073 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2075 if (UIAccessibilityIsReduceMotionEnabled()) {
2076 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2078 if (UIAccessibilityIsBoldTextEnabled()) {
2079 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2081 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2082 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2085 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2091 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2092 if (@available(iOS 13, *)) {
2093 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2099 #pragma mark - Set user settings
2101 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2102 [
super traitCollectionDidChange:previousTraitCollection];
2103 [
self onUserSettingsChanged:nil];
2106 - (void)onUserSettingsChanged:(NSNotification*)notification {
2107 [[_engine.get() settingsChannel] sendMessage:@{
2108 @"textScaleFactor" : @([
self textScaleFactor]),
2109 @"alwaysUse24HourFormat" : @([
self isAlwaysUse24HourFormat]),
2110 @"platformBrightness" : [
self brightnessMode],
2111 @"platformContrast" : [
self contrastMode],
2112 @"nativeSpellCheckServiceDefined" : @true
2116 - (CGFloat)textScaleFactor {
2117 #if APPLICATION_EXTENSION_API_ONLY
2118 FML_LOG(WARNING) <<
"Dynamic content size update is not supported in app extension.";
2121 UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory;
2127 const CGFloat xs = 14;
2128 const CGFloat s = 15;
2129 const CGFloat m = 16;
2130 const CGFloat l = 17;
2131 const CGFloat xl = 19;
2132 const CGFloat xxl = 21;
2133 const CGFloat xxxl = 23;
2136 const CGFloat ax1 = 28;
2137 const CGFloat ax2 = 33;
2138 const CGFloat ax3 = 40;
2139 const CGFloat ax4 = 47;
2140 const CGFloat ax5 = 53;
2144 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2146 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2148 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2150 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2152 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2154 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2156 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2158 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2160 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2162 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2164 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2166 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2174 - (BOOL)isAlwaysUse24HourFormat {
2186 NSString* dateFormat = [NSDateFormatter dateFormatFromTemplate:@"j"
2188 locale:[NSLocale currentLocale]];
2189 return [dateFormat rangeOfString:@"a"].location == NSNotFound;
2195 - (NSString*)brightnessMode {
2196 if (@available(iOS 13, *)) {
2197 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2199 if (style == UIUserInterfaceStyleDark) {
2212 - (NSString*)contrastMode {
2213 if (@available(iOS 13, *)) {
2214 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2216 if (contrast == UIAccessibilityContrastHigh) {
2226 #pragma mark - Status bar style
2228 - (UIStatusBarStyle)preferredStatusBarStyle {
2232 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2234 dispatch_async(dispatch_get_main_queue(), ^{
2235 NSDictionary* info = notification.userInfo;
2237 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2239 if (update == nil) {
2243 NSInteger style = update.integerValue;
2247 [
self setNeedsStatusBarAppearanceUpdate];
2252 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2253 if (hidden != _flutterPrefersStatusBarHidden) {
2254 _flutterPrefersStatusBarHidden = hidden;
2255 [
self setNeedsStatusBarAppearanceUpdate];
2259 - (BOOL)prefersStatusBarHidden {
2260 return _flutterPrefersStatusBarHidden;
2263 #pragma mark - Platform views
2265 - (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController {
2266 return [_engine.get() platformViewsController];
2270 return _engine.get().binaryMessenger;
2273 #pragma mark - FlutterBinaryMessenger
2275 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2276 [_engine.get().binaryMessenger sendOnChannel:channel message:message];
2279 - (void)sendOnChannel:(NSString*)channel
2280 message:(NSData*)message
2282 NSAssert(channel,
@"The channel must not be null");
2283 [_engine.get().binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2287 return [_engine.get().binaryMessenger makeBackgroundTaskQueue];
2291 binaryMessageHandler:
2293 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2297 setMessageHandlerOnChannel:(NSString*)channel
2300 NSAssert(channel,
@"The channel must not be null");
2301 return [_engine.get().binaryMessenger setMessageHandlerOnChannel:channel
2302 binaryMessageHandler:handler
2303 taskQueue:taskQueue];
2307 [_engine.get().binaryMessenger cleanUpConnection:connection];
2310 #pragma mark - FlutterTextureRegistry
2313 return [_engine.get().textureRegistry registerTexture:texture];
2316 - (void)unregisterTexture:(int64_t)textureId {
2317 [_engine.get().textureRegistry unregisterTexture:textureId];
2320 - (void)textureFrameAvailable:(int64_t)textureId {
2321 [_engine.get().textureRegistry textureFrameAvailable:textureId];
2324 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2328 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2332 - (
id<FlutterPluginRegistry>)pluginRegistry {
2336 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2337 return UIAccessibilityIsVoiceOverRunning();
2340 #pragma mark - FlutterPluginRegistry
2343 return [_engine.get() registrarForPlugin:pluginKey];
2346 - (BOOL)hasPlugin:(NSString*)pluginKey {
2347 return [_engine.get() hasPlugin:pluginKey];
2350 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2351 return [_engine.get() valuePublishedByPlugin:pluginKey];
2354 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2356 completion:(
void (^)(
void))completion {
2357 self.isPresentingViewControllerAnimating = YES;
2358 [
super presentViewController:viewControllerToPresent
2361 self.isPresentingViewControllerAnimating = NO;
2368 - (BOOL)isPresentingViewController {
2369 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2372 - (
flutter::PointerData)generatePointerDataAtLastMouseLocation API_AVAILABLE(ios(13.4)) {
2373 flutter::PointerData pointer_data;
2374 pointer_data.Clear();
2378 return pointer_data;
2381 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2382 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2383 API_AVAILABLE(ios(13.4)) {
2387 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2388 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2389 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2390 event.type == UIEventTypeScroll) {
2392 flutter::PointerData pointer_data = [
self generatePointerDataAtLastMouseLocation];
2393 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2394 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2395 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2400 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2401 packet->SetPointerData(0, pointer_data);
2402 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2410 - (void)hoverEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2411 CGPoint location = [recognizer locationInView:self.view];
2412 CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2416 flutter::PointerData pointer_data = [
self generatePointerDataAtLastMouseLocation];
2417 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2418 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2420 switch (_hoverGestureRecognizer.state) {
2421 case UIGestureRecognizerStateBegan:
2422 pointer_data.change = flutter::PointerData::Change::kAdd;
2424 case UIGestureRecognizerStateChanged:
2425 pointer_data.change = flutter::PointerData::Change::kHover;
2427 case UIGestureRecognizerStateEnded:
2428 case UIGestureRecognizerStateCancelled:
2429 pointer_data.change = flutter::PointerData::Change::kRemove;
2434 pointer_data.change = flutter::PointerData::Change::kHover;
2438 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2439 BOOL isRunningOnMac = NO;
2440 if (@available(iOS 14.0, *)) {
2444 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2451 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2452 packet->SetPointerData(0, pointer_data);
2453 flutter::PointerData inertia_cancel = pointer_data;
2454 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2455 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2456 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2457 packet->SetPointerData(1, inertia_cancel);
2458 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2461 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2462 packet->SetPointerData(0, pointer_data);
2463 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2467 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2468 CGPoint translation = [recognizer translationInView:self.view];
2469 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2471 translation.x *= scale;
2472 translation.y *= scale;
2474 flutter::PointerData pointer_data = [
self generatePointerDataAtLastMouseLocation];
2475 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2476 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2477 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2478 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2479 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2485 if (recognizer.state != UIGestureRecognizerStateEnded) {
2491 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2492 packet->SetPointerData(0, pointer_data);
2493 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2496 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2497 CGPoint translation = [recognizer translationInView:self.view];
2498 const CGFloat scale = [
self flutterScreenIfViewLoaded].scale;
2500 flutter::PointerData pointer_data = [
self generatePointerDataAtLastMouseLocation];
2501 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2502 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2503 switch (recognizer.state) {
2504 case UIGestureRecognizerStateBegan:
2505 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2507 case UIGestureRecognizerStateChanged:
2508 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2509 pointer_data.pan_x = translation.x * scale;
2510 pointer_data.pan_y = translation.y * scale;
2511 pointer_data.pan_delta_x = 0;
2512 pointer_data.pan_delta_y = 0;
2513 pointer_data.scale = 1;
2515 case UIGestureRecognizerStateEnded:
2516 case UIGestureRecognizerStateCancelled:
2518 [[NSProcessInfo processInfo] systemUptime] +
2529 [[NSProcessInfo processInfo] systemUptime] +
2530 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2531 [recognizer velocityInView:self.view].y))) -
2533 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2537 NSAssert(
false,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2538 (
long)recognizer.state);
2542 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2543 packet->SetPointerData(0, pointer_data);
2544 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2547 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2548 flutter::PointerData pointer_data = [
self generatePointerDataAtLastMouseLocation];
2549 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2550 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2551 switch (recognizer.state) {
2552 case UIGestureRecognizerStateBegan:
2553 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2555 case UIGestureRecognizerStateChanged:
2556 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2557 pointer_data.scale = recognizer.scale;
2558 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2560 case UIGestureRecognizerStateEnded:
2561 case UIGestureRecognizerStateCancelled:
2562 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2566 NSAssert(
false,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2567 (
long)recognizer.state);
2571 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2572 packet->SetPointerData(0, pointer_data);
2573 [_engine.get() dispatchPointerDataPacket:std::move(packet)];
2576 #pragma mark - State Restoration
2578 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2579 NSData* restorationData = [[_engine.get() restorationPlugin] restorationData];
2580 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2581 length:restorationData.length
2582 forKey:kFlutterRestorationStateAppData];
2583 [
super encodeRestorableStateWithCoder:coder];
2586 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2587 NSUInteger restorationDataLength;
2588 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2589 returnedLength:&restorationDataLength];
2590 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2591 [[_engine.get() restorationPlugin] setRestorationData:restorationData];
2595 return [_engine.get() restorationPlugin];