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"
38 #import "flutter/shell/platform/embedder/embedder.h"
39 #import "flutter/third_party/spring_animation/spring_animation.h"
51 @"FlutterViewControllerHideHomeIndicator";
53 @"FlutterViewControllerShowHomeIndicator";
71 @property(nonatomic, readonly) int64_t viewIdentifier;
76 @property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
78 @property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
79 @property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
80 @property(nonatomic, assign) BOOL initialized;
81 @property(nonatomic, assign) BOOL engineNeedsLaunch;
82 @property(nonatomic, assign) BOOL awokenFromNib;
85 @property(nonatomic, assign) BOOL isHomeIndicatorHidden;
86 @property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
89 @property(nonatomic, assign) BOOL flutterPrefersStatusBarHidden;
91 @property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
96 @property(nonatomic, strong) UIScrollView* scrollView;
97 @property(nonatomic, strong) UIView* keyboardAnimationView;
98 @property(nonatomic, strong) SpringAnimation* keyboardSpringAnimation;
103 @property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
107 @property(nonatomic, assign) CGFloat targetViewInsetBottom;
108 @property(nonatomic, assign) CGFloat originalViewInsetBottom;
109 @property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
110 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
111 @property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
112 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
115 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
123 @property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
131 @property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
135 @property(nonatomic, assign) CGSize sizeBeforeAutoResized;
141 @property(nonatomic, strong)
144 @property(nonatomic, strong)
145 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
147 @property(nonatomic, strong)
148 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
150 @property(nonatomic, strong)
153 @property(nonatomic, strong)
154 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
157 - (void)addInternalPlugins;
158 - (void)deregisterNotifications;
161 - (void)onFirstFrameRendered;
164 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime;
168 flutter::ViewportMetrics _viewportMetrics;
173 @synthesize viewOpaque = _viewOpaque;
174 @synthesize displayingFlutterUI = _displayingFlutterUI;
179 @dynamic viewIdentifier;
181 #pragma mark - Manage and override all designated initializers
184 nibName:(nullable NSString*)nibName
185 bundle:(nullable NSBundle*)nibBundle {
186 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
187 self = [
super initWithNibName:nibName bundle:nibBundle];
190 if (
engine.viewController) {
191 NSString* errorMessage =
192 [NSString stringWithFormat:
193 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
194 "instance %@. One instance of the FlutterEngine can only be attached to "
195 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
196 "nil before attaching it to another FlutterViewController.",
197 engine.description, engine.viewController.description];
198 [FlutterLogger logError:errorMessage];
201 _engineNeedsLaunch = NO;
202 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
203 opaque:self.isViewOpaque
204 enableWideGamut:engine.project.isWideGamutEnabled];
205 _ongoingTouches = [[NSMutableSet alloc] init];
209 [
self performCommonViewControllerInitialization];
210 [engine setViewController:self];
217 nibName:(NSString*)nibName
218 bundle:(NSBundle*)nibBundle {
219 self = [
super initWithNibName:nibName bundle:nibBundle];
223 [
self sharedSetupWithProject:project initialRoute:nil];
230 initialRoute:(NSString*)initialRoute
231 nibName:(NSString*)nibName
232 bundle:(NSBundle*)nibBundle {
233 self = [
super initWithNibName:nibName bundle:nibBundle];
237 [
self sharedSetupWithProject:project initialRoute:initialRoute];
243 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
244 return [
self initWithProject:nil nibName:nil bundle:nil];
248 self = [
super initWithCoder:aDecoder];
252 - (void)awakeFromNib {
253 [
super awakeFromNib];
254 self.awokenFromNib = YES;
256 [
self sharedSetupWithProject:nil initialRoute:nil];
260 - (instancetype)init {
261 return [
self initWithProject:nil nibName:nil bundle:nil];
265 initialRoute:(nullable NSString*)initialRoute {
268 if ([appDelegate respondsToSelector:
@selector(takeLaunchEngine)]) {
273 engine = [appDelegate takeLaunchEngine];
278 [appDelegate takeLaunchEngine];
290 allowHeadlessExecution:self.engineAllowHeadlessExecution
291 restorationEnabled:self.restorationIdentifier != nil];
299 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
301 enableWideGamut:engine.project.isWideGamutEnabled];
302 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
308 BOOL performedCallback = [_engine performImplicitEngineCallback];
312 respondsToSelector:
@selector(pluginRegistrant)]) {
313 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
315 [pluginRegistrant registerWithRegistry:self];
316 performedCallback = YES;
324 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
325 [applicationLifeCycleDelegate
326 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
327 [applicationLifeCycleDelegate
328 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
331 _engineNeedsLaunch = YES;
332 _ongoingTouches = [[NSMutableSet alloc] init];
336 [
self loadDefaultSplashScreenView];
337 [
self performCommonViewControllerInitialization];
340 - (BOOL)isViewOpaque {
344 - (void)setViewOpaque:(BOOL)value {
346 if (
self.flutterView.layer.opaque != value) {
347 self.flutterView.layer.opaque = value;
348 [
self.flutterView.layer setNeedsLayout];
352 #pragma mark - Common view controller initialization tasks
354 - (void)performCommonViewControllerInitialization {
360 _orientationPreferences = UIInterfaceOrientationMaskAll;
361 _statusBarStyle = UIStatusBarStyleDefault;
363 _accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
367 [
self setUpNotificationCenterObservers];
370 - (void)setUpNotificationCenterObservers {
371 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
372 [center addObserver:self
373 selector:@selector(onOrientationPreferencesUpdated:)
374 name:@(flutter::kOrientationUpdateNotificationName)
377 [center addObserver:self
378 selector:@selector(onPreferredStatusBarStyleUpdated:)
379 name:@(flutter::kOverlayStyleUpdateNotificationName)
383 [
self setUpApplicationLifecycleNotifications:center];
385 [
self setUpSceneLifecycleNotifications:center];
388 [center addObserver:self
389 selector:@selector(keyboardWillChangeFrame:)
390 name:UIKeyboardWillChangeFrameNotification
393 [center addObserver:self
394 selector:@selector(keyboardWillShowNotification:)
395 name:UIKeyboardWillShowNotification
398 [center addObserver:self
399 selector:@selector(keyboardWillBeHidden:)
400 name:UIKeyboardWillHideNotification
403 for (NSString* notification in [
self.accessibilityFeatures observedNotificationNames]) {
404 [center addObserver:self
405 selector:@selector(onAccessibilityStatusChanged:)
410 [center addObserver:self
411 selector:@selector(onUserSettingsChanged:)
412 name:UIContentSizeCategoryDidChangeNotification
415 [center addObserver:self
416 selector:@selector(onHideHomeIndicatorNotification:)
417 name:FlutterViewControllerHideHomeIndicator
420 [center addObserver:self
421 selector:@selector(onShowHomeIndicatorNotification:)
422 name:FlutterViewControllerShowHomeIndicator
426 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
427 [center addObserver:self
428 selector:@selector(sceneBecameActive:)
429 name:UISceneDidActivateNotification
432 [center addObserver:self
433 selector:@selector(sceneWillResignActive:)
434 name:UISceneWillDeactivateNotification
437 [center addObserver:self
438 selector:@selector(sceneWillDisconnect:)
439 name:UISceneDidDisconnectNotification
442 [center addObserver:self
443 selector:@selector(sceneDidEnterBackground:)
444 name:UISceneDidEnterBackgroundNotification
447 [center addObserver:self
448 selector:@selector(sceneWillEnterForeground:)
449 name:UISceneWillEnterForegroundNotification
453 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
454 [center addObserver:self
455 selector:@selector(applicationBecameActive:)
456 name:UIApplicationDidBecomeActiveNotification
459 [center addObserver:self
460 selector:@selector(applicationWillResignActive:)
461 name:UIApplicationWillResignActiveNotification
464 [center addObserver:self
465 selector:@selector(applicationWillTerminate:)
466 name:UIApplicationWillTerminateNotification
469 [center addObserver:self
470 selector:@selector(applicationDidEnterBackground:)
471 name:UIApplicationDidEnterBackgroundNotification
474 [center addObserver:self
475 selector:@selector(applicationWillEnterForeground:)
476 name:UIApplicationWillEnterForegroundNotification
480 - (void)setInitialRoute:(NSString*)route {
481 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
485 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
488 - (void)pushRoute:(NSString*)route {
489 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
492 #pragma mark - Loading the view
494 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
496 return existing_view;
499 auto placeholder = [[UIView alloc] init];
501 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
502 placeholder.backgroundColor = UIColor.systemBackgroundColor;
503 placeholder.autoresizesSubviews = YES;
508 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
509 auto messageLabel = [[UILabel alloc] init];
510 messageLabel.numberOfLines = 0u;
511 messageLabel.textAlignment = NSTextAlignmentCenter;
512 messageLabel.autoresizingMask =
513 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
515 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
516 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
517 @"modes to enable launching from the home screen.";
518 [placeholder addSubview:messageLabel];
525 self.view = GetViewOrPlaceholder(
self.flutterView);
526 self.view.multipleTouchEnabled = YES;
527 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
529 [
self installSplashScreenViewIfNecessary];
532 UIScrollView* scrollView = [[UIScrollView alloc] init];
533 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
535 scrollView.backgroundColor = UIColor.whiteColor;
536 scrollView.delegate =
self;
542 [
self.view addSubview:scrollView];
543 self.scrollView = scrollView;
546 - (
flutter::PointerData)generatePointerDataForFake {
547 flutter::PointerData pointer_data;
548 pointer_data.Clear();
549 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
557 static void SendFakeTouchEvent(UIScreen* screen,
560 flutter::PointerData::Change change) {
561 const CGFloat scale = screen.scale;
562 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
563 pointer_data.physical_x = location.x * scale;
564 pointer_data.physical_y = location.y * scale;
565 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
566 pointer_data.change = change;
567 packet->SetPointerData(0, pointer_data);
568 [engine dispatchPointerDataPacket:std::move(packet)];
571 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
575 CGPoint statusBarPoint = CGPointZero;
576 UIScreen* screen =
self.flutterScreenIfViewLoaded;
578 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
579 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
584 #pragma mark - Managing launch views
586 - (void)installSplashScreenViewIfNecessary {
589 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
590 [
self.splashScreenView removeFromSuperview];
591 self.splashScreenView = nil;
596 UIView* splashScreenView =
self.splashScreenView;
597 if (splashScreenView == nil) {
600 splashScreenView.frame =
self.view.bounds;
601 [
self.view addSubview:splashScreenView];
604 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
608 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
609 if (_displayingFlutterUI != displayingFlutterUI) {
610 if (displayingFlutterUI == YES) {
611 if (!
self.viewIfLoaded.window) {
615 [
self willChangeValueForKey:@"displayingFlutterUI"];
616 _displayingFlutterUI = displayingFlutterUI;
617 [
self didChangeValueForKey:@"displayingFlutterUI"];
621 - (void)callViewRenderedCallback {
622 self.displayingFlutterUI = YES;
623 if (
self.flutterViewRenderedCallback) {
624 self.flutterViewRenderedCallback();
625 self.flutterViewRenderedCallback = nil;
629 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
630 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
631 UIView* splashScreen =
self.splashScreenView;
633 _splashScreenView = nil;
634 [UIView animateWithDuration:0.2
636 splashScreen.alpha = 0;
638 completion:^(BOOL finished) {
639 [splashScreen removeFromSuperview];
646 - (void)onFirstFrameRendered {
647 if (
self.splashScreenView) {
649 [
self removeSplashScreenWithCompletion:^{
650 [weakSelf callViewRenderedCallback];
653 [
self callViewRenderedCallback];
657 - (void)installFirstFrameCallback {
662 [
self.engine installFirstFrameCallback:^{
663 [weakSelf onFirstFrameRendered];
667 #pragma mark - Properties
669 - (int64_t)viewIdentifier {
672 return flutter::kFlutterImplicitViewId;
675 - (BOOL)loadDefaultSplashScreenView {
676 NSString* launchscreenName =
677 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
678 if (launchscreenName == nil) {
681 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
683 splashView = [
self splashScreenFromXib:launchscreenName];
692 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
693 UIStoryboard* storyboard = nil;
695 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
696 }
@catch (NSException* exception) {
700 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
701 return splashScreenViewController.view;
706 - (UIView*)splashScreenFromXib:(NSString*)name {
707 NSArray* objects = nil;
709 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
710 }
@catch (NSException* exception) {
713 if ([objects count] != 0) {
714 UIView* view = [objects objectAtIndex:0];
720 - (void)setSplashScreenView:(UIView*)view {
721 if (view == _splashScreenView) {
727 if (_splashScreenView) {
728 [
self removeSplashScreenWithCompletion:nil];
733 _splashScreenView = view;
734 _splashScreenView.autoresizingMask =
735 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
738 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
739 _flutterViewRenderedCallback = callback;
742 - (UISceneActivationState)activationState {
743 return self.flutterWindowSceneIfViewLoaded.activationState;
746 - (BOOL)stateIsActive {
749 BOOL isActive = flutterApplication
750 ? [
self isApplicationStateMatching:UIApplicationStateActive
751 withApplication:flutterApplication]
752 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
756 - (BOOL)stateIsBackground {
759 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
760 withApplication:flutterApplication]
761 : [
self isSceneStateMatching:UISceneActivationStateBackground];
764 - (BOOL)isApplicationStateMatching:(UIApplicationState)match
765 withApplication:(UIApplication*)application {
766 switch (application.applicationState) {
767 case UIApplicationStateActive:
768 case UIApplicationStateInactive:
769 case UIApplicationStateBackground:
770 return application.applicationState == match;
774 - (BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
775 switch (
self.activationState) {
776 case UISceneActivationStateForegroundActive:
777 case UISceneActivationStateUnattached:
778 case UISceneActivationStateForegroundInactive:
779 case UISceneActivationStateBackground:
780 return self.activationState == match;
784 #pragma mark - Surface creation and teardown updates
786 - (void)surfaceUpdated:(BOOL)appeared {
794 [
self installFirstFrameCallback];
795 self.platformViewsController.flutterView =
self.flutterView;
796 self.platformViewsController.flutterViewController =
self;
797 [
self.engine notifyViewCreated];
799 self.displayingFlutterUI = NO;
800 [
self.engine notifyViewDestroyed];
801 self.platformViewsController.flutterView = nil;
802 self.platformViewsController.flutterViewController = nil;
806 #pragma mark - UIViewController lifecycle notifications
808 - (void)viewDidLoad {
809 TRACE_EVENT0(
"flutter",
"viewDidLoad");
811 if (
self.
engine &&
self.engineNeedsLaunch) {
812 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
813 [
self.engine setViewController:self];
814 self.engineNeedsLaunch = NO;
815 }
else if (
self.
engine.viewController ==
self) {
816 [
self.engine attachView];
820 [
self addInternalPlugins];
823 [
self createTouchRateCorrectionVSyncClientIfNeeded];
825 if (@available(iOS 13.4, *)) {
826 _hoverGestureRecognizer =
827 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
828 _hoverGestureRecognizer.delegate =
self;
829 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
831 _discreteScrollingPanGestureRecognizer =
832 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
833 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
838 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
839 _discreteScrollingPanGestureRecognizer.delegate =
self;
840 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
841 _continuousScrollingPanGestureRecognizer =
842 [[UIPanGestureRecognizer alloc] initWithTarget:self
843 action:@selector(continuousScrollEvent:)];
844 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
845 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
846 _continuousScrollingPanGestureRecognizer.delegate =
self;
847 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
848 _pinchGestureRecognizer =
849 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
850 _pinchGestureRecognizer.allowedTouchTypes = @[];
851 _pinchGestureRecognizer.delegate =
self;
852 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
853 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
854 _rotationGestureRecognizer.allowedTouchTypes = @[];
855 _rotationGestureRecognizer.delegate =
self;
856 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
862 - (void)addInternalPlugins {
866 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
867 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
869 [
self.keyboardManager
873 [
self.keyboardManager addPrimaryResponder:responder];
876 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
878 if (
self.
engine.viewController ==
self) {
883 - (void)removeInternalPlugins {
884 self.keyboardManager = nil;
887 - (void)viewWillAppear:(BOOL)animated {
888 TRACE_EVENT0(
"flutter",
"viewWillAppear");
889 if (
self.
engine.viewController ==
self) {
891 [
self onUserSettingsChanged:nil];
895 if (_viewportMetrics.physical_width) {
896 [
self surfaceUpdated:YES];
898 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
899 [
self.engine.restorationPlugin markRestorationComplete];
902 [
super viewWillAppear:animated];
905 - (void)viewDidAppear:(BOOL)animated {
906 TRACE_EVENT0(
"flutter",
"viewDidAppear");
907 if (
self.
engine.viewController ==
self) {
908 [
self onUserSettingsChanged:nil];
909 [
self onAccessibilityStatusChanged:nil];
911 if (
self.stateIsActive) {
912 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
915 [
super viewDidAppear:animated];
918 - (void)viewWillDisappear:(BOOL)animated {
919 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
920 if (
self.
engine.viewController ==
self) {
921 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
923 [
super viewWillDisappear:animated];
926 - (void)viewDidDisappear:(BOOL)animated {
927 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
928 if (
self.
engine.viewController ==
self) {
929 [
self invalidateKeyboardAnimationVSyncClient];
930 [
self ensureViewportMetricsIsCorrect];
931 [
self surfaceUpdated:NO];
932 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
933 [
self flushOngoingTouches];
934 [
self.engine notifyLowMemory];
937 [
super viewDidDisappear:animated];
940 - (void)viewWillTransitionToSize:(CGSize)size
941 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
942 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
952 NSTimeInterval transitionDuration = coordinator.transitionDuration;
954 if (transitionDuration == 0) {
959 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
960 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
961 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
962 dispatch_get_main_queue(), ^{
970 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
971 [strongSelf updateViewportMetricsIfNeeded];
975 - (void)flushOngoingTouches {
976 if (
self.
engine &&
self.ongoingTouches.count > 0) {
977 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
978 size_t pointer_index = 0;
981 for (NSNumber* device in
self.ongoingTouches) {
983 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
985 pointer_data.change = flutter::PointerData::Change::kCancel;
986 pointer_data.device = device.longLongValue;
987 pointer_data.pointer_identifier = 0;
988 pointer_data.view_id =
self.viewIdentifier;
991 pointer_data.physical_x = 0;
992 pointer_data.physical_y = 0;
993 pointer_data.physical_delta_x = 0.0;
994 pointer_data.physical_delta_y = 0.0;
995 pointer_data.pressure = 1.0;
996 pointer_data.pressure_max = 1.0;
998 packet->SetPointerData(pointer_index++, pointer_data);
1001 [
self.ongoingTouches removeAllObjects];
1002 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1006 - (void)deregisterNotifications {
1007 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
1010 [[NSNotificationCenter defaultCenter] removeObserver:self];
1016 [
self removeInternalPlugins];
1017 [
self deregisterNotifications];
1019 [
self invalidateKeyboardAnimationVSyncClient];
1020 [
self invalidateTouchRateCorrectionVSyncClient];
1024 _scrollView.delegate = nil;
1025 _hoverGestureRecognizer.delegate = nil;
1026 _discreteScrollingPanGestureRecognizer.delegate = nil;
1027 _continuousScrollingPanGestureRecognizer.delegate = nil;
1028 _pinchGestureRecognizer.delegate = nil;
1029 _rotationGestureRecognizer.delegate = nil;
1032 #pragma mark - Application lifecycle notifications
1034 - (void)applicationBecameActive:(NSNotification*)notification {
1035 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
1036 [
self appOrSceneBecameActive];
1039 - (void)applicationWillResignActive:(NSNotification*)notification {
1040 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1041 [
self appOrSceneWillResignActive];
1044 - (void)applicationWillTerminate:(NSNotification*)notification {
1045 [
self appOrSceneWillTerminate];
1048 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1049 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1050 [
self appOrSceneDidEnterBackground];
1053 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1054 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1055 [
self appOrSceneWillEnterForeground];
1058 #pragma mark - Scene lifecycle notifications
1060 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1061 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1062 [
self appOrSceneBecameActive];
1065 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1066 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1067 [
self appOrSceneWillResignActive];
1070 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1071 [
self appOrSceneWillTerminate];
1074 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1075 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1076 [
self appOrSceneDidEnterBackground];
1079 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1080 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1081 [
self appOrSceneWillEnterForeground];
1084 #pragma mark - Lifecycle shared
1086 - (void)appOrSceneBecameActive {
1087 self.isKeyboardInOrTransitioningFromBackground = NO;
1088 if (_viewportMetrics.physical_width) {
1089 [
self surfaceUpdated:YES];
1091 [
self performSelector:@selector(goToApplicationLifecycle:)
1092 withObject:@"AppLifecycleState.resumed"
1096 - (void)appOrSceneWillResignActive {
1097 [NSObject cancelPreviousPerformRequestsWithTarget:self
1098 selector:@selector(goToApplicationLifecycle:)
1099 object:@"AppLifecycleState.resumed"];
1100 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1103 - (void)appOrSceneWillTerminate {
1104 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1105 [
self.engine destroyContext];
1108 - (void)appOrSceneDidEnterBackground {
1109 self.isKeyboardInOrTransitioningFromBackground = YES;
1110 [
self surfaceUpdated:NO];
1111 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1114 - (void)appOrSceneWillEnterForeground {
1115 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1119 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1122 if (
self.viewIfLoaded.window) {
1123 [
self.engine.lifecycleChannel sendMessage:state];
1127 #pragma mark - Touch event handling
1129 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1131 case UITouchPhaseBegan:
1132 return flutter::PointerData::Change::kDown;
1133 case UITouchPhaseMoved:
1134 case UITouchPhaseStationary:
1137 return flutter::PointerData::Change::kMove;
1138 case UITouchPhaseEnded:
1139 return flutter::PointerData::Change::kUp;
1140 case UITouchPhaseCancelled:
1141 return flutter::PointerData::Change::kCancel;
1144 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1148 return flutter::PointerData::Change::kCancel;
1151 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1152 switch (touch.type) {
1153 case UITouchTypeDirect:
1154 case UITouchTypeIndirect:
1155 return flutter::PointerData::DeviceKind::kTouch;
1156 case UITouchTypeStylus:
1157 return flutter::PointerData::DeviceKind::kStylus;
1158 case UITouchTypeIndirectPointer:
1159 return flutter::PointerData::DeviceKind::kMouse;
1161 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1165 return flutter::PointerData::DeviceKind::kTouch;
1172 - (void)dispatchTouches:(NSSet*)touches
1173 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1174 event:(UIEvent*)event {
1199 NSUInteger touches_to_remove_count = 0;
1200 for (UITouch* touch in touches) {
1201 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1202 touches_to_remove_count++;
1207 [
self triggerTouchRateCorrectionIfNeeded:touches];
1209 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1211 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1213 size_t pointer_index = 0;
1215 for (UITouch* touch in touches) {
1216 CGPoint windowCoordinates = [touch locationInView:self.view];
1218 flutter::PointerData pointer_data;
1219 pointer_data.Clear();
1224 pointer_data.change = overridden_change !=
nullptr
1225 ? *overridden_change
1226 : PointerDataChangeFromUITouchPhase(touch.phase);
1228 pointer_data.kind = DeviceKindFromTouchType(touch);
1230 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1232 pointer_data.view_id =
self.viewIdentifier;
1235 pointer_data.pointer_identifier = 0;
1237 pointer_data.physical_x = windowCoordinates.x * scale;
1238 pointer_data.physical_y = windowCoordinates.y * scale;
1241 pointer_data.physical_delta_x = 0.0;
1242 pointer_data.physical_delta_y = 0.0;
1244 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1247 switch (pointer_data.change) {
1248 case flutter::PointerData::Change::kDown:
1249 [
self.ongoingTouches addObject:deviceKey];
1251 case flutter::PointerData::Change::kCancel:
1252 case flutter::PointerData::Change::kUp:
1253 [
self.ongoingTouches removeObject:deviceKey];
1255 case flutter::PointerData::Change::kHover:
1256 case flutter::PointerData::Change::kMove:
1259 case flutter::PointerData::Change::kAdd:
1260 case flutter::PointerData::Change::kRemove:
1263 case flutter::PointerData::Change::kPanZoomStart:
1264 case flutter::PointerData::Change::kPanZoomUpdate:
1265 case flutter::PointerData::Change::kPanZoomEnd:
1271 pointer_data.pressure = touch.force;
1272 pointer_data.pressure_max = touch.maximumPossibleForce;
1273 pointer_data.radius_major = touch.majorRadius;
1274 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1275 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1290 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1310 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1312 if (@available(iOS 13.4, *)) {
1313 if (event !=
nullptr) {
1314 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1315 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1317 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1318 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1323 packet->SetPointerData(pointer_index++, pointer_data);
1325 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1326 flutter::PointerData remove_pointer_data = pointer_data;
1327 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1328 packet->SetPointerData(pointer_index++, remove_pointer_data);
1332 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1335 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1336 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1339 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1340 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1343 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1344 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1347 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1348 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1351 - (void)forceTouchesCancelled:(NSSet*)touches {
1352 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1353 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1356 #pragma mark - Touch events rate correction
1358 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1359 if (_touchRateCorrectionVSyncClient != nil) {
1364 const double epsilon = 0.1;
1365 if (displayRefreshRate < 60.0 + epsilon) {
1373 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1376 _touchRateCorrectionVSyncClient =
1377 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1378 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1381 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1382 if (_touchRateCorrectionVSyncClient == nil) {
1390 BOOL isUserInteracting = NO;
1391 for (UITouch* touch in touches) {
1392 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1393 isUserInteracting = YES;
1398 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1399 [_touchRateCorrectionVSyncClient await];
1401 [_touchRateCorrectionVSyncClient pause];
1405 - (void)invalidateTouchRateCorrectionVSyncClient {
1406 [_touchRateCorrectionVSyncClient invalidate];
1407 _touchRateCorrectionVSyncClient = nil;
1410 #pragma mark - Handle view resizing
1412 - (void)updateViewportMetricsIfNeeded {
1413 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1416 if (
self.
engine.viewController ==
self) {
1417 [
self.engine updateViewportMetrics:_viewportMetrics];
1421 - (void)viewDidLayoutSubviews {
1422 CGRect viewBounds =
self.view.bounds;
1423 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1426 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1430 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1431 _viewportMetrics.device_pixel_ratio = scale;
1432 [
self setViewportMetricsSize];
1433 [
self checkAndUpdateAutoResizeConstraints];
1434 [
self setViewportMetricsPaddings];
1435 [
self updateViewportMetricsIfNeeded];
1442 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.
engine) {
1443 [
self surfaceUpdated:YES];
1444 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1445 NSTimeInterval timeout = 0.2;
1447 NSTimeInterval timeout = 0.1;
1450 waitForFirstFrameSync:timeout
1451 callback:^(BOOL didTimeout) {
1453 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1454 "This may happen in unoptimized builds. If this is"
1455 "a release build, you should load a less complex "
1456 "frame to avoid the timeout."];
1462 - (BOOL)isAutoResizable {
1463 return self.flutterView.autoResizable;
1466 - (void)setAutoResizable:(BOOL)value {
1467 self.flutterView.autoResizable = value;
1468 self.flutterView.contentMode = UIViewContentModeCenter;
1471 - (void)checkAndUpdateAutoResizeConstraints {
1472 if (!
self.isAutoResizable) {
1476 [
self updateAutoResizeConstraints];
1500 - (void)updateAutoResizeConstraints {
1501 BOOL hasBeenAutoResized = NO;
1502 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1504 hasBeenAutoResized = YES;
1508 if (!hasBeenAutoResized) {
1509 self.sizeBeforeAutoResized =
self.view.frame.size;
1512 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1513 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1514 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1515 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1519 if (maxWidth == 0) {
1520 maxWidth = CGFLOAT_MAX;
1523 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1524 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1525 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1526 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1527 @"constraints (e.g., using SizedBox or Container)."];
1529 if (maxHeight == 0) {
1530 maxHeight = CGFLOAT_MAX;
1533 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1534 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1535 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1536 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1537 @"constraints (e.g., using SizedBox or Container)."];
1539 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1540 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1541 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1542 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1545 - (void)viewSafeAreaInsetsDidChange {
1546 [
self setViewportMetricsPaddings];
1547 [
self updateViewportMetricsIfNeeded];
1548 [
super viewSafeAreaInsetsDidChange];
1552 - (void)setViewportMetricsSize {
1553 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1558 CGFloat scale = screen.scale;
1559 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1560 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1562 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1563 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1564 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1565 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1571 - (void)setViewportMetricsPaddings {
1572 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1577 CGFloat scale = screen.scale;
1578 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1579 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1580 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1581 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1584 #pragma mark - Keyboard events
1586 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1591 [
self handleKeyboardNotification:notification];
1594 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1599 [
self handleKeyboardNotification:notification];
1602 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1606 [
self handleKeyboardNotification:notification];
1609 - (void)handleKeyboardNotification:(NSNotification*)notification {
1612 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1616 NSDictionary* info = notification.userInfo;
1617 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1618 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1619 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1620 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1621 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1628 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1629 [
self hideKeyboardImmediately];
1634 if (
self.targetViewInsetBottom == calculatedInset) {
1638 self.targetViewInsetBottom = calculatedInset;
1645 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1646 BOOL keyboardAnimationIsCompounding =
1647 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1650 self.keyboardAnimationIsShowing = keyboardWillShow;
1652 if (!keyboardAnimationIsCompounding) {
1653 [
self startKeyBoardAnimation:duration];
1654 }
else if (
self.keyboardSpringAnimation) {
1655 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1659 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1664 if (notification.name == UIKeyboardWillHideNotification) {
1673 NSDictionary* info = notification.userInfo;
1674 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1675 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1676 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1682 if (CGRectIsEmpty(keyboardFrame)) {
1687 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1693 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1694 NSDictionary* info = notification.userInfo;
1698 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1699 if (isLocal && ![isLocal boolValue]) {
1702 return self.engine.viewController !=
self;
1705 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1713 NSDictionary* info = notification.userInfo;
1714 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1716 if (notification.name == UIKeyboardWillHideNotification) {
1717 return FlutterKeyboardModeHidden;
1722 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1723 return FlutterKeyboardModeFloating;
1726 if (CGRectIsEmpty(keyboardFrame)) {
1727 return FlutterKeyboardModeHidden;
1730 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1731 CGRect adjustedKeyboardFrame = keyboardFrame;
1732 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1733 keyboardFrame:keyboardFrame];
1738 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1739 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1740 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1741 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1743 CGFloat screenHeight = CGRectGetHeight(screenRect);
1744 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1745 if (round(adjustedKeyboardBottom) < screenHeight) {
1746 return FlutterKeyboardModeFloating;
1748 return FlutterKeyboardModeDocked;
1750 return FlutterKeyboardModeHidden;
1753 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1757 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1758 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1759 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1760 CGFloat screenHeight = CGRectGetHeight(screenRect);
1761 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1765 if (screenHeight == keyboardBottom) {
1768 CGRect viewRectRelativeToScreen =
1769 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1770 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1771 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1772 CGFloat offset = screenHeight - viewBottom;
1780 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1782 if (keyboardMode == FlutterKeyboardModeDocked) {
1784 CGRect viewRectRelativeToScreen =
1785 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1786 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1787 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1788 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1793 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1794 return portionOfKeyboardInView * scale;
1799 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1801 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1807 if (!
self.keyboardAnimationView) {
1808 UIView* keyboardAnimationView = [[UIView alloc] init];
1809 keyboardAnimationView.hidden = YES;
1810 self.keyboardAnimationView = keyboardAnimationView;
1813 if (!
self.keyboardAnimationView.superview) {
1814 [
self.view addSubview:self.keyboardAnimationView];
1818 [
self.keyboardAnimationView.layer removeAllAnimations];
1821 self.keyboardAnimationView.frame =
1822 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1823 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1824 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1827 [
self invalidateKeyboardAnimationVSyncClient];
1830 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1831 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1833 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1835 [UIView animateWithDuration:duration
1843 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1846 CAAnimation* keyboardAnimation =
1847 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1848 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1850 completion:^(BOOL finished) {
1851 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1860 [strongSelf invalidateKeyboardAnimationVSyncClient];
1861 [strongSelf removeKeyboardAnimationView];
1862 [strongSelf ensureViewportMetricsIsCorrect];
1867 - (void)hideKeyboardImmediately {
1868 [
self invalidateKeyboardAnimationVSyncClient];
1869 if (
self.keyboardAnimationView) {
1870 [
self.keyboardAnimationView.layer removeAllAnimations];
1871 [
self removeKeyboardAnimationView];
1872 self.keyboardAnimationView = nil;
1874 if (
self.keyboardSpringAnimation) {
1875 self.keyboardSpringAnimation = nil;
1878 self.targetViewInsetBottom = 0.0;
1879 [
self ensureViewportMetricsIsCorrect];
1882 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1884 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1885 _keyboardSpringAnimation = nil;
1890 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1891 _keyboardSpringAnimation =
1892 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1893 damping:keyboardCASpringAnimation.damping
1894 mass:keyboardCASpringAnimation.mass
1895 initialVelocity:keyboardCASpringAnimation.initialVelocity
1896 fromValue:self.originalViewInsetBottom
1897 toValue:self.targetViewInsetBottom];
1900 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1902 if (!
self.isViewLoaded) {
1907 if (!
self.keyboardAnimationView) {
1912 if (!
self.keyboardAnimationVSyncClient) {
1916 if (!
self.keyboardAnimationView.superview) {
1918 [
self.view addSubview:self.keyboardAnimationView];
1921 if (!
self.keyboardSpringAnimation) {
1922 if (
self.keyboardAnimationView.layer.presentationLayer) {
1923 self->_viewportMetrics.physical_view_inset_bottom =
1924 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1925 [
self updateViewportMetricsIfNeeded];
1928 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1929 self->_viewportMetrics.physical_view_inset_bottom =
1930 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1931 [
self updateViewportMetricsIfNeeded];
1935 - (void)setUpKeyboardAnimationVsyncClient:
1937 if (!keyboardAnimationCallback) {
1940 NSAssert(_keyboardAnimationVSyncClient == nil,
1941 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1945 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1946 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1947 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1948 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1949 animationCallback(targetTime);
1953 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1954 callback:uiCallback];
1955 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1956 [_keyboardAnimationVSyncClient await];
1959 - (void)invalidateKeyboardAnimationVSyncClient {
1960 [_keyboardAnimationVSyncClient invalidate];
1961 _keyboardAnimationVSyncClient = nil;
1964 - (void)removeKeyboardAnimationView {
1965 if (
self.keyboardAnimationView.superview != nil) {
1966 [
self.keyboardAnimationView removeFromSuperview];
1970 - (void)ensureViewportMetricsIsCorrect {
1971 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1973 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1974 [
self updateViewportMetricsIfNeeded];
1978 - (void)handlePressEvent:(FlutterUIPressProxy*)press
1979 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1980 if (@available(iOS 13.4, *)) {
1985 [
self.keyboardManager handlePress:press nextAction:next];
2001 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2002 [
super pressesBegan:presses withEvent:event];
2005 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2006 [
super pressesChanged:presses withEvent:event];
2009 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2010 [
super pressesEnded:presses withEvent:event];
2013 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2014 [
super pressesCancelled:presses withEvent:event];
2022 - (void)pressesBegan:(NSSet<UIPress*>*)presses
2023 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2024 if (@available(iOS 13.4, *)) {
2026 for (UIPress* press in presses) {
2027 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2029 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
2033 [
super pressesBegan:presses withEvent:event];
2037 - (void)pressesChanged:(NSSet<UIPress*>*)presses
2038 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2039 if (@available(iOS 13.4, *)) {
2041 for (UIPress* press in presses) {
2042 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2044 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
2048 [
super pressesChanged:presses withEvent:event];
2052 - (void)pressesEnded:(NSSet<UIPress*>*)presses
2053 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2054 if (@available(iOS 13.4, *)) {
2056 for (UIPress* press in presses) {
2057 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2059 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2063 [
super pressesEnded:presses withEvent:event];
2067 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
2068 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2069 if (@available(iOS 13.4, *)) {
2071 for (UIPress* press in presses) {
2072 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2074 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2078 [
super pressesCancelled:presses withEvent:event];
2082 #pragma mark - Orientation updates
2084 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2087 dispatch_async(dispatch_get_main_queue(), ^{
2088 NSDictionary* info = notification.userInfo;
2089 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2090 if (update == nil) {
2093 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2097 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2098 API_AVAILABLE(ios(16.0)) {
2099 for (UIScene* windowScene in windowScenes) {
2100 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
2101 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2102 initWithInterfaceOrientations:self.orientationPreferences];
2103 [(UIWindowScene*)windowScene
2104 requestGeometryUpdateWithPreferences:preference
2105 errorHandler:^(NSError* error) {
2106 os_log_error(OS_LOG_DEFAULT,
2107 "Failed to change device orientation: %@", error);
2109 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2113 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2114 if (new_preferences !=
self.orientationPreferences) {
2115 self.orientationPreferences = new_preferences;
2117 if (@available(iOS 16.0, *)) {
2119 NSSet<UIScene*>* scenes = [NSSet set];
2120 if (flutterApplication) {
2121 scenes = [flutterApplication.connectedScenes
2122 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2123 id scene, NSDictionary* bindings) {
2124 return [scene isKindOfClass:[UIWindowScene class]];
2126 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2127 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2129 [
self requestGeometryUpdateForWindowScenes:scenes];
2131 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2132 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2136 @"Accessing the interface orientation when the window scene is unavailable."];
2139 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2140 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2141 [UIViewController attemptRotationToDeviceOrientation];
2143 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2147 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2148 forKey:@"orientation"];
2149 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2150 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2151 forKey:@"orientation"];
2152 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2153 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2154 forKey:@"orientation"];
2155 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2156 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2157 forKey:@"orientation"];
2164 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2165 self.isHomeIndicatorHidden = YES;
2168 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2169 self.isHomeIndicatorHidden = NO;
2172 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2173 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2174 _isHomeIndicatorHidden = hideHomeIndicator;
2175 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2179 - (BOOL)prefersHomeIndicatorAutoHidden {
2180 return self.isHomeIndicatorHidden;
2183 - (BOOL)shouldAutorotate {
2187 - (NSUInteger)supportedInterfaceOrientations {
2188 return self.orientationPreferences;
2191 #pragma mark - Accessibility
2193 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2198 int32_t flags = [
self.accessibilityFeatures flags];
2199 #if TARGET_OS_SIMULATOR
2205 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
2206 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
2207 [
self.accessibilityFeatures isSpeakScreenEnabled];
2209 [
self.engine enableSemantics:enabled withFlags:flags];
2212 - (BOOL)accessibilityPerformEscape {
2214 if (navigationChannel) {
2221 #pragma mark - Set user settings
2223 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2224 [
super traitCollectionDidChange:previousTraitCollection];
2225 [
self onUserSettingsChanged:nil];
2229 if (
self.isAutoResizable) {
2230 [
self.flutterView resetIntrinsicContentSize];
2234 - (void)onUserSettingsChanged:(NSNotification*)notification {
2235 [
self.engine.settingsChannel sendMessage:@{
2236 @"textScaleFactor" : @(
self.textScaleFactor),
2238 @"platformBrightness" :
self.brightnessMode,
2239 @"platformContrast" : self.contrastMode,
2240 @"nativeSpellCheckServiceDefined" : @YES,
2241 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2245 - (CGFloat)textScaleFactor {
2247 if (flutterApplication == nil) {
2248 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2252 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2258 const CGFloat xs = 14;
2259 const CGFloat s = 15;
2260 const CGFloat m = 16;
2261 const CGFloat l = 17;
2262 const CGFloat xl = 19;
2263 const CGFloat xxl = 21;
2264 const CGFloat xxxl = 23;
2267 const CGFloat ax1 = 28;
2268 const CGFloat ax2 = 33;
2269 const CGFloat ax3 = 40;
2270 const CGFloat ax4 = 47;
2271 const CGFloat ax5 = 53;
2275 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2277 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2279 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2281 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2283 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2285 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2287 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2289 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2291 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2293 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2295 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2297 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2304 - (BOOL)supportsShowingSystemContextMenu {
2305 if (@available(iOS 16.0, *)) {
2315 - (NSString*)brightnessMode {
2316 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2318 if (style == UIUserInterfaceStyleDark) {
2328 - (NSString*)contrastMode {
2329 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2331 if (contrast == UIAccessibilityContrastHigh) {
2338 #pragma mark - Status bar style
2340 - (UIStatusBarStyle)preferredStatusBarStyle {
2341 return self.statusBarStyle;
2344 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2347 dispatch_async(dispatch_get_main_queue(), ^{
2353 NSDictionary* info = notification.userInfo;
2354 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2355 if (update == nil) {
2359 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2360 if (style != strongSelf.statusBarStyle) {
2361 strongSelf.statusBarStyle = style;
2362 [strongSelf setNeedsStatusBarAppearanceUpdate];
2367 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2368 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2369 self.flutterPrefersStatusBarHidden = hidden;
2370 [
self setNeedsStatusBarAppearanceUpdate];
2374 - (BOOL)prefersStatusBarHidden {
2375 return self.flutterPrefersStatusBarHidden;
2378 #pragma mark - Platform views
2381 return self.engine.platformViewsController;
2385 return self.engine.binaryMessenger;
2388 #pragma mark - FlutterBinaryMessenger
2390 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2391 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2394 - (void)sendOnChannel:(NSString*)channel
2395 message:(NSData*)message
2397 NSAssert(channel,
@"The channel must not be null");
2398 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2402 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2406 binaryMessageHandler:
2408 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2412 setMessageHandlerOnChannel:(NSString*)channel
2415 NSAssert(channel,
@"The channel must not be null");
2416 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2417 binaryMessageHandler:handler
2418 taskQueue:taskQueue];
2422 [
self.engine.binaryMessenger cleanUpConnection:connection];
2425 #pragma mark - FlutterTextureRegistry
2428 return [
self.engine.textureRegistry registerTexture:texture];
2431 - (void)unregisterTexture:(int64_t)textureId {
2432 [
self.engine.textureRegistry unregisterTexture:textureId];
2435 - (void)textureFrameAvailable:(int64_t)textureId {
2436 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2439 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2443 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2447 - (id<FlutterPluginRegistry>)pluginRegistry {
2451 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2452 return UIAccessibilityIsVoiceOverRunning();
2455 #pragma mark - FlutterPluginRegistry
2458 return [
self.engine registrarForPlugin:pluginKey];
2461 - (BOOL)hasPlugin:(NSString*)pluginKey {
2462 return [
self.engine hasPlugin:pluginKey];
2465 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2466 return [
self.engine valuePublishedByPlugin:pluginKey];
2469 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2471 completion:(
void (^)(
void))completion {
2472 self.isPresentingViewControllerAnimating = YES;
2474 [
super presentViewController:viewControllerToPresent
2477 weakSelf.isPresentingViewControllerAnimating = NO;
2484 - (BOOL)isPresentingViewController {
2485 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2488 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2489 API_AVAILABLE(ios(13.4)) {
2490 CGPoint location = [gestureRecognizer locationInView:self.view];
2491 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2493 flutter::PointerData pointer_data;
2494 pointer_data.Clear();
2498 return pointer_data;
2501 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2502 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2503 API_AVAILABLE(ios(13.4)) {
2507 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2508 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2509 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2510 event.type == UIEventTypeScroll) {
2512 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2513 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2514 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2515 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2516 pointer_data.view_id =
self.viewIdentifier;
2518 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2521 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2522 packet->SetPointerData(0, pointer_data);
2523 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2524 self.scrollInertiaEventAppKitDeadline = 0;
2531 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2534 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2535 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2536 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2537 pointer_data.view_id =
self.viewIdentifier;
2539 switch (_hoverGestureRecognizer.state) {
2540 case UIGestureRecognizerStateBegan:
2541 pointer_data.change = flutter::PointerData::Change::kAdd;
2543 case UIGestureRecognizerStateChanged:
2544 pointer_data.change = flutter::PointerData::Change::kHover;
2546 case UIGestureRecognizerStateEnded:
2547 case UIGestureRecognizerStateCancelled:
2548 pointer_data.change = flutter::PointerData::Change::kRemove;
2553 pointer_data.change = flutter::PointerData::Change::kHover;
2557 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2558 BOOL isRunningOnMac = NO;
2559 if (@available(iOS 14.0, *)) {
2563 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2566 time >
self.scrollInertiaEventStartline) {
2570 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2571 packet->SetPointerData(0, pointer_data);
2572 flutter::PointerData inertia_cancel = pointer_data;
2573 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2574 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2575 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2576 inertia_cancel.view_id =
self.viewIdentifier;
2577 packet->SetPointerData(1, inertia_cancel);
2578 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2579 self.scrollInertiaEventStartline = DBL_MAX;
2581 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2582 packet->SetPointerData(0, pointer_data);
2583 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2587 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2588 CGPoint translation = [recognizer translationInView:self.view];
2589 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2591 translation.x *= scale;
2592 translation.y *= scale;
2594 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2595 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2596 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2597 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2598 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2599 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2600 pointer_data.view_id =
self.viewIdentifier;
2606 if (recognizer.state != UIGestureRecognizerStateEnded) {
2612 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2613 packet->SetPointerData(0, pointer_data);
2614 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2617 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2618 CGPoint translation = [recognizer translationInView:self.view];
2619 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2621 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2622 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2623 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2624 pointer_data.view_id =
self.viewIdentifier;
2625 switch (recognizer.state) {
2626 case UIGestureRecognizerStateBegan:
2627 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2629 case UIGestureRecognizerStateChanged:
2630 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2631 pointer_data.pan_x = translation.x * scale;
2632 pointer_data.pan_y = translation.y * scale;
2633 pointer_data.pan_delta_x = 0;
2634 pointer_data.pan_delta_y = 0;
2635 pointer_data.scale = 1;
2637 case UIGestureRecognizerStateEnded:
2638 case UIGestureRecognizerStateCancelled:
2639 self.scrollInertiaEventStartline =
2640 [[NSProcessInfo processInfo] systemUptime] +
2650 self.scrollInertiaEventAppKitDeadline =
2651 [[NSProcessInfo processInfo] systemUptime] +
2652 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2653 [recognizer velocityInView:self.view].y))) -
2655 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2659 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2660 (
long)recognizer.state);
2664 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2665 packet->SetPointerData(0, pointer_data);
2666 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2669 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2670 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2671 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2672 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2673 pointer_data.view_id =
self.viewIdentifier;
2674 switch (recognizer.state) {
2675 case UIGestureRecognizerStateBegan:
2676 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2678 case UIGestureRecognizerStateChanged:
2679 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2680 pointer_data.scale = recognizer.scale;
2681 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2683 case UIGestureRecognizerStateEnded:
2684 case UIGestureRecognizerStateCancelled:
2685 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2689 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2690 (
long)recognizer.state);
2694 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2695 packet->SetPointerData(0, pointer_data);
2696 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2699 #pragma mark - State Restoration
2701 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2702 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2703 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2704 length:restorationData.length
2705 forKey:kFlutterRestorationStateAppData];
2706 [
super encodeRestorableStateWithCoder:coder];
2709 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2710 NSUInteger restorationDataLength;
2711 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2712 returnedLength:&restorationDataLength];
2713 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2714 [
self.engine.restorationPlugin setRestorationData:restorationData];
2718 return self.engine.restorationPlugin;
2722 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