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"
34 #import "flutter/shell/platform/embedder/embedder.h"
35 #import "flutter/third_party/spring_animation/spring_animation.h"
47 @"FlutterViewControllerHideHomeIndicator";
49 @"FlutterViewControllerShowHomeIndicator";
67 @property(nonatomic, readonly) int64_t viewIdentifier;
72 @property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
74 @property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
75 @property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
76 @property(nonatomic, assign) BOOL initialized;
77 @property(nonatomic, assign) BOOL engineNeedsLaunch;
80 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
81 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
84 @property(nonatomic, assign) BOOL flutterPrefersStatusBarHidden;
86 @property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
91 @property(nonatomic, strong) UIScrollView* scrollView;
92 @property(nonatomic, strong) UIView* keyboardAnimationView;
93 @property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation;
98 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
103 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
104 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
105 @property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
106 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
107 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
108 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
111 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
119 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
127 @property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
133 @property(nonatomic, strong)
136 @property(nonatomic, strong)
137 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
139 @property(nonatomic, strong)
140 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
142 @property(nonatomic, strong)
145 @property(nonatomic, strong)
146 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
149 - (void)addInternalPlugins;
150 - (void)deregisterNotifications;
153 - (void)onFirstFrameRendered;
156 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime;
160 flutter::ViewportMetrics _viewportMetrics;
165 @synthesize viewOpaque = _viewOpaque;
166 @synthesize displayingFlutterUI = _displayingFlutterUI;
171 @dynamic viewIdentifier;
173 #pragma mark - Manage and override all designated initializers
176 nibName:(nullable NSString*)nibName
177 bundle:(nullable NSBundle*)nibBundle {
178 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
179 self = [
super initWithNibName:nibName bundle:nibBundle];
182 if (
engine.viewController) {
183 FML_LOG(ERROR) <<
"The supplied FlutterEngine " << [[engine description] UTF8String]
184 <<
" is already used with FlutterViewController instance "
185 << [[engine.viewController description] UTF8String]
186 <<
". One instance of the FlutterEngine can only be attached to one "
187 "FlutterViewController at a time. Set FlutterEngine.viewController "
188 "to nil before attaching it to another FlutterViewController.";
191 _engineNeedsLaunch = NO;
192 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
193 opaque:self.isViewOpaque
194 enableWideGamut:engine.project.isWideGamutEnabled];
195 _ongoingTouches = [[NSMutableSet alloc] init];
199 [
self performCommonViewControllerInitialization];
200 [engine setViewController:self];
207 nibName:(NSString*)nibName
208 bundle:(NSBundle*)nibBundle {
209 self = [
super initWithNibName:nibName bundle:nibBundle];
213 [
self sharedSetupWithProject:project initialRoute:nil];
220 initialRoute:(NSString*)initialRoute
221 nibName:(NSString*)nibName
222 bundle:(NSBundle*)nibBundle {
223 self = [
super initWithNibName:nibName bundle:nibBundle];
227 [
self sharedSetupWithProject:project initialRoute:initialRoute];
233 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
234 return [
self initWithProject:nil nibName:nil bundle:nil];
238 self = [
super initWithCoder:aDecoder];
242 - (void)awakeFromNib {
243 [
super awakeFromNib];
245 [
self sharedSetupWithProject:nil initialRoute:nil];
249 - (instancetype)init {
250 return [
self initWithProject:nil nibName:nil bundle:nil];
254 initialRoute:(nullable NSString*)initialRoute {
262 allowHeadlessExecution:self.engineAllowHeadlessExecution
263 restorationEnabled:self.restorationIdentifier != nil];
270 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
272 enableWideGamut:project.isWideGamutEnabled];
273 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
274 _engineNeedsLaunch = YES;
275 _ongoingTouches = [[NSMutableSet alloc] init];
279 [
self loadDefaultSplashScreenView];
280 [
self performCommonViewControllerInitialization];
283 - (BOOL)isViewOpaque {
287 - (void)setViewOpaque:(BOOL)value {
289 if (
self.flutterView.layer.opaque != value) {
290 self.flutterView.layer.opaque = value;
291 [
self.flutterView.layer setNeedsLayout];
295 #pragma mark - Common view controller initialization tasks
297 - (void)performCommonViewControllerInitialization {
303 _orientationPreferences = UIInterfaceOrientationMaskAll;
304 _statusBarStyle = UIStatusBarStyleDefault;
308 [
self setUpNotificationCenterObservers];
311 - (void)setUpNotificationCenterObservers {
312 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
313 [center addObserver:self
314 selector:@selector(onOrientationPreferencesUpdated:)
315 name:@(flutter::kOrientationUpdateNotificationName)
318 [center addObserver:self
319 selector:@selector(onPreferredStatusBarStyleUpdated:)
320 name:@(flutter::kOverlayStyleUpdateNotificationName)
324 [
self setUpApplicationLifecycleNotifications:center];
326 if (@available(iOS 13.0, *)) {
327 [
self setUpSceneLifecycleNotifications:center];
329 [
self setUpApplicationLifecycleNotifications:center];
333 [center addObserver:self
334 selector:@selector(keyboardWillChangeFrame:)
335 name:UIKeyboardWillChangeFrameNotification
338 [center addObserver:self
339 selector:@selector(keyboardWillShowNotification:)
340 name:UIKeyboardWillShowNotification
343 [center addObserver:self
344 selector:@selector(keyboardWillBeHidden:)
345 name:UIKeyboardWillHideNotification
348 [center addObserver:self
349 selector:@selector(onAccessibilityStatusChanged:)
350 name:UIAccessibilityVoiceOverStatusDidChangeNotification
353 [center addObserver:self
354 selector:@selector(onAccessibilityStatusChanged:)
355 name:UIAccessibilitySwitchControlStatusDidChangeNotification
358 [center addObserver:self
359 selector:@selector(onAccessibilityStatusChanged:)
360 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
363 [center addObserver:self
364 selector:@selector(onAccessibilityStatusChanged:)
365 name:UIAccessibilityInvertColorsStatusDidChangeNotification
368 [center addObserver:self
369 selector:@selector(onAccessibilityStatusChanged:)
370 name:UIAccessibilityReduceMotionStatusDidChangeNotification
373 [center addObserver:self
374 selector:@selector(onAccessibilityStatusChanged:)
375 name:UIAccessibilityBoldTextStatusDidChangeNotification
378 [center addObserver:self
379 selector:@selector(onAccessibilityStatusChanged:)
380 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
383 if (@available(iOS 13.0, *)) {
384 [center addObserver:self
385 selector:@selector(onAccessibilityStatusChanged:)
386 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
390 [center addObserver:self
391 selector:@selector(onUserSettingsChanged:)
392 name:UIContentSizeCategoryDidChangeNotification
395 [center addObserver:self
396 selector:@selector(onHideHomeIndicatorNotification:)
397 name:FlutterViewControllerHideHomeIndicator
400 [center addObserver:self
401 selector:@selector(onShowHomeIndicatorNotification:)
402 name:FlutterViewControllerShowHomeIndicator
406 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
407 [center addObserver:self
408 selector:@selector(sceneBecameActive:)
409 name:UISceneDidActivateNotification
412 [center addObserver:self
413 selector:@selector(sceneWillResignActive:)
414 name:UISceneWillDeactivateNotification
417 [center addObserver:self
418 selector:@selector(sceneWillDisconnect:)
419 name:UISceneDidDisconnectNotification
422 [center addObserver:self
423 selector:@selector(sceneDidEnterBackground:)
424 name:UISceneDidEnterBackgroundNotification
427 [center addObserver:self
428 selector:@selector(sceneWillEnterForeground:)
429 name:UISceneWillEnterForegroundNotification
433 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
434 [center addObserver:self
435 selector:@selector(applicationBecameActive:)
436 name:UIApplicationDidBecomeActiveNotification
439 [center addObserver:self
440 selector:@selector(applicationWillResignActive:)
441 name:UIApplicationWillResignActiveNotification
444 [center addObserver:self
445 selector:@selector(applicationWillTerminate:)
446 name:UIApplicationWillTerminateNotification
449 [center addObserver:self
450 selector:@selector(applicationDidEnterBackground:)
451 name:UIApplicationDidEnterBackgroundNotification
454 [center addObserver:self
455 selector:@selector(applicationWillEnterForeground:)
456 name:UIApplicationWillEnterForegroundNotification
460 - (void)setInitialRoute:(NSString*)route {
461 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
465 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
468 - (void)pushRoute:(NSString*)route {
469 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
472 #pragma mark - Loading the view
474 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
476 return existing_view;
479 auto placeholder = [[UIView alloc] init];
481 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
482 if (@available(iOS 13.0, *)) {
483 placeholder.backgroundColor = UIColor.systemBackgroundColor;
485 placeholder.backgroundColor = UIColor.whiteColor;
487 placeholder.autoresizesSubviews = YES;
492 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
493 auto messageLabel = [[UILabel alloc] init];
494 messageLabel.numberOfLines = 0u;
495 messageLabel.textAlignment = NSTextAlignmentCenter;
496 messageLabel.autoresizingMask =
497 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
499 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
500 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
501 @"modes to enable launching from the home screen.";
502 [placeholder addSubview:messageLabel];
509 self.view = GetViewOrPlaceholder(
self.flutterView);
510 self.view.multipleTouchEnabled = YES;
511 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
513 [
self installSplashScreenViewIfNecessary];
516 UIScrollView* scrollView = [[UIScrollView alloc] init];
517 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
519 scrollView.backgroundColor = UIColor.whiteColor;
520 scrollView.delegate =
self;
526 [
self.view addSubview:scrollView];
527 self.scrollView = scrollView;
530 - (
flutter::PointerData)generatePointerDataForFake {
531 flutter::PointerData pointer_data;
532 pointer_data.Clear();
533 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
541 static void SendFakeTouchEvent(UIScreen* screen,
544 flutter::PointerData::Change change) {
545 const CGFloat scale = screen.scale;
546 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
547 pointer_data.physical_x = location.x * scale;
548 pointer_data.physical_y = location.y * scale;
549 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
550 pointer_data.change = change;
551 packet->SetPointerData(0, pointer_data);
552 [engine dispatchPointerDataPacket:std::move(packet)];
555 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
559 CGPoint statusBarPoint = CGPointZero;
560 UIScreen* screen =
self.flutterScreenIfViewLoaded;
562 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
563 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
568 #pragma mark - Managing launch views
570 - (void)installSplashScreenViewIfNecessary {
573 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
574 [
self.splashScreenView removeFromSuperview];
575 self.splashScreenView = nil;
580 UIView* splashScreenView =
self.splashScreenView;
581 if (splashScreenView == nil) {
584 splashScreenView.frame =
self.view.bounds;
585 [
self.view addSubview:splashScreenView];
588 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
592 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
593 if (_displayingFlutterUI != displayingFlutterUI) {
594 if (displayingFlutterUI == YES) {
595 if (!
self.viewIfLoaded.window) {
599 [
self willChangeValueForKey:@"displayingFlutterUI"];
600 _displayingFlutterUI = displayingFlutterUI;
601 [
self didChangeValueForKey:@"displayingFlutterUI"];
605 - (void)callViewRenderedCallback {
606 self.displayingFlutterUI = YES;
607 if (
self.flutterViewRenderedCallback) {
608 self.flutterViewRenderedCallback();
609 self.flutterViewRenderedCallback = nil;
613 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
614 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
615 UIView* splashScreen =
self.splashScreenView;
617 _splashScreenView = nil;
618 [UIView animateWithDuration:0.2
620 splashScreen.alpha = 0;
622 completion:^(BOOL finished) {
623 [splashScreen removeFromSuperview];
630 - (void)onFirstFrameRendered {
631 if (
self.splashScreenView) {
633 [
self removeSplashScreenWithCompletion:^{
634 [weakSelf callViewRenderedCallback];
637 [
self callViewRenderedCallback];
641 - (void)installFirstFrameCallback {
646 [
self.engine installFirstFrameCallback:^{
647 [weakSelf onFirstFrameRendered];
651 #pragma mark - Properties
653 - (int64_t)viewIdentifier {
656 return flutter::kFlutterImplicitViewId;
659 - (BOOL)loadDefaultSplashScreenView {
660 NSString* launchscreenName =
661 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
662 if (launchscreenName == nil) {
665 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
667 splashView = [
self splashScreenFromXib:launchscreenName];
676 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
677 UIStoryboard* storyboard = nil;
679 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
680 }
@catch (NSException* exception) {
684 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
685 return splashScreenViewController.view;
690 - (UIView*)splashScreenFromXib:(NSString*)name {
691 NSArray* objects = nil;
693 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
694 }
@catch (NSException* exception) {
697 if ([objects count] != 0) {
698 UIView* view = [objects objectAtIndex:0];
704 - (void)setSplashScreenView:(UIView*)view {
705 if (view == _splashScreenView) {
711 if (_splashScreenView) {
712 [
self removeSplashScreenWithCompletion:nil];
717 _splashScreenView = view;
718 _splashScreenView.autoresizingMask =
719 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
722 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
723 _flutterViewRenderedCallback = callback;
726 - (UISceneActivationState)activationState {
727 return self.flutterWindowSceneIfViewLoaded.activationState;
730 - (BOOL)stateIsActive {
734 if (flutterApplication) {
735 isActive = [
self isApplicationStateMatching:UIApplicationStateActive
736 withApplication:flutterApplication];
737 }
else if (@available(iOS 13.0, *)) {
738 isActive = [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
743 - (BOOL)stateIsBackground {
746 BOOL isBackground = NO;
749 if (flutterApplication) {
750 isBackground = [
self isApplicationStateMatching:UIApplicationStateBackground
751 withApplication:flutterApplication];
752 }
else if (@available(iOS 13.0, *)) {
753 isBackground = [
self isSceneStateMatching:UISceneActivationStateBackground];
758 - (BOOL)isApplicationStateMatching:(UIApplicationState)match
759 withApplication:(UIApplication*)application {
760 switch (application.applicationState) {
761 case UIApplicationStateActive:
762 case UIApplicationStateInactive:
763 case UIApplicationStateBackground:
764 return application.applicationState == match;
768 - (BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
769 switch (
self.activationState) {
770 case UISceneActivationStateForegroundActive:
771 case UISceneActivationStateUnattached:
772 case UISceneActivationStateForegroundInactive:
773 case UISceneActivationStateBackground:
774 return self.activationState == match;
778 #pragma mark - Surface creation and teardown updates
780 - (void)surfaceUpdated:(BOOL)appeared {
788 [
self installFirstFrameCallback];
789 self.platformViewsController.flutterView =
self.flutterView;
790 self.platformViewsController.flutterViewController =
self;
791 [
self.engine notifyViewCreated];
793 self.displayingFlutterUI = NO;
794 [
self.engine notifyViewDestroyed];
795 self.platformViewsController.flutterView = nil;
796 self.platformViewsController.flutterViewController = nil;
800 #pragma mark - UIViewController lifecycle notifications
802 - (void)viewDidLoad {
803 TRACE_EVENT0(
"flutter",
"viewDidLoad");
805 if (
self.
engine &&
self.engineNeedsLaunch) {
806 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
807 [
self.engine setViewController:self];
808 self.engineNeedsLaunch = NO;
809 }
else if (
self.
engine.viewController ==
self) {
810 [
self.engine attachView];
814 [
self addInternalPlugins];
817 [
self createTouchRateCorrectionVSyncClientIfNeeded];
819 if (@available(iOS 13.4, *)) {
820 _hoverGestureRecognizer =
821 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
822 _hoverGestureRecognizer.delegate =
self;
823 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
825 _discreteScrollingPanGestureRecognizer =
826 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
827 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
832 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
833 _discreteScrollingPanGestureRecognizer.delegate =
self;
834 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
835 _continuousScrollingPanGestureRecognizer =
836 [[UIPanGestureRecognizer alloc] initWithTarget:self
837 action:@selector(continuousScrollEvent:)];
838 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
839 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
840 _continuousScrollingPanGestureRecognizer.delegate =
self;
841 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
842 _pinchGestureRecognizer =
843 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
844 _pinchGestureRecognizer.allowedTouchTypes = @[];
845 _pinchGestureRecognizer.delegate =
self;
846 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
847 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
848 _rotationGestureRecognizer.allowedTouchTypes = @[];
849 _rotationGestureRecognizer.delegate =
self;
850 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
856 - (void)addInternalPlugins {
860 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
861 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
863 [
self.keyboardManager
867 [
self.keyboardManager addPrimaryResponder:responder];
870 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
872 if (
self.
engine.viewController ==
self) {
877 - (void)removeInternalPlugins {
878 self.keyboardManager = nil;
881 - (void)viewWillAppear:(BOOL)animated {
882 TRACE_EVENT0(
"flutter",
"viewWillAppear");
883 if (
self.
engine.viewController ==
self) {
885 [
self onUserSettingsChanged:nil];
889 if (_viewportMetrics.physical_width) {
890 [
self surfaceUpdated:YES];
892 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
893 [
self.engine.restorationPlugin markRestorationComplete];
896 [
super viewWillAppear:animated];
899 - (void)viewDidAppear:(BOOL)animated {
900 TRACE_EVENT0(
"flutter",
"viewDidAppear");
901 if (
self.
engine.viewController ==
self) {
902 [
self onUserSettingsChanged:nil];
903 [
self onAccessibilityStatusChanged:nil];
905 if (
self.stateIsActive) {
906 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
909 [
super viewDidAppear:animated];
912 - (void)viewWillDisappear:(BOOL)animated {
913 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
914 if (
self.
engine.viewController ==
self) {
915 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
917 [
super viewWillDisappear:animated];
920 - (void)viewDidDisappear:(BOOL)animated {
921 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
922 if (
self.
engine.viewController ==
self) {
923 [
self invalidateKeyboardAnimationVSyncClient];
924 [
self ensureViewportMetricsIsCorrect];
925 [
self surfaceUpdated:NO];
926 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
927 [
self flushOngoingTouches];
928 [
self.engine notifyLowMemory];
931 [
super viewDidDisappear:animated];
934 - (void)viewWillTransitionToSize:(CGSize)size
935 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
936 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
946 NSTimeInterval transitionDuration = coordinator.transitionDuration;
948 if (transitionDuration == 0) {
953 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
954 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
955 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
956 dispatch_get_main_queue(), ^{
964 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
965 [strongSelf updateViewportMetricsIfNeeded];
969 - (void)flushOngoingTouches {
970 if (
self.
engine &&
self.ongoingTouches.count > 0) {
971 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
972 size_t pointer_index = 0;
975 for (NSNumber* device in
self.ongoingTouches) {
977 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
979 pointer_data.change = flutter::PointerData::Change::kCancel;
980 pointer_data.device = device.longLongValue;
981 pointer_data.pointer_identifier = 0;
982 pointer_data.view_id =
self.viewIdentifier;
985 pointer_data.physical_x = 0;
986 pointer_data.physical_y = 0;
987 pointer_data.physical_delta_x = 0.0;
988 pointer_data.physical_delta_y = 0.0;
989 pointer_data.pressure = 1.0;
990 pointer_data.pressure_max = 1.0;
992 packet->SetPointerData(pointer_index++, pointer_data);
995 [
self.ongoingTouches removeAllObjects];
996 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1000 - (void)deregisterNotifications {
1001 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
1004 [[NSNotificationCenter defaultCenter] removeObserver:self];
1010 [
self removeInternalPlugins];
1011 [
self deregisterNotifications];
1013 [
self invalidateKeyboardAnimationVSyncClient];
1014 [
self invalidateTouchRateCorrectionVSyncClient];
1018 _scrollView.delegate = nil;
1019 _hoverGestureRecognizer.delegate = nil;
1020 _discreteScrollingPanGestureRecognizer.delegate = nil;
1021 _continuousScrollingPanGestureRecognizer.delegate = nil;
1022 _pinchGestureRecognizer.delegate = nil;
1023 _rotationGestureRecognizer.delegate = nil;
1026 #pragma mark - Application lifecycle notifications
1028 - (void)applicationBecameActive:(NSNotification*)notification {
1029 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
1030 [
self appOrSceneBecameActive];
1033 - (void)applicationWillResignActive:(NSNotification*)notification {
1034 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1035 [
self appOrSceneWillResignActive];
1038 - (void)applicationWillTerminate:(NSNotification*)notification {
1039 [
self appOrSceneWillTerminate];
1042 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1043 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1044 [
self appOrSceneDidEnterBackground];
1047 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1048 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1049 [
self appOrSceneWillEnterForeground];
1052 #pragma mark - Scene lifecycle notifications
1054 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1055 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1056 [
self appOrSceneBecameActive];
1059 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1060 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1061 [
self appOrSceneWillResignActive];
1064 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1065 [
self appOrSceneWillTerminate];
1068 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1069 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1070 [
self appOrSceneDidEnterBackground];
1073 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1074 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1075 [
self appOrSceneWillEnterForeground];
1078 #pragma mark - Lifecycle shared
1080 - (void)appOrSceneBecameActive {
1081 self.isKeyboardInOrTransitioningFromBackground = NO;
1082 if (_viewportMetrics.physical_width) {
1083 [
self surfaceUpdated:YES];
1085 [
self performSelector:@selector(goToApplicationLifecycle:)
1086 withObject:@"AppLifecycleState.resumed"
1090 - (void)appOrSceneWillResignActive {
1091 [NSObject cancelPreviousPerformRequestsWithTarget:self
1092 selector:@selector(goToApplicationLifecycle:)
1093 object:@"AppLifecycleState.resumed"];
1094 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1097 - (void)appOrSceneWillTerminate {
1098 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1099 [
self.engine destroyContext];
1102 - (void)appOrSceneDidEnterBackground {
1103 self.isKeyboardInOrTransitioningFromBackground = YES;
1104 [
self surfaceUpdated:NO];
1105 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1108 - (void)appOrSceneWillEnterForeground {
1109 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1113 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1116 if (
self.viewIfLoaded.window) {
1117 [
self.engine.lifecycleChannel sendMessage:state];
1121 #pragma mark - Touch event handling
1123 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1125 case UITouchPhaseBegan:
1126 return flutter::PointerData::Change::kDown;
1127 case UITouchPhaseMoved:
1128 case UITouchPhaseStationary:
1131 return flutter::PointerData::Change::kMove;
1132 case UITouchPhaseEnded:
1133 return flutter::PointerData::Change::kUp;
1134 case UITouchPhaseCancelled:
1135 return flutter::PointerData::Change::kCancel;
1138 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1142 return flutter::PointerData::Change::kCancel;
1145 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1146 switch (touch.type) {
1147 case UITouchTypeDirect:
1148 case UITouchTypeIndirect:
1149 return flutter::PointerData::DeviceKind::kTouch;
1150 case UITouchTypeStylus:
1151 return flutter::PointerData::DeviceKind::kStylus;
1152 case UITouchTypeIndirectPointer:
1153 return flutter::PointerData::DeviceKind::kMouse;
1155 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1159 return flutter::PointerData::DeviceKind::kTouch;
1166 - (void)dispatchTouches:(NSSet*)touches
1167 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1168 event:(UIEvent*)event {
1193 NSUInteger touches_to_remove_count = 0;
1194 for (UITouch* touch in touches) {
1195 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1196 touches_to_remove_count++;
1201 [
self triggerTouchRateCorrectionIfNeeded:touches];
1203 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1205 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1207 size_t pointer_index = 0;
1209 for (UITouch* touch in touches) {
1210 CGPoint windowCoordinates = [touch locationInView:self.view];
1212 flutter::PointerData pointer_data;
1213 pointer_data.Clear();
1218 pointer_data.change = overridden_change !=
nullptr
1219 ? *overridden_change
1220 : PointerDataChangeFromUITouchPhase(touch.phase);
1222 pointer_data.kind = DeviceKindFromTouchType(touch);
1224 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1226 pointer_data.view_id =
self.viewIdentifier;
1229 pointer_data.pointer_identifier = 0;
1231 pointer_data.physical_x = windowCoordinates.x * scale;
1232 pointer_data.physical_y = windowCoordinates.y * scale;
1235 pointer_data.physical_delta_x = 0.0;
1236 pointer_data.physical_delta_y = 0.0;
1238 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1241 switch (pointer_data.change) {
1242 case flutter::PointerData::Change::kDown:
1243 [
self.ongoingTouches addObject:deviceKey];
1245 case flutter::PointerData::Change::kCancel:
1246 case flutter::PointerData::Change::kUp:
1247 [
self.ongoingTouches removeObject:deviceKey];
1249 case flutter::PointerData::Change::kHover:
1250 case flutter::PointerData::Change::kMove:
1253 case flutter::PointerData::Change::kAdd:
1254 case flutter::PointerData::Change::kRemove:
1257 case flutter::PointerData::Change::kPanZoomStart:
1258 case flutter::PointerData::Change::kPanZoomUpdate:
1259 case flutter::PointerData::Change::kPanZoomEnd:
1265 pointer_data.pressure = touch.force;
1266 pointer_data.pressure_max = touch.maximumPossibleForce;
1267 pointer_data.radius_major = touch.majorRadius;
1268 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1269 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1284 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1304 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1306 if (@available(iOS 13.4, *)) {
1307 if (event !=
nullptr) {
1308 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1309 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1311 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1312 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1317 packet->SetPointerData(pointer_index++, pointer_data);
1319 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1320 flutter::PointerData remove_pointer_data = pointer_data;
1321 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1322 packet->SetPointerData(pointer_index++, remove_pointer_data);
1326 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1329 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1330 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1333 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1334 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1337 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1338 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1341 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1342 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1345 - (void)forceTouchesCancelled:(NSSet*)touches {
1346 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1347 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1350 #pragma mark - Touch events rate correction
1352 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1353 if (_touchRateCorrectionVSyncClient != nil) {
1358 const double epsilon = 0.1;
1359 if (displayRefreshRate < 60.0 + epsilon) {
1367 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1370 _touchRateCorrectionVSyncClient =
1371 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1372 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1375 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1376 if (_touchRateCorrectionVSyncClient == nil) {
1384 BOOL isUserInteracting = NO;
1385 for (UITouch* touch in touches) {
1386 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1387 isUserInteracting = YES;
1392 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1393 [_touchRateCorrectionVSyncClient await];
1395 [_touchRateCorrectionVSyncClient pause];
1399 - (void)invalidateTouchRateCorrectionVSyncClient {
1400 [_touchRateCorrectionVSyncClient invalidate];
1401 _touchRateCorrectionVSyncClient = nil;
1404 #pragma mark - Handle view resizing
1406 - (void)updateViewportMetricsIfNeeded {
1407 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1410 if (
self.
engine.viewController ==
self) {
1411 [
self.engine updateViewportMetrics:_viewportMetrics];
1415 - (void)viewDidLayoutSubviews {
1416 CGRect viewBounds =
self.view.bounds;
1417 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1420 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1424 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1425 _viewportMetrics.device_pixel_ratio = scale;
1426 [
self setViewportMetricsSize];
1427 [
self setViewportMetricsPaddings];
1428 [
self updateViewportMetricsIfNeeded];
1435 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.
engine) {
1436 [
self surfaceUpdated:YES];
1437 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1438 NSTimeInterval timeout = 0.2;
1440 NSTimeInterval timeout = 0.1;
1443 waitForFirstFrameSync:timeout
1444 callback:^(BOOL didTimeout) {
1447 << "Timeout waiting for the first frame to render. This may happen in "
1448 "unoptimized builds. If this is a release build, you should load a "
1449 "less complex frame to avoid the timeout.";
1455 - (void)viewSafeAreaInsetsDidChange {
1456 [
self setViewportMetricsPaddings];
1457 [
self updateViewportMetricsIfNeeded];
1458 [
super viewSafeAreaInsetsDidChange];
1462 - (void)setViewportMetricsSize {
1463 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1468 CGFloat scale = screen.scale;
1469 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1470 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1476 - (void)setViewportMetricsPaddings {
1477 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1482 CGFloat scale = screen.scale;
1483 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1484 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1485 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1486 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1489 #pragma mark - Keyboard events
1491 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1496 [
self handleKeyboardNotification:notification];
1499 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1504 [
self handleKeyboardNotification:notification];
1507 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1511 [
self handleKeyboardNotification:notification];
1514 - (void)handleKeyboardNotification:(NSNotification*)notification {
1517 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1521 NSDictionary* info = notification.userInfo;
1522 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1523 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1524 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1525 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1526 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1533 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1534 [
self hideKeyboardImmediately];
1539 if (
self.targetViewInsetBottom == calculatedInset) {
1543 self.targetViewInsetBottom = calculatedInset;
1550 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1551 BOOL keyboardAnimationIsCompounding =
1552 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1555 self.keyboardAnimationIsShowing = keyboardWillShow;
1557 if (!keyboardAnimationIsCompounding) {
1558 [
self startKeyBoardAnimation:duration];
1559 }
else if (
self.keyboardSpringAnimation) {
1560 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1564 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1569 if (notification.name == UIKeyboardWillHideNotification) {
1578 NSDictionary* info = notification.userInfo;
1579 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1580 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1581 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1587 if (CGRectIsEmpty(keyboardFrame)) {
1592 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1596 if (@available(iOS 13.0, *)) {
1604 if (
self.isKeyboardInOrTransitioningFromBackground) {
1612 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1613 NSDictionary* info = notification.userInfo;
1617 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1618 if (isLocal && ![isLocal boolValue]) {
1621 return self.engine.viewController !=
self;
1624 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1632 NSDictionary* info = notification.userInfo;
1633 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1635 if (notification.name == UIKeyboardWillHideNotification) {
1636 return FlutterKeyboardModeHidden;
1641 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1642 return FlutterKeyboardModeFloating;
1645 if (CGRectIsEmpty(keyboardFrame)) {
1646 return FlutterKeyboardModeHidden;
1649 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1650 CGRect adjustedKeyboardFrame = keyboardFrame;
1651 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1652 keyboardFrame:keyboardFrame];
1657 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1658 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1659 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1660 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1662 CGFloat screenHeight = CGRectGetHeight(screenRect);
1663 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1664 if (round(adjustedKeyboardBottom) < screenHeight) {
1665 return FlutterKeyboardModeFloating;
1667 return FlutterKeyboardModeDocked;
1669 return FlutterKeyboardModeHidden;
1672 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1676 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1677 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1678 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1679 CGFloat screenHeight = CGRectGetHeight(screenRect);
1680 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1684 if (screenHeight == keyboardBottom) {
1687 CGRect viewRectRelativeToScreen =
1688 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1689 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1690 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1691 CGFloat offset = screenHeight - viewBottom;
1699 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1701 if (keyboardMode == FlutterKeyboardModeDocked) {
1703 CGRect viewRectRelativeToScreen =
1704 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1705 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1706 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1707 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1712 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1713 return portionOfKeyboardInView * scale;
1718 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1720 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1726 if (!
self.keyboardAnimationView) {
1727 UIView* keyboardAnimationView = [[UIView alloc] init];
1728 keyboardAnimationView.hidden = YES;
1729 self.keyboardAnimationView = keyboardAnimationView;
1732 if (!
self.keyboardAnimationView.superview) {
1733 [
self.view addSubview:self.keyboardAnimationView];
1737 [
self.keyboardAnimationView.layer removeAllAnimations];
1740 self.keyboardAnimationView.frame =
1741 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1742 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1743 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1746 [
self invalidateKeyboardAnimationVSyncClient];
1749 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1750 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1752 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1754 [UIView animateWithDuration:duration
1762 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1765 CAAnimation* keyboardAnimation =
1766 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1767 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1769 completion:^(BOOL finished) {
1770 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1779 [strongSelf invalidateKeyboardAnimationVSyncClient];
1780 [strongSelf removeKeyboardAnimationView];
1781 [strongSelf ensureViewportMetricsIsCorrect];
1786 - (void)hideKeyboardImmediately {
1787 [
self invalidateKeyboardAnimationVSyncClient];
1788 if (
self.keyboardAnimationView) {
1789 [
self.keyboardAnimationView.layer removeAllAnimations];
1790 [
self removeKeyboardAnimationView];
1791 self.keyboardAnimationView = nil;
1793 if (
self.keyboardSpringAnimation) {
1794 self.keyboardSpringAnimation = nil;
1797 self.targetViewInsetBottom = 0.0;
1798 [
self ensureViewportMetricsIsCorrect];
1801 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1803 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1804 _keyboardSpringAnimation = nil;
1809 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1810 _keyboardSpringAnimation =
1811 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1812 damping:keyboardCASpringAnimation.damping
1813 mass:keyboardCASpringAnimation.mass
1814 initialVelocity:keyboardCASpringAnimation.initialVelocity
1815 fromValue:self.originalViewInsetBottom
1816 toValue:self.targetViewInsetBottom];
1819 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1821 if (!
self.isViewLoaded) {
1826 if (!
self.keyboardAnimationView) {
1831 if (!
self.keyboardAnimationVSyncClient) {
1835 if (!
self.keyboardAnimationView.superview) {
1837 [
self.view addSubview:self.keyboardAnimationView];
1840 if (!
self.keyboardSpringAnimation) {
1841 if (
self.keyboardAnimationView.layer.presentationLayer) {
1842 self->_viewportMetrics.physical_view_inset_bottom =
1843 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1844 [
self updateViewportMetricsIfNeeded];
1847 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1848 self->_viewportMetrics.physical_view_inset_bottom =
1849 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1850 [
self updateViewportMetricsIfNeeded];
1854 - (void)setUpKeyboardAnimationVsyncClient:
1856 if (!keyboardAnimationCallback) {
1859 NSAssert(_keyboardAnimationVSyncClient == nil,
1860 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1864 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1865 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1866 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1867 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1868 animationCallback(targetTime);
1872 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1873 callback:uiCallback];
1874 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1875 [_keyboardAnimationVSyncClient await];
1878 - (void)invalidateKeyboardAnimationVSyncClient {
1879 [_keyboardAnimationVSyncClient invalidate];
1880 _keyboardAnimationVSyncClient = nil;
1883 - (void)removeKeyboardAnimationView {
1884 if (
self.keyboardAnimationView.superview != nil) {
1885 [
self.keyboardAnimationView removeFromSuperview];
1889 - (void)ensureViewportMetricsIsCorrect {
1890 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1892 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1893 [
self updateViewportMetricsIfNeeded];
1898 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1899 if (@available(iOS 13.4, *)) {
1904 [
self.keyboardManager handlePress:press nextAction:next];
1907 - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(
void (^)(BOOL success))completion {
1910 waitForFirstFrame:3.0
1911 callback:^(BOOL didTimeout) {
1913 FML_LOG(ERROR) << "Timeout waiting for the first frame when launching an URL.";
1917 [weakSelf.engine.navigationChannel
1918 invokeMethod:@"pushRouteInformation"
1920 @"location" : url.absoluteString ?: [NSNull null],
1922 result:^(id _Nullable result) {
1924 [result isKindOfClass:[NSNumber class]] && [result boolValue];
1927 FML_LOG(ERROR) << "Failed to handle route information in Flutter.";
1929 completion(success);
1948 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1949 [
super pressesBegan:presses withEvent:event];
1952 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1953 [
super pressesChanged:presses withEvent:event];
1956 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1957 [
super pressesEnded:presses withEvent:event];
1960 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1961 [
super pressesCancelled:presses withEvent:event];
1969 - (void)pressesBegan:(NSSet<UIPress*>*)presses
1970 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1971 if (@available(iOS 13.4, *)) {
1973 for (UIPress* press in presses) {
1976 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1980 [
super pressesBegan:presses withEvent:event];
1984 - (void)pressesChanged:(NSSet<UIPress*>*)presses
1985 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1986 if (@available(iOS 13.4, *)) {
1988 for (UIPress* press in presses) {
1991 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1995 [
super pressesChanged:presses withEvent:event];
1999 - (void)pressesEnded:(NSSet<UIPress*>*)presses
2000 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2001 if (@available(iOS 13.4, *)) {
2003 for (UIPress* press in presses) {
2006 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2010 [
super pressesEnded:presses withEvent:event];
2014 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
2015 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2016 if (@available(iOS 13.4, *)) {
2018 for (UIPress* press in presses) {
2021 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2025 [
super pressesCancelled:presses withEvent:event];
2029 #pragma mark - Orientation updates
2031 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2034 dispatch_async(dispatch_get_main_queue(), ^{
2035 NSDictionary* info = notification.userInfo;
2036 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2037 if (update == nil) {
2040 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2044 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2045 API_AVAILABLE(ios(16.0)) {
2046 for (UIScene* windowScene in windowScenes) {
2047 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
2048 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2049 initWithInterfaceOrientations:self.orientationPreferences];
2050 [(UIWindowScene*)windowScene
2051 requestGeometryUpdateWithPreferences:preference
2052 errorHandler:^(NSError* error) {
2053 os_log_error(OS_LOG_DEFAULT,
2054 "Failed to change device orientation: %@", error);
2056 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2060 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2061 if (new_preferences !=
self.orientationPreferences) {
2062 self.orientationPreferences = new_preferences;
2064 if (@available(iOS 16.0, *)) {
2066 NSSet<UIScene*>* scenes = [NSSet set];
2067 if (flutterApplication) {
2068 scenes = [flutterApplication.connectedScenes
2069 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2070 id scene, NSDictionary* bindings) {
2071 return [scene isKindOfClass:[UIWindowScene class]];
2073 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2074 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2076 [
self requestGeometryUpdateForWindowScenes:scenes];
2078 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2079 if (@available(iOS 13.0, *)) {
2080 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2083 <<
"Accessing the interface orientation when the window scene is unavailable.";
2086 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2089 if (flutterApplication) {
2090 currentInterfaceOrientation = 1 << [flutterApplication statusBarOrientation];
2092 FML_LOG(ERROR) <<
"Application based status bar orentiation update is not supported in "
2093 "app extension. Orientation: "
2094 << currentInterfaceOrientation;
2097 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2098 [UIViewController attemptRotationToDeviceOrientation];
2100 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2104 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2105 forKey:@"orientation"];
2106 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2107 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2108 forKey:@"orientation"];
2109 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2110 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2111 forKey:@"orientation"];
2112 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2113 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2114 forKey:@"orientation"];
2121 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2122 self.isHomeIndicatorHidden = YES;
2125 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2126 self.isHomeIndicatorHidden = NO;
2129 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2130 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2131 _isHomeIndicatorHidden = hideHomeIndicator;
2132 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2136 - (BOOL)prefersHomeIndicatorAutoHidden {
2137 return self.isHomeIndicatorHidden;
2140 - (BOOL)shouldAutorotate {
2144 - (NSUInteger)supportedInterfaceOrientations {
2145 return self.orientationPreferences;
2148 #pragma mark - Accessibility
2150 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2155 int32_t flags =
self.accessibilityFlags;
2156 #if TARGET_OS_SIMULATOR
2162 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2163 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2165 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2167 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2169 [
self.engine enableSemantics:enabled withFlags:flags];
2172 - (int32_t)accessibilityFlags {
2174 if (UIAccessibilityIsInvertColorsEnabled()) {
2175 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2177 if (UIAccessibilityIsReduceMotionEnabled()) {
2178 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2180 if (UIAccessibilityIsBoldTextEnabled()) {
2181 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2183 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2184 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2187 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2193 - (BOOL)accessibilityPerformEscape {
2195 if (navigationChannel) {
2202 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2203 if (@available(iOS 13, *)) {
2204 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2210 #pragma mark - Set user settings
2212 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2213 [
super traitCollectionDidChange:previousTraitCollection];
2214 [
self onUserSettingsChanged:nil];
2217 - (void)onUserSettingsChanged:(NSNotification*)notification {
2218 [
self.engine.settingsChannel sendMessage:@{
2219 @"textScaleFactor" : @(
self.textScaleFactor),
2221 @"platformBrightness" :
self.brightnessMode,
2222 @"platformContrast" : self.contrastMode,
2223 @"nativeSpellCheckServiceDefined" : @YES,
2224 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2228 - (CGFloat)textScaleFactor {
2230 if (flutterApplication == nil) {
2231 FML_LOG(WARNING) <<
"Dynamic content size update is not supported in app extension.";
2235 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2241 const CGFloat xs = 14;
2242 const CGFloat s = 15;
2243 const CGFloat m = 16;
2244 const CGFloat l = 17;
2245 const CGFloat xl = 19;
2246 const CGFloat xxl = 21;
2247 const CGFloat xxxl = 23;
2250 const CGFloat ax1 = 28;
2251 const CGFloat ax2 = 33;
2252 const CGFloat ax3 = 40;
2253 const CGFloat ax4 = 47;
2254 const CGFloat ax5 = 53;
2258 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2260 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2262 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2264 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2266 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2268 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2270 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2272 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2274 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2276 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2278 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2280 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2287 - (BOOL)supportsShowingSystemContextMenu {
2288 if (@available(iOS 16.0, *)) {
2298 - (NSString*)brightnessMode {
2299 if (@available(iOS 13, *)) {
2300 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2302 if (style == UIUserInterfaceStyleDark) {
2315 - (NSString*)contrastMode {
2316 if (@available(iOS 13, *)) {
2317 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2319 if (contrast == UIAccessibilityContrastHigh) {
2329 #pragma mark - Status bar style
2331 - (UIStatusBarStyle)preferredStatusBarStyle {
2332 return self.statusBarStyle;
2335 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2338 dispatch_async(dispatch_get_main_queue(), ^{
2344 NSDictionary* info = notification.userInfo;
2345 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2346 if (update == nil) {
2350 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2351 if (style != strongSelf.statusBarStyle) {
2352 strongSelf.statusBarStyle = style;
2353 [strongSelf setNeedsStatusBarAppearanceUpdate];
2358 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2359 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2360 self.flutterPrefersStatusBarHidden = hidden;
2361 [
self setNeedsStatusBarAppearanceUpdate];
2365 - (BOOL)prefersStatusBarHidden {
2366 return self.flutterPrefersStatusBarHidden;
2369 #pragma mark - Platform views
2372 return self.engine.platformViewsController;
2376 return self.engine.binaryMessenger;
2379 #pragma mark - FlutterBinaryMessenger
2381 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2382 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2385 - (void)sendOnChannel:(NSString*)channel
2386 message:(NSData*)message
2388 NSAssert(channel,
@"The channel must not be null");
2389 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2393 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2397 binaryMessageHandler:
2399 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2403 setMessageHandlerOnChannel:(NSString*)channel
2406 NSAssert(channel,
@"The channel must not be null");
2407 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2408 binaryMessageHandler:handler
2409 taskQueue:taskQueue];
2413 [
self.engine.binaryMessenger cleanUpConnection:connection];
2416 #pragma mark - FlutterTextureRegistry
2419 return [
self.engine.textureRegistry registerTexture:texture];
2422 - (void)unregisterTexture:(int64_t)textureId {
2423 [
self.engine.textureRegistry unregisterTexture:textureId];
2426 - (void)textureFrameAvailable:(int64_t)textureId {
2427 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2430 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2434 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2438 - (id<FlutterPluginRegistry>)pluginRegistry {
2442 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2443 return UIAccessibilityIsVoiceOverRunning();
2446 #pragma mark - FlutterPluginRegistry
2449 return [
self.engine registrarForPlugin:pluginKey];
2452 - (BOOL)hasPlugin:(NSString*)pluginKey {
2453 return [
self.engine hasPlugin:pluginKey];
2456 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2457 return [
self.engine valuePublishedByPlugin:pluginKey];
2460 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2462 completion:(
void (^)(
void))completion {
2463 self.isPresentingViewControllerAnimating = YES;
2465 [
super presentViewController:viewControllerToPresent
2468 weakSelf.isPresentingViewControllerAnimating = NO;
2475 - (BOOL)isPresentingViewController {
2476 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2479 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2480 API_AVAILABLE(ios(13.4)) {
2481 CGPoint location = [gestureRecognizer locationInView:self.view];
2482 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2484 flutter::PointerData pointer_data;
2485 pointer_data.Clear();
2489 return pointer_data;
2492 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2493 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2494 API_AVAILABLE(ios(13.4)) {
2498 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2499 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2500 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2501 event.type == UIEventTypeScroll) {
2503 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2504 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2505 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2506 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2507 pointer_data.view_id =
self.viewIdentifier;
2509 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2512 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2513 packet->SetPointerData(0, pointer_data);
2514 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2515 self.scrollInertiaEventAppKitDeadline = 0;
2522 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2525 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2526 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2527 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2528 pointer_data.view_id =
self.viewIdentifier;
2530 switch (_hoverGestureRecognizer.state) {
2531 case UIGestureRecognizerStateBegan:
2532 pointer_data.change = flutter::PointerData::Change::kAdd;
2534 case UIGestureRecognizerStateChanged:
2535 pointer_data.change = flutter::PointerData::Change::kHover;
2537 case UIGestureRecognizerStateEnded:
2538 case UIGestureRecognizerStateCancelled:
2539 pointer_data.change = flutter::PointerData::Change::kRemove;
2544 pointer_data.change = flutter::PointerData::Change::kHover;
2548 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2549 BOOL isRunningOnMac = NO;
2550 if (@available(iOS 14.0, *)) {
2554 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2557 time >
self.scrollInertiaEventStartline) {
2561 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2562 packet->SetPointerData(0, pointer_data);
2563 flutter::PointerData inertia_cancel = pointer_data;
2564 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2565 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2566 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2567 inertia_cancel.view_id =
self.viewIdentifier;
2568 packet->SetPointerData(1, inertia_cancel);
2569 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2570 self.scrollInertiaEventStartline = DBL_MAX;
2572 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2573 packet->SetPointerData(0, pointer_data);
2574 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2578 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2579 CGPoint translation = [recognizer translationInView:self.view];
2580 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2582 translation.x *= scale;
2583 translation.y *= scale;
2585 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2586 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2587 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2588 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2589 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2590 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2591 pointer_data.view_id =
self.viewIdentifier;
2597 if (recognizer.state != UIGestureRecognizerStateEnded) {
2603 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2604 packet->SetPointerData(0, pointer_data);
2605 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2608 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2609 CGPoint translation = [recognizer translationInView:self.view];
2610 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2612 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2613 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2614 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2615 pointer_data.view_id =
self.viewIdentifier;
2616 switch (recognizer.state) {
2617 case UIGestureRecognizerStateBegan:
2618 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2620 case UIGestureRecognizerStateChanged:
2621 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2622 pointer_data.pan_x = translation.x * scale;
2623 pointer_data.pan_y = translation.y * scale;
2624 pointer_data.pan_delta_x = 0;
2625 pointer_data.pan_delta_y = 0;
2626 pointer_data.scale = 1;
2628 case UIGestureRecognizerStateEnded:
2629 case UIGestureRecognizerStateCancelled:
2630 self.scrollInertiaEventStartline =
2631 [[NSProcessInfo processInfo] systemUptime] +
2641 self.scrollInertiaEventAppKitDeadline =
2642 [[NSProcessInfo processInfo] systemUptime] +
2643 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2644 [recognizer velocityInView:self.view].y))) -
2646 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2650 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2651 (
long)recognizer.state);
2655 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2656 packet->SetPointerData(0, pointer_data);
2657 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2660 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2661 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2662 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2663 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2664 pointer_data.view_id =
self.viewIdentifier;
2665 switch (recognizer.state) {
2666 case UIGestureRecognizerStateBegan:
2667 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2669 case UIGestureRecognizerStateChanged:
2670 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2671 pointer_data.scale = recognizer.scale;
2672 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2674 case UIGestureRecognizerStateEnded:
2675 case UIGestureRecognizerStateCancelled:
2676 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2680 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2681 (
long)recognizer.state);
2685 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2686 packet->SetPointerData(0, pointer_data);
2687 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2690 #pragma mark - State Restoration
2692 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2693 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2694 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2695 length:restorationData.length
2696 forKey:kFlutterRestorationStateAppData];
2697 [
super encodeRestorableStateWithCoder:coder];
2700 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2701 NSUInteger restorationDataLength;
2702 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2703 returnedLength:&restorationDataLength];
2704 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2705 [
self.engine.restorationPlugin setRestorationData:restorationData];
2709 return self.engine.restorationPlugin;
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