5 #define FML_USED_ON_EMBEDDER
12 #include "flutter/common/constants.h"
13 #include "flutter/fml/memory/weak_ptr.h"
14 #include "flutter/fml/message_loop.h"
15 #include "flutter/fml/platform/darwin/platform_version.h"
16 #include "flutter/runtime/ptrace_check.h"
17 #include "flutter/shell/common/thread_host.h"
18 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
37 #import "flutter/shell/platform/embedder/embedder.h"
38 #import "flutter/third_party/spring_animation/spring_animation.h"
50 @"FlutterViewControllerHideHomeIndicator";
52 @"FlutterViewControllerShowHomeIndicator";
70 @property(nonatomic, readonly) int64_t viewIdentifier;
75 @property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
77 @property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
78 @property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
79 @property(nonatomic, assign) BOOL initialized;
80 @property(nonatomic, assign) BOOL engineNeedsLaunch;
83 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
84 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
87 @property(nonatomic, assign) BOOL flutterPrefersStatusBarHidden;
89 @property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
94 @property(nonatomic, strong) UIScrollView* scrollView;
95 @property(nonatomic, strong) UIView* keyboardAnimationView;
96 @property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation;
101 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
106 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
107 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
108 @property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
109 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
110 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
111 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
114 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
122 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
130 @property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
136 @property(nonatomic, strong)
139 @property(nonatomic, strong)
140 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
142 @property(nonatomic, strong)
143 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
145 @property(nonatomic, strong)
148 @property(nonatomic, strong)
149 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
152 - (void)addInternalPlugins;
153 - (void)deregisterNotifications;
156 - (void)onFirstFrameRendered;
159 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime;
163 flutter::ViewportMetrics _viewportMetrics;
168 @synthesize viewOpaque = _viewOpaque;
169 @synthesize displayingFlutterUI = _displayingFlutterUI;
174 @dynamic viewIdentifier;
176 #pragma mark - Manage and override all designated initializers
179 nibName:(nullable NSString*)nibName
180 bundle:(nullable NSBundle*)nibBundle {
181 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
182 self = [
super initWithNibName:nibName bundle:nibBundle];
185 if (
engine.viewController) {
186 NSString* errorMessage =
187 [NSString stringWithFormat:
188 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
189 "instance %@. One instance of the FlutterEngine can only be attached to "
190 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
191 "nil before attaching it to another FlutterViewController.",
192 engine.description, engine.viewController.description];
193 [FlutterLogger logError:errorMessage];
196 _engineNeedsLaunch = NO;
197 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
198 opaque:self.isViewOpaque
199 enableWideGamut:engine.project.isWideGamutEnabled];
200 _ongoingTouches = [[NSMutableSet alloc] init];
204 [
self performCommonViewControllerInitialization];
205 [engine setViewController:self];
212 nibName:(NSString*)nibName
213 bundle:(NSBundle*)nibBundle {
214 self = [
super initWithNibName:nibName bundle:nibBundle];
218 [
self sharedSetupWithProject:project initialRoute:nil];
225 initialRoute:(NSString*)initialRoute
226 nibName:(NSString*)nibName
227 bundle:(NSBundle*)nibBundle {
228 self = [
super initWithNibName:nibName bundle:nibBundle];
232 [
self sharedSetupWithProject:project initialRoute:initialRoute];
238 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
239 return [
self initWithProject:nil nibName:nil bundle:nil];
243 self = [
super initWithCoder:aDecoder];
247 - (void)awakeFromNib {
248 [
super awakeFromNib];
250 [
self sharedSetupWithProject:nil initialRoute:nil];
254 - (instancetype)init {
255 return [
self initWithProject:nil nibName:nil bundle:nil];
259 initialRoute:(nullable NSString*)initialRoute {
262 if ([appDelegate respondsToSelector:
@selector(takeLaunchEngine)]) {
267 engine = [appDelegate takeLaunchEngine];
272 [appDelegate takeLaunchEngine];
284 allowHeadlessExecution:self.engineAllowHeadlessExecution
285 restorationEnabled:self.restorationIdentifier != nil];
293 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
295 enableWideGamut:engine.project.isWideGamutEnabled];
296 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
297 _engineNeedsLaunch = YES;
298 _ongoingTouches = [[NSMutableSet alloc] init];
302 [
self loadDefaultSplashScreenView];
303 [
self performCommonViewControllerInitialization];
306 respondsToSelector:
@selector(pluginRegistrant)]) {
307 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
309 [pluginRegistrant registerWithRegistry:self];
313 - (BOOL)isViewOpaque {
317 - (void)setViewOpaque:(BOOL)value {
319 if (
self.flutterView.layer.opaque != value) {
320 self.flutterView.layer.opaque = value;
321 [
self.flutterView.layer setNeedsLayout];
325 #pragma mark - Common view controller initialization tasks
327 - (void)performCommonViewControllerInitialization {
333 _orientationPreferences = UIInterfaceOrientationMaskAll;
334 _statusBarStyle = UIStatusBarStyleDefault;
338 [
self setUpNotificationCenterObservers];
341 - (void)setUpNotificationCenterObservers {
342 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
343 [center addObserver:self
344 selector:@selector(onOrientationPreferencesUpdated:)
345 name:@(flutter::kOrientationUpdateNotificationName)
348 [center addObserver:self
349 selector:@selector(onPreferredStatusBarStyleUpdated:)
350 name:@(flutter::kOverlayStyleUpdateNotificationName)
354 [
self setUpApplicationLifecycleNotifications:center];
356 [
self setUpSceneLifecycleNotifications:center];
359 [center addObserver:self
360 selector:@selector(keyboardWillChangeFrame:)
361 name:UIKeyboardWillChangeFrameNotification
364 [center addObserver:self
365 selector:@selector(keyboardWillShowNotification:)
366 name:UIKeyboardWillShowNotification
369 [center addObserver:self
370 selector:@selector(keyboardWillBeHidden:)
371 name:UIKeyboardWillHideNotification
374 [center addObserver:self
375 selector:@selector(onAccessibilityStatusChanged:)
376 name:UIAccessibilityVoiceOverStatusDidChangeNotification
379 [center addObserver:self
380 selector:@selector(onAccessibilityStatusChanged:)
381 name:UIAccessibilitySwitchControlStatusDidChangeNotification
384 [center addObserver:self
385 selector:@selector(onAccessibilityStatusChanged:)
386 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
389 [center addObserver:self
390 selector:@selector(onAccessibilityStatusChanged:)
391 name:UIAccessibilityInvertColorsStatusDidChangeNotification
394 [center addObserver:self
395 selector:@selector(onAccessibilityStatusChanged:)
396 name:UIAccessibilityReduceMotionStatusDidChangeNotification
399 [center addObserver:self
400 selector:@selector(onAccessibilityStatusChanged:)
401 name:UIAccessibilityBoldTextStatusDidChangeNotification
404 [center addObserver:self
405 selector:@selector(onAccessibilityStatusChanged:)
406 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
409 [center addObserver:self
410 selector:@selector(onAccessibilityStatusChanged:)
411 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
414 [center addObserver:self
415 selector:@selector(onUserSettingsChanged:)
416 name:UIContentSizeCategoryDidChangeNotification
419 [center addObserver:self
420 selector:@selector(onHideHomeIndicatorNotification:)
421 name:FlutterViewControllerHideHomeIndicator
424 [center addObserver:self
425 selector:@selector(onShowHomeIndicatorNotification:)
426 name:FlutterViewControllerShowHomeIndicator
430 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
431 [center addObserver:self
432 selector:@selector(sceneBecameActive:)
433 name:UISceneDidActivateNotification
436 [center addObserver:self
437 selector:@selector(sceneWillResignActive:)
438 name:UISceneWillDeactivateNotification
441 [center addObserver:self
442 selector:@selector(sceneWillDisconnect:)
443 name:UISceneDidDisconnectNotification
446 [center addObserver:self
447 selector:@selector(sceneDidEnterBackground:)
448 name:UISceneDidEnterBackgroundNotification
451 [center addObserver:self
452 selector:@selector(sceneWillEnterForeground:)
453 name:UISceneWillEnterForegroundNotification
457 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
458 [center addObserver:self
459 selector:@selector(applicationBecameActive:)
460 name:UIApplicationDidBecomeActiveNotification
463 [center addObserver:self
464 selector:@selector(applicationWillResignActive:)
465 name:UIApplicationWillResignActiveNotification
468 [center addObserver:self
469 selector:@selector(applicationWillTerminate:)
470 name:UIApplicationWillTerminateNotification
473 [center addObserver:self
474 selector:@selector(applicationDidEnterBackground:)
475 name:UIApplicationDidEnterBackgroundNotification
478 [center addObserver:self
479 selector:@selector(applicationWillEnterForeground:)
480 name:UIApplicationWillEnterForegroundNotification
484 - (void)setInitialRoute:(NSString*)route {
485 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
489 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
492 - (void)pushRoute:(NSString*)route {
493 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
496 #pragma mark - Loading the view
498 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
500 return existing_view;
503 auto placeholder = [[UIView alloc] init];
505 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
506 placeholder.backgroundColor = UIColor.systemBackgroundColor;
507 placeholder.autoresizesSubviews = YES;
512 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
513 auto messageLabel = [[UILabel alloc] init];
514 messageLabel.numberOfLines = 0u;
515 messageLabel.textAlignment = NSTextAlignmentCenter;
516 messageLabel.autoresizingMask =
517 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
519 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
520 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
521 @"modes to enable launching from the home screen.";
522 [placeholder addSubview:messageLabel];
529 self.view = GetViewOrPlaceholder(
self.flutterView);
530 self.view.multipleTouchEnabled = YES;
531 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
533 [
self installSplashScreenViewIfNecessary];
536 UIScrollView* scrollView = [[UIScrollView alloc] init];
537 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
539 scrollView.backgroundColor = UIColor.whiteColor;
540 scrollView.delegate =
self;
546 [
self.view addSubview:scrollView];
547 self.scrollView = scrollView;
550 - (
flutter::PointerData)generatePointerDataForFake {
551 flutter::PointerData pointer_data;
552 pointer_data.Clear();
553 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
561 static void SendFakeTouchEvent(UIScreen* screen,
564 flutter::PointerData::Change change) {
565 const CGFloat scale = screen.scale;
566 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
567 pointer_data.physical_x = location.x * scale;
568 pointer_data.physical_y = location.y * scale;
569 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
570 pointer_data.change = change;
571 packet->SetPointerData(0, pointer_data);
572 [engine dispatchPointerDataPacket:std::move(packet)];
575 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
579 CGPoint statusBarPoint = CGPointZero;
580 UIScreen* screen =
self.flutterScreenIfViewLoaded;
582 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
583 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
588 #pragma mark - Managing launch views
590 - (void)installSplashScreenViewIfNecessary {
593 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
594 [
self.splashScreenView removeFromSuperview];
595 self.splashScreenView = nil;
600 UIView* splashScreenView =
self.splashScreenView;
601 if (splashScreenView == nil) {
604 splashScreenView.frame =
self.view.bounds;
605 [
self.view addSubview:splashScreenView];
608 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
612 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
613 if (_displayingFlutterUI != displayingFlutterUI) {
614 if (displayingFlutterUI == YES) {
615 if (!
self.viewIfLoaded.window) {
619 [
self willChangeValueForKey:@"displayingFlutterUI"];
620 _displayingFlutterUI = displayingFlutterUI;
621 [
self didChangeValueForKey:@"displayingFlutterUI"];
625 - (void)callViewRenderedCallback {
626 self.displayingFlutterUI = YES;
627 if (
self.flutterViewRenderedCallback) {
628 self.flutterViewRenderedCallback();
629 self.flutterViewRenderedCallback = nil;
633 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
634 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
635 UIView* splashScreen =
self.splashScreenView;
637 _splashScreenView = nil;
638 [UIView animateWithDuration:0.2
640 splashScreen.alpha = 0;
642 completion:^(BOOL finished) {
643 [splashScreen removeFromSuperview];
650 - (void)onFirstFrameRendered {
651 if (
self.splashScreenView) {
653 [
self removeSplashScreenWithCompletion:^{
654 [weakSelf callViewRenderedCallback];
657 [
self callViewRenderedCallback];
661 - (void)installFirstFrameCallback {
666 [
self.engine installFirstFrameCallback:^{
667 [weakSelf onFirstFrameRendered];
671 #pragma mark - Properties
673 - (int64_t)viewIdentifier {
676 return flutter::kFlutterImplicitViewId;
679 - (BOOL)loadDefaultSplashScreenView {
680 NSString* launchscreenName =
681 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
682 if (launchscreenName == nil) {
685 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
687 splashView = [
self splashScreenFromXib:launchscreenName];
696 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
697 UIStoryboard* storyboard = nil;
699 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
700 }
@catch (NSException* exception) {
704 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
705 return splashScreenViewController.view;
710 - (UIView*)splashScreenFromXib:(NSString*)name {
711 NSArray* objects = nil;
713 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
714 }
@catch (NSException* exception) {
717 if ([objects count] != 0) {
718 UIView* view = [objects objectAtIndex:0];
724 - (void)setSplashScreenView:(UIView*)view {
725 if (view == _splashScreenView) {
731 if (_splashScreenView) {
732 [
self removeSplashScreenWithCompletion:nil];
737 _splashScreenView = view;
738 _splashScreenView.autoresizingMask =
739 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
742 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
743 _flutterViewRenderedCallback = callback;
746 - (UISceneActivationState)activationState {
747 return self.flutterWindowSceneIfViewLoaded.activationState;
750 - (BOOL)stateIsActive {
753 BOOL isActive = flutterApplication
754 ? [
self isApplicationStateMatching:UIApplicationStateActive
755 withApplication:flutterApplication]
756 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
760 - (BOOL)stateIsBackground {
763 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
764 withApplication:flutterApplication]
765 : [
self isSceneStateMatching:UISceneActivationStateBackground];
768 - (BOOL)isApplicationStateMatching:(UIApplicationState)match
769 withApplication:(UIApplication*)application {
770 switch (application.applicationState) {
771 case UIApplicationStateActive:
772 case UIApplicationStateInactive:
773 case UIApplicationStateBackground:
774 return application.applicationState == match;
778 - (BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
779 switch (
self.activationState) {
780 case UISceneActivationStateForegroundActive:
781 case UISceneActivationStateUnattached:
782 case UISceneActivationStateForegroundInactive:
783 case UISceneActivationStateBackground:
784 return self.activationState == match;
788 #pragma mark - Surface creation and teardown updates
790 - (void)surfaceUpdated:(BOOL)appeared {
798 [
self installFirstFrameCallback];
799 self.platformViewsController.flutterView =
self.flutterView;
800 self.platformViewsController.flutterViewController =
self;
801 [
self.engine notifyViewCreated];
803 self.displayingFlutterUI = NO;
804 [
self.engine notifyViewDestroyed];
805 self.platformViewsController.flutterView = nil;
806 self.platformViewsController.flutterViewController = nil;
810 #pragma mark - UIViewController lifecycle notifications
812 - (void)viewDidLoad {
813 TRACE_EVENT0(
"flutter",
"viewDidLoad");
815 if (
self.
engine &&
self.engineNeedsLaunch) {
816 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
817 [
self.engine setViewController:self];
818 self.engineNeedsLaunch = NO;
819 }
else if (
self.
engine.viewController ==
self) {
820 [
self.engine attachView];
824 [
self addInternalPlugins];
827 [
self createTouchRateCorrectionVSyncClientIfNeeded];
829 if (@available(iOS 13.4, *)) {
830 _hoverGestureRecognizer =
831 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
832 _hoverGestureRecognizer.delegate =
self;
833 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
835 _discreteScrollingPanGestureRecognizer =
836 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
837 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
842 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
843 _discreteScrollingPanGestureRecognizer.delegate =
self;
844 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
845 _continuousScrollingPanGestureRecognizer =
846 [[UIPanGestureRecognizer alloc] initWithTarget:self
847 action:@selector(continuousScrollEvent:)];
848 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
849 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
850 _continuousScrollingPanGestureRecognizer.delegate =
self;
851 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
852 _pinchGestureRecognizer =
853 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
854 _pinchGestureRecognizer.allowedTouchTypes = @[];
855 _pinchGestureRecognizer.delegate =
self;
856 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
857 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
858 _rotationGestureRecognizer.allowedTouchTypes = @[];
859 _rotationGestureRecognizer.delegate =
self;
860 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
866 - (void)addInternalPlugins {
870 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
871 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
873 [
self.keyboardManager
877 [
self.keyboardManager addPrimaryResponder:responder];
880 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
882 if (
self.
engine.viewController ==
self) {
887 - (void)removeInternalPlugins {
888 self.keyboardManager = nil;
891 - (void)viewWillAppear:(BOOL)animated {
892 TRACE_EVENT0(
"flutter",
"viewWillAppear");
893 if (
self.
engine.viewController ==
self) {
895 [
self onUserSettingsChanged:nil];
899 if (_viewportMetrics.physical_width) {
900 [
self surfaceUpdated:YES];
902 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
903 [
self.engine.restorationPlugin markRestorationComplete];
906 [
super viewWillAppear:animated];
909 - (void)viewDidAppear:(BOOL)animated {
910 TRACE_EVENT0(
"flutter",
"viewDidAppear");
911 if (
self.
engine.viewController ==
self) {
912 [
self onUserSettingsChanged:nil];
913 [
self onAccessibilityStatusChanged:nil];
915 if (
self.stateIsActive) {
916 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
919 [
super viewDidAppear:animated];
922 - (void)viewWillDisappear:(BOOL)animated {
923 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
924 if (
self.
engine.viewController ==
self) {
925 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
927 [
super viewWillDisappear:animated];
930 - (void)viewDidDisappear:(BOOL)animated {
931 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
932 if (
self.
engine.viewController ==
self) {
933 [
self invalidateKeyboardAnimationVSyncClient];
934 [
self ensureViewportMetricsIsCorrect];
935 [
self surfaceUpdated:NO];
936 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
937 [
self flushOngoingTouches];
938 [
self.engine notifyLowMemory];
941 [
super viewDidDisappear:animated];
944 - (void)viewWillTransitionToSize:(CGSize)size
945 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
946 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
956 NSTimeInterval transitionDuration = coordinator.transitionDuration;
958 if (transitionDuration == 0) {
963 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
964 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
965 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
966 dispatch_get_main_queue(), ^{
974 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
975 [strongSelf updateViewportMetricsIfNeeded];
979 - (void)flushOngoingTouches {
980 if (
self.
engine &&
self.ongoingTouches.count > 0) {
981 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
982 size_t pointer_index = 0;
985 for (NSNumber* device in
self.ongoingTouches) {
987 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
989 pointer_data.change = flutter::PointerData::Change::kCancel;
990 pointer_data.device = device.longLongValue;
991 pointer_data.pointer_identifier = 0;
992 pointer_data.view_id =
self.viewIdentifier;
995 pointer_data.physical_x = 0;
996 pointer_data.physical_y = 0;
997 pointer_data.physical_delta_x = 0.0;
998 pointer_data.physical_delta_y = 0.0;
999 pointer_data.pressure = 1.0;
1000 pointer_data.pressure_max = 1.0;
1002 packet->SetPointerData(pointer_index++, pointer_data);
1005 [
self.ongoingTouches removeAllObjects];
1006 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1010 - (void)deregisterNotifications {
1011 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
1014 [[NSNotificationCenter defaultCenter] removeObserver:self];
1020 [
self removeInternalPlugins];
1021 [
self deregisterNotifications];
1023 [
self invalidateKeyboardAnimationVSyncClient];
1024 [
self invalidateTouchRateCorrectionVSyncClient];
1028 _scrollView.delegate = nil;
1029 _hoverGestureRecognizer.delegate = nil;
1030 _discreteScrollingPanGestureRecognizer.delegate = nil;
1031 _continuousScrollingPanGestureRecognizer.delegate = nil;
1032 _pinchGestureRecognizer.delegate = nil;
1033 _rotationGestureRecognizer.delegate = nil;
1036 #pragma mark - Application lifecycle notifications
1038 - (void)applicationBecameActive:(NSNotification*)notification {
1039 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
1040 [
self appOrSceneBecameActive];
1043 - (void)applicationWillResignActive:(NSNotification*)notification {
1044 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1045 [
self appOrSceneWillResignActive];
1048 - (void)applicationWillTerminate:(NSNotification*)notification {
1049 [
self appOrSceneWillTerminate];
1052 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1053 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1054 [
self appOrSceneDidEnterBackground];
1057 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1058 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1059 [
self appOrSceneWillEnterForeground];
1062 #pragma mark - Scene lifecycle notifications
1064 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1065 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1066 [
self appOrSceneBecameActive];
1069 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1070 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1071 [
self appOrSceneWillResignActive];
1074 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1075 [
self appOrSceneWillTerminate];
1078 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1079 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1080 [
self appOrSceneDidEnterBackground];
1083 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1084 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1085 [
self appOrSceneWillEnterForeground];
1088 #pragma mark - Lifecycle shared
1090 - (void)appOrSceneBecameActive {
1091 self.isKeyboardInOrTransitioningFromBackground = NO;
1092 if (_viewportMetrics.physical_width) {
1093 [
self surfaceUpdated:YES];
1095 [
self performSelector:@selector(goToApplicationLifecycle:)
1096 withObject:@"AppLifecycleState.resumed"
1100 - (void)appOrSceneWillResignActive {
1101 [NSObject cancelPreviousPerformRequestsWithTarget:self
1102 selector:@selector(goToApplicationLifecycle:)
1103 object:@"AppLifecycleState.resumed"];
1104 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1107 - (void)appOrSceneWillTerminate {
1108 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1109 [
self.engine destroyContext];
1112 - (void)appOrSceneDidEnterBackground {
1113 self.isKeyboardInOrTransitioningFromBackground = YES;
1114 [
self surfaceUpdated:NO];
1115 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1118 - (void)appOrSceneWillEnterForeground {
1119 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1123 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1126 if (
self.viewIfLoaded.window) {
1127 [
self.engine.lifecycleChannel sendMessage:state];
1131 #pragma mark - Touch event handling
1133 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1135 case UITouchPhaseBegan:
1136 return flutter::PointerData::Change::kDown;
1137 case UITouchPhaseMoved:
1138 case UITouchPhaseStationary:
1141 return flutter::PointerData::Change::kMove;
1142 case UITouchPhaseEnded:
1143 return flutter::PointerData::Change::kUp;
1144 case UITouchPhaseCancelled:
1145 return flutter::PointerData::Change::kCancel;
1148 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1152 return flutter::PointerData::Change::kCancel;
1155 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1156 switch (touch.type) {
1157 case UITouchTypeDirect:
1158 case UITouchTypeIndirect:
1159 return flutter::PointerData::DeviceKind::kTouch;
1160 case UITouchTypeStylus:
1161 return flutter::PointerData::DeviceKind::kStylus;
1162 case UITouchTypeIndirectPointer:
1163 return flutter::PointerData::DeviceKind::kMouse;
1165 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1169 return flutter::PointerData::DeviceKind::kTouch;
1176 - (void)dispatchTouches:(NSSet*)touches
1177 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1178 event:(UIEvent*)event {
1203 NSUInteger touches_to_remove_count = 0;
1204 for (UITouch* touch in touches) {
1205 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1206 touches_to_remove_count++;
1211 [
self triggerTouchRateCorrectionIfNeeded:touches];
1213 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1215 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1217 size_t pointer_index = 0;
1219 for (UITouch* touch in touches) {
1220 CGPoint windowCoordinates = [touch locationInView:self.view];
1222 flutter::PointerData pointer_data;
1223 pointer_data.Clear();
1228 pointer_data.change = overridden_change !=
nullptr
1229 ? *overridden_change
1230 : PointerDataChangeFromUITouchPhase(touch.phase);
1232 pointer_data.kind = DeviceKindFromTouchType(touch);
1234 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1236 pointer_data.view_id =
self.viewIdentifier;
1239 pointer_data.pointer_identifier = 0;
1241 pointer_data.physical_x = windowCoordinates.x * scale;
1242 pointer_data.physical_y = windowCoordinates.y * scale;
1245 pointer_data.physical_delta_x = 0.0;
1246 pointer_data.physical_delta_y = 0.0;
1248 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1251 switch (pointer_data.change) {
1252 case flutter::PointerData::Change::kDown:
1253 [
self.ongoingTouches addObject:deviceKey];
1255 case flutter::PointerData::Change::kCancel:
1256 case flutter::PointerData::Change::kUp:
1257 [
self.ongoingTouches removeObject:deviceKey];
1259 case flutter::PointerData::Change::kHover:
1260 case flutter::PointerData::Change::kMove:
1263 case flutter::PointerData::Change::kAdd:
1264 case flutter::PointerData::Change::kRemove:
1267 case flutter::PointerData::Change::kPanZoomStart:
1268 case flutter::PointerData::Change::kPanZoomUpdate:
1269 case flutter::PointerData::Change::kPanZoomEnd:
1275 pointer_data.pressure = touch.force;
1276 pointer_data.pressure_max = touch.maximumPossibleForce;
1277 pointer_data.radius_major = touch.majorRadius;
1278 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1279 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1294 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1314 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1316 if (@available(iOS 13.4, *)) {
1317 if (event !=
nullptr) {
1318 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1319 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1321 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1322 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1327 packet->SetPointerData(pointer_index++, pointer_data);
1329 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1330 flutter::PointerData remove_pointer_data = pointer_data;
1331 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1332 packet->SetPointerData(pointer_index++, remove_pointer_data);
1336 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1339 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1340 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1343 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1344 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1347 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1348 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1351 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1352 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1355 - (void)forceTouchesCancelled:(NSSet*)touches {
1356 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1357 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1360 #pragma mark - Touch events rate correction
1362 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1363 if (_touchRateCorrectionVSyncClient != nil) {
1368 const double epsilon = 0.1;
1369 if (displayRefreshRate < 60.0 + epsilon) {
1377 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1380 _touchRateCorrectionVSyncClient =
1381 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1382 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1385 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1386 if (_touchRateCorrectionVSyncClient == nil) {
1394 BOOL isUserInteracting = NO;
1395 for (UITouch* touch in touches) {
1396 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1397 isUserInteracting = YES;
1402 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1403 [_touchRateCorrectionVSyncClient await];
1405 [_touchRateCorrectionVSyncClient pause];
1409 - (void)invalidateTouchRateCorrectionVSyncClient {
1410 [_touchRateCorrectionVSyncClient invalidate];
1411 _touchRateCorrectionVSyncClient = nil;
1414 #pragma mark - Handle view resizing
1416 - (void)updateViewportMetricsIfNeeded {
1417 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1420 if (
self.
engine.viewController ==
self) {
1421 [
self.engine updateViewportMetrics:_viewportMetrics];
1425 - (void)viewDidLayoutSubviews {
1426 CGRect viewBounds =
self.view.bounds;
1427 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1430 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1434 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1435 _viewportMetrics.device_pixel_ratio = scale;
1436 [
self setViewportMetricsSize];
1437 [
self setViewportMetricsPaddings];
1438 [
self updateViewportMetricsIfNeeded];
1445 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.
engine) {
1446 [
self surfaceUpdated:YES];
1447 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1448 NSTimeInterval timeout = 0.2;
1450 NSTimeInterval timeout = 0.1;
1453 waitForFirstFrameSync:timeout
1454 callback:^(BOOL didTimeout) {
1456 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1457 "This may happen in unoptimized builds. If this is"
1458 "a release build, you should load a less complex "
1459 "frame to avoid the timeout."];
1465 - (void)viewSafeAreaInsetsDidChange {
1466 [
self setViewportMetricsPaddings];
1467 [
self updateViewportMetricsIfNeeded];
1468 [
super viewSafeAreaInsetsDidChange];
1472 - (void)setViewportMetricsSize {
1473 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1478 CGFloat scale = screen.scale;
1479 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1480 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1486 - (void)setViewportMetricsPaddings {
1487 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1492 CGFloat scale = screen.scale;
1493 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1494 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1495 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1496 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1499 #pragma mark - Keyboard events
1501 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1506 [
self handleKeyboardNotification:notification];
1509 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1514 [
self handleKeyboardNotification:notification];
1517 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1521 [
self handleKeyboardNotification:notification];
1524 - (void)handleKeyboardNotification:(NSNotification*)notification {
1527 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1531 NSDictionary* info = notification.userInfo;
1532 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1533 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1534 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1535 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1536 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1543 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1544 [
self hideKeyboardImmediately];
1549 if (
self.targetViewInsetBottom == calculatedInset) {
1553 self.targetViewInsetBottom = calculatedInset;
1560 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1561 BOOL keyboardAnimationIsCompounding =
1562 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1565 self.keyboardAnimationIsShowing = keyboardWillShow;
1567 if (!keyboardAnimationIsCompounding) {
1568 [
self startKeyBoardAnimation:duration];
1569 }
else if (
self.keyboardSpringAnimation) {
1570 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1574 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1579 if (notification.name == UIKeyboardWillHideNotification) {
1588 NSDictionary* info = notification.userInfo;
1589 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1590 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1591 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1597 if (CGRectIsEmpty(keyboardFrame)) {
1602 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1608 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1609 NSDictionary* info = notification.userInfo;
1613 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1614 if (isLocal && ![isLocal boolValue]) {
1617 return self.engine.viewController !=
self;
1620 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1628 NSDictionary* info = notification.userInfo;
1629 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1631 if (notification.name == UIKeyboardWillHideNotification) {
1632 return FlutterKeyboardModeHidden;
1637 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1638 return FlutterKeyboardModeFloating;
1641 if (CGRectIsEmpty(keyboardFrame)) {
1642 return FlutterKeyboardModeHidden;
1645 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1646 CGRect adjustedKeyboardFrame = keyboardFrame;
1647 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1648 keyboardFrame:keyboardFrame];
1653 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1654 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1655 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1656 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1658 CGFloat screenHeight = CGRectGetHeight(screenRect);
1659 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1660 if (round(adjustedKeyboardBottom) < screenHeight) {
1661 return FlutterKeyboardModeFloating;
1663 return FlutterKeyboardModeDocked;
1665 return FlutterKeyboardModeHidden;
1668 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1672 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1673 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1674 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1675 CGFloat screenHeight = CGRectGetHeight(screenRect);
1676 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1680 if (screenHeight == keyboardBottom) {
1683 CGRect viewRectRelativeToScreen =
1684 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1685 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1686 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1687 CGFloat offset = screenHeight - viewBottom;
1695 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1697 if (keyboardMode == FlutterKeyboardModeDocked) {
1699 CGRect viewRectRelativeToScreen =
1700 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1701 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1702 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1703 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1708 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1709 return portionOfKeyboardInView * scale;
1714 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1716 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1722 if (!
self.keyboardAnimationView) {
1723 UIView* keyboardAnimationView = [[UIView alloc] init];
1724 keyboardAnimationView.hidden = YES;
1725 self.keyboardAnimationView = keyboardAnimationView;
1728 if (!
self.keyboardAnimationView.superview) {
1729 [
self.view addSubview:self.keyboardAnimationView];
1733 [
self.keyboardAnimationView.layer removeAllAnimations];
1736 self.keyboardAnimationView.frame =
1737 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1738 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1739 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1742 [
self invalidateKeyboardAnimationVSyncClient];
1745 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1746 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1748 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1750 [UIView animateWithDuration:duration
1758 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1761 CAAnimation* keyboardAnimation =
1762 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1763 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1765 completion:^(BOOL finished) {
1766 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1775 [strongSelf invalidateKeyboardAnimationVSyncClient];
1776 [strongSelf removeKeyboardAnimationView];
1777 [strongSelf ensureViewportMetricsIsCorrect];
1782 - (void)hideKeyboardImmediately {
1783 [
self invalidateKeyboardAnimationVSyncClient];
1784 if (
self.keyboardAnimationView) {
1785 [
self.keyboardAnimationView.layer removeAllAnimations];
1786 [
self removeKeyboardAnimationView];
1787 self.keyboardAnimationView = nil;
1789 if (
self.keyboardSpringAnimation) {
1790 self.keyboardSpringAnimation = nil;
1793 self.targetViewInsetBottom = 0.0;
1794 [
self ensureViewportMetricsIsCorrect];
1797 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1799 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1800 _keyboardSpringAnimation = nil;
1805 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1806 _keyboardSpringAnimation =
1807 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1808 damping:keyboardCASpringAnimation.damping
1809 mass:keyboardCASpringAnimation.mass
1810 initialVelocity:keyboardCASpringAnimation.initialVelocity
1811 fromValue:self.originalViewInsetBottom
1812 toValue:self.targetViewInsetBottom];
1815 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1817 if (!
self.isViewLoaded) {
1822 if (!
self.keyboardAnimationView) {
1827 if (!
self.keyboardAnimationVSyncClient) {
1831 if (!
self.keyboardAnimationView.superview) {
1833 [
self.view addSubview:self.keyboardAnimationView];
1836 if (!
self.keyboardSpringAnimation) {
1837 if (
self.keyboardAnimationView.layer.presentationLayer) {
1838 self->_viewportMetrics.physical_view_inset_bottom =
1839 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1840 [
self updateViewportMetricsIfNeeded];
1843 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1844 self->_viewportMetrics.physical_view_inset_bottom =
1845 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1846 [
self updateViewportMetricsIfNeeded];
1850 - (void)setUpKeyboardAnimationVsyncClient:
1852 if (!keyboardAnimationCallback) {
1855 NSAssert(_keyboardAnimationVSyncClient == nil,
1856 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1860 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1861 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1862 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1863 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1864 animationCallback(targetTime);
1868 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1869 callback:uiCallback];
1870 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1871 [_keyboardAnimationVSyncClient await];
1874 - (void)invalidateKeyboardAnimationVSyncClient {
1875 [_keyboardAnimationVSyncClient invalidate];
1876 _keyboardAnimationVSyncClient = nil;
1879 - (void)removeKeyboardAnimationView {
1880 if (
self.keyboardAnimationView.superview != nil) {
1881 [
self.keyboardAnimationView removeFromSuperview];
1885 - (void)ensureViewportMetricsIsCorrect {
1886 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1888 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1889 [
self updateViewportMetricsIfNeeded];
1893 - (void)handlePressEvent:(FlutterUIPressProxy*)press
1894 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1895 if (@available(iOS 13.4, *)) {
1900 [
self.keyboardManager handlePress:press nextAction:next];
1903 - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(
void (^)(BOOL success))completion {
1906 waitForFirstFrame:3.0
1907 callback:^(BOOL didTimeout) {
1910 logError:@"Timeout waiting for first frame when launching a URL."];
1914 [weakSelf.engine.navigationChannel
1915 invokeMethod:@"pushRouteInformation"
1917 @"location" : url.absoluteString ?: [NSNull null],
1919 result:^(id _Nullable result) {
1921 [result isKindOfClass:[NSNumber class]] && [result boolValue];
1925 logError:@"Failed to handle route information in Flutter."];
1927 completion(success);
1946 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1947 [
super pressesBegan:presses withEvent:event];
1950 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1951 [
super pressesChanged:presses withEvent:event];
1954 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1955 [
super pressesEnded:presses withEvent:event];
1958 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1959 [
super pressesCancelled:presses withEvent:event];
1967 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1968 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1969 if (@available(iOS 13.4, *)) {
1971 for (UIPress* press in presses) {
1972 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1974 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1978 [
super pressesBegan:presses withEvent:event];
1982 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1983 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1984 if (@available(iOS 13.4, *)) {
1986 for (UIPress* press in presses) {
1987 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1989 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1993 [
super pressesChanged:presses withEvent:event];
1997 - (void)pressesEnded:(NSSet<UIPress*>*)presses
1998 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1999 if (@available(iOS 13.4, *)) {
2001 for (UIPress* press in presses) {
2002 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2004 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2008 [
super pressesEnded:presses withEvent:event];
2012 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
2013 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2014 if (@available(iOS 13.4, *)) {
2016 for (UIPress* press in presses) {
2017 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2019 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2023 [
super pressesCancelled:presses withEvent:event];
2027 #pragma mark - Orientation updates
2029 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2032 dispatch_async(dispatch_get_main_queue(), ^{
2033 NSDictionary* info = notification.userInfo;
2034 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2035 if (update == nil) {
2038 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2042 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2043 API_AVAILABLE(ios(16.0)) {
2044 for (UIScene* windowScene in windowScenes) {
2045 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
2046 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2047 initWithInterfaceOrientations:self.orientationPreferences];
2048 [(UIWindowScene*)windowScene
2049 requestGeometryUpdateWithPreferences:preference
2050 errorHandler:^(NSError* error) {
2051 os_log_error(OS_LOG_DEFAULT,
2052 "Failed to change device orientation: %@", error);
2054 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2058 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2059 if (new_preferences !=
self.orientationPreferences) {
2060 self.orientationPreferences = new_preferences;
2062 if (@available(iOS 16.0, *)) {
2064 NSSet<UIScene*>* scenes = [NSSet set];
2065 if (flutterApplication) {
2066 scenes = [flutterApplication.connectedScenes
2067 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2068 id scene, NSDictionary* bindings) {
2069 return [scene isKindOfClass:[UIWindowScene class]];
2071 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2072 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2074 [
self requestGeometryUpdateForWindowScenes:scenes];
2076 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2077 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2081 @"Accessing the interface orientation when the window scene is unavailable."];
2084 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2085 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2086 [UIViewController attemptRotationToDeviceOrientation];
2088 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2092 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2093 forKey:@"orientation"];
2094 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2095 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2096 forKey:@"orientation"];
2097 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2098 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2099 forKey:@"orientation"];
2100 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2101 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2102 forKey:@"orientation"];
2109 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2110 self.isHomeIndicatorHidden = YES;
2113 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2114 self.isHomeIndicatorHidden = NO;
2117 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2118 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2119 _isHomeIndicatorHidden = hideHomeIndicator;
2120 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2124 - (BOOL)prefersHomeIndicatorAutoHidden {
2125 return self.isHomeIndicatorHidden;
2128 - (BOOL)shouldAutorotate {
2132 - (NSUInteger)supportedInterfaceOrientations {
2133 return self.orientationPreferences;
2136 #pragma mark - Accessibility
2138 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2143 int32_t flags =
self.accessibilityFlags;
2144 #if TARGET_OS_SIMULATOR
2150 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2151 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2153 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2155 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2157 [
self.engine enableSemantics:enabled withFlags:flags];
2160 - (int32_t)accessibilityFlags {
2162 if (UIAccessibilityIsInvertColorsEnabled()) {
2163 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2165 if (UIAccessibilityIsReduceMotionEnabled()) {
2166 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2168 if (UIAccessibilityIsBoldTextEnabled()) {
2169 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2171 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2172 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2175 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2181 - (BOOL)accessibilityPerformEscape {
2183 if (navigationChannel) {
2190 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2191 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2194 #pragma mark - Set user settings
2196 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2197 [
super traitCollectionDidChange:previousTraitCollection];
2198 [
self onUserSettingsChanged:nil];
2201 - (void)onUserSettingsChanged:(NSNotification*)notification {
2202 [
self.engine.settingsChannel sendMessage:@{
2203 @"textScaleFactor" : @(
self.textScaleFactor),
2205 @"platformBrightness" :
self.brightnessMode,
2206 @"platformContrast" : self.contrastMode,
2207 @"nativeSpellCheckServiceDefined" : @YES,
2208 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2212 - (CGFloat)textScaleFactor {
2214 if (flutterApplication == nil) {
2215 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2219 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2225 const CGFloat xs = 14;
2226 const CGFloat s = 15;
2227 const CGFloat m = 16;
2228 const CGFloat l = 17;
2229 const CGFloat xl = 19;
2230 const CGFloat xxl = 21;
2231 const CGFloat xxxl = 23;
2234 const CGFloat ax1 = 28;
2235 const CGFloat ax2 = 33;
2236 const CGFloat ax3 = 40;
2237 const CGFloat ax4 = 47;
2238 const CGFloat ax5 = 53;
2242 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2244 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2246 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2248 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2250 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2252 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2254 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2256 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2258 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2260 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2262 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2264 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2271 - (BOOL)supportsShowingSystemContextMenu {
2272 if (@available(iOS 16.0, *)) {
2282 - (NSString*)brightnessMode {
2283 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2285 if (style == UIUserInterfaceStyleDark) {
2295 - (NSString*)contrastMode {
2296 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2298 if (contrast == UIAccessibilityContrastHigh) {
2305 #pragma mark - Status bar style
2307 - (UIStatusBarStyle)preferredStatusBarStyle {
2308 return self.statusBarStyle;
2311 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2314 dispatch_async(dispatch_get_main_queue(), ^{
2320 NSDictionary* info = notification.userInfo;
2321 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2322 if (update == nil) {
2326 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2327 if (style != strongSelf.statusBarStyle) {
2328 strongSelf.statusBarStyle = style;
2329 [strongSelf setNeedsStatusBarAppearanceUpdate];
2334 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2335 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2336 self.flutterPrefersStatusBarHidden = hidden;
2337 [
self setNeedsStatusBarAppearanceUpdate];
2341 - (BOOL)prefersStatusBarHidden {
2342 return self.flutterPrefersStatusBarHidden;
2345 #pragma mark - Platform views
2348 return self.engine.platformViewsController;
2352 return self.engine.binaryMessenger;
2355 #pragma mark - FlutterBinaryMessenger
2357 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2358 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2361 - (void)sendOnChannel:(NSString*)channel
2362 message:(NSData*)message
2364 NSAssert(channel,
@"The channel must not be null");
2365 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2369 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2373 binaryMessageHandler:
2375 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2379 setMessageHandlerOnChannel:(NSString*)channel
2382 NSAssert(channel,
@"The channel must not be null");
2383 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2384 binaryMessageHandler:handler
2385 taskQueue:taskQueue];
2389 [
self.engine.binaryMessenger cleanUpConnection:connection];
2392 #pragma mark - FlutterTextureRegistry
2395 return [
self.engine.textureRegistry registerTexture:texture];
2398 - (void)unregisterTexture:(int64_t)textureId {
2399 [
self.engine.textureRegistry unregisterTexture:textureId];
2402 - (void)textureFrameAvailable:(int64_t)textureId {
2403 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2406 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2410 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2414 - (id<FlutterPluginRegistry>)pluginRegistry {
2418 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2419 return UIAccessibilityIsVoiceOverRunning();
2422 #pragma mark - FlutterPluginRegistry
2425 return [
self.engine registrarForPlugin:pluginKey];
2428 - (BOOL)hasPlugin:(NSString*)pluginKey {
2429 return [
self.engine hasPlugin:pluginKey];
2432 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2433 return [
self.engine valuePublishedByPlugin:pluginKey];
2436 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2438 completion:(
void (^)(
void))completion {
2439 self.isPresentingViewControllerAnimating = YES;
2441 [
super presentViewController:viewControllerToPresent
2444 weakSelf.isPresentingViewControllerAnimating = NO;
2451 - (BOOL)isPresentingViewController {
2452 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2455 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2456 API_AVAILABLE(ios(13.4)) {
2457 CGPoint location = [gestureRecognizer locationInView:self.view];
2458 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2460 flutter::PointerData pointer_data;
2461 pointer_data.Clear();
2465 return pointer_data;
2468 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2469 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2470 API_AVAILABLE(ios(13.4)) {
2474 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2475 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2476 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2477 event.type == UIEventTypeScroll) {
2479 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2480 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2481 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2482 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2483 pointer_data.view_id =
self.viewIdentifier;
2485 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2488 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2489 packet->SetPointerData(0, pointer_data);
2490 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2491 self.scrollInertiaEventAppKitDeadline = 0;
2498 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2501 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2502 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2503 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2504 pointer_data.view_id =
self.viewIdentifier;
2506 switch (_hoverGestureRecognizer.state) {
2507 case UIGestureRecognizerStateBegan:
2508 pointer_data.change = flutter::PointerData::Change::kAdd;
2510 case UIGestureRecognizerStateChanged:
2511 pointer_data.change = flutter::PointerData::Change::kHover;
2513 case UIGestureRecognizerStateEnded:
2514 case UIGestureRecognizerStateCancelled:
2515 pointer_data.change = flutter::PointerData::Change::kRemove;
2520 pointer_data.change = flutter::PointerData::Change::kHover;
2524 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2525 BOOL isRunningOnMac = NO;
2526 if (@available(iOS 14.0, *)) {
2530 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2533 time >
self.scrollInertiaEventStartline) {
2537 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2538 packet->SetPointerData(0, pointer_data);
2539 flutter::PointerData inertia_cancel = pointer_data;
2540 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2541 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2542 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2543 inertia_cancel.view_id =
self.viewIdentifier;
2544 packet->SetPointerData(1, inertia_cancel);
2545 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2546 self.scrollInertiaEventStartline = DBL_MAX;
2548 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2549 packet->SetPointerData(0, pointer_data);
2550 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2554 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2555 CGPoint translation = [recognizer translationInView:self.view];
2556 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2558 translation.x *= scale;
2559 translation.y *= scale;
2561 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2562 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2563 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2564 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2565 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2566 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2567 pointer_data.view_id =
self.viewIdentifier;
2573 if (recognizer.state != UIGestureRecognizerStateEnded) {
2579 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2580 packet->SetPointerData(0, pointer_data);
2581 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2584 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2585 CGPoint translation = [recognizer translationInView:self.view];
2586 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2588 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2589 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2590 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2591 pointer_data.view_id =
self.viewIdentifier;
2592 switch (recognizer.state) {
2593 case UIGestureRecognizerStateBegan:
2594 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2596 case UIGestureRecognizerStateChanged:
2597 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2598 pointer_data.pan_x = translation.x * scale;
2599 pointer_data.pan_y = translation.y * scale;
2600 pointer_data.pan_delta_x = 0;
2601 pointer_data.pan_delta_y = 0;
2602 pointer_data.scale = 1;
2604 case UIGestureRecognizerStateEnded:
2605 case UIGestureRecognizerStateCancelled:
2606 self.scrollInertiaEventStartline =
2607 [[NSProcessInfo processInfo] systemUptime] +
2617 self.scrollInertiaEventAppKitDeadline =
2618 [[NSProcessInfo processInfo] systemUptime] +
2619 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2620 [recognizer velocityInView:self.view].y))) -
2622 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2626 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2627 (
long)recognizer.state);
2631 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2632 packet->SetPointerData(0, pointer_data);
2633 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2636 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2637 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2638 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2639 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2640 pointer_data.view_id =
self.viewIdentifier;
2641 switch (recognizer.state) {
2642 case UIGestureRecognizerStateBegan:
2643 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2645 case UIGestureRecognizerStateChanged:
2646 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2647 pointer_data.scale = recognizer.scale;
2648 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2650 case UIGestureRecognizerStateEnded:
2651 case UIGestureRecognizerStateCancelled:
2652 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2656 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2657 (
long)recognizer.state);
2661 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2662 packet->SetPointerData(0, pointer_data);
2663 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2666 #pragma mark - State Restoration
2668 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2669 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2670 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2671 length:restorationData.length
2672 forKey:kFlutterRestorationStateAppData];
2673 [
super encodeRestorableStateWithCoder:coder];
2676 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2677 NSUInteger restorationDataLength;
2678 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2679 returnedLength:&restorationDataLength];
2680 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2681 [
self.engine.restorationPlugin setRestorationData:restorationData];
2685 return self.engine.restorationPlugin;
2689 return self.engine.textInputPlugin;
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
instancetype initWithCoder
FlutterTextInputPlugin * textInputPlugin
NSNotificationName const FlutterViewControllerHideHomeIndicator
static NSString *const kFlutterRestorationStateAppData
NSNotificationName const FlutterViewControllerShowHomeIndicator
NSNotificationName const FlutterSemanticsUpdateNotification
struct MouseState MouseState
static constexpr CGFloat kScrollViewContentSize
NSNotificationName const FlutterViewControllerWillDealloc
static constexpr FLUTTER_ASSERT_ARC int kMicrosecondsPerSecond
void(^ FlutterKeyboardAnimationCallback)(fml::TimePoint)
UIPanGestureRecognizer *continuousScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPanGestureRecognizer *discreteScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPinchGestureRecognizer *pinchGestureRecognizer API_AVAILABLE(ios(13.4))
UIHoverGestureRecognizer *hoverGestureRecognizer API_AVAILABLE(ios(13.4))
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
double displayRefreshRate
The display refresh rate used for reporting purposes. The engine does not care about this for frame s...
FlutterViewController * viewController
UIApplication * application
void setUpIndirectScribbleInteraction:(id< FlutterViewResponder > viewResponder)
UIView * splashScreenView