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;
365 [
self setUpNotificationCenterObservers];
368 - (void)setUpNotificationCenterObservers {
369 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
370 [center addObserver:self
371 selector:@selector(onOrientationPreferencesUpdated:)
372 name:@(flutter::kOrientationUpdateNotificationName)
375 [center addObserver:self
376 selector:@selector(onPreferredStatusBarStyleUpdated:)
377 name:@(flutter::kOverlayStyleUpdateNotificationName)
381 [
self setUpApplicationLifecycleNotifications:center];
383 [
self setUpSceneLifecycleNotifications:center];
386 [center addObserver:self
387 selector:@selector(keyboardWillChangeFrame:)
388 name:UIKeyboardWillChangeFrameNotification
391 [center addObserver:self
392 selector:@selector(keyboardWillShowNotification:)
393 name:UIKeyboardWillShowNotification
396 [center addObserver:self
397 selector:@selector(keyboardWillBeHidden:)
398 name:UIKeyboardWillHideNotification
401 [center addObserver:self
402 selector:@selector(onAccessibilityStatusChanged:)
403 name:UIAccessibilityVoiceOverStatusDidChangeNotification
406 [center addObserver:self
407 selector:@selector(onAccessibilityStatusChanged:)
408 name:UIAccessibilitySwitchControlStatusDidChangeNotification
411 [center addObserver:self
412 selector:@selector(onAccessibilityStatusChanged:)
413 name:UIAccessibilitySpeakScreenStatusDidChangeNotification
416 [center addObserver:self
417 selector:@selector(onAccessibilityStatusChanged:)
418 name:UIAccessibilityInvertColorsStatusDidChangeNotification
421 [center addObserver:self
422 selector:@selector(onAccessibilityStatusChanged:)
423 name:UIAccessibilityReduceMotionStatusDidChangeNotification
426 [center addObserver:self
427 selector:@selector(onAccessibilityStatusChanged:)
428 name:UIAccessibilityBoldTextStatusDidChangeNotification
431 [center addObserver:self
432 selector:@selector(onAccessibilityStatusChanged:)
433 name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification
436 [center addObserver:self
437 selector:@selector(onAccessibilityStatusChanged:)
438 name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification
441 [center addObserver:self
442 selector:@selector(onUserSettingsChanged:)
443 name:UIContentSizeCategoryDidChangeNotification
446 [center addObserver:self
447 selector:@selector(onHideHomeIndicatorNotification:)
448 name:FlutterViewControllerHideHomeIndicator
451 [center addObserver:self
452 selector:@selector(onShowHomeIndicatorNotification:)
453 name:FlutterViewControllerShowHomeIndicator
457 - (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
458 [center addObserver:self
459 selector:@selector(sceneBecameActive:)
460 name:UISceneDidActivateNotification
463 [center addObserver:self
464 selector:@selector(sceneWillResignActive:)
465 name:UISceneWillDeactivateNotification
468 [center addObserver:self
469 selector:@selector(sceneWillDisconnect:)
470 name:UISceneDidDisconnectNotification
473 [center addObserver:self
474 selector:@selector(sceneDidEnterBackground:)
475 name:UISceneDidEnterBackgroundNotification
478 [center addObserver:self
479 selector:@selector(sceneWillEnterForeground:)
480 name:UISceneWillEnterForegroundNotification
484 - (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
485 [center addObserver:self
486 selector:@selector(applicationBecameActive:)
487 name:UIApplicationDidBecomeActiveNotification
490 [center addObserver:self
491 selector:@selector(applicationWillResignActive:)
492 name:UIApplicationWillResignActiveNotification
495 [center addObserver:self
496 selector:@selector(applicationWillTerminate:)
497 name:UIApplicationWillTerminateNotification
500 [center addObserver:self
501 selector:@selector(applicationDidEnterBackground:)
502 name:UIApplicationDidEnterBackgroundNotification
505 [center addObserver:self
506 selector:@selector(applicationWillEnterForeground:)
507 name:UIApplicationWillEnterForegroundNotification
511 - (void)setInitialRoute:(NSString*)route {
512 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
516 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
519 - (void)pushRoute:(NSString*)route {
520 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
523 #pragma mark - Loading the view
525 static UIView* GetViewOrPlaceholder(UIView* existing_view) {
527 return existing_view;
530 auto placeholder = [[UIView alloc] init];
532 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
533 placeholder.backgroundColor = UIColor.systemBackgroundColor;
534 placeholder.autoresizesSubviews = YES;
539 if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
540 auto messageLabel = [[UILabel alloc] init];
541 messageLabel.numberOfLines = 0u;
542 messageLabel.textAlignment = NSTextAlignmentCenter;
543 messageLabel.autoresizingMask =
544 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
546 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
547 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
548 @"modes to enable launching from the home screen.";
549 [placeholder addSubview:messageLabel];
556 self.view = GetViewOrPlaceholder(
self.flutterView);
557 self.view.multipleTouchEnabled = YES;
558 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
560 [
self installSplashScreenViewIfNecessary];
563 UIScrollView* scrollView = [[UIScrollView alloc] init];
564 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
566 scrollView.backgroundColor = UIColor.whiteColor;
567 scrollView.delegate =
self;
573 [
self.view addSubview:scrollView];
574 self.scrollView = scrollView;
577 - (
flutter::PointerData)generatePointerDataForFake {
578 flutter::PointerData pointer_data;
579 pointer_data.Clear();
580 pointer_data.kind = flutter::PointerData::DeviceKind::kTouch;
588 static void SendFakeTouchEvent(UIScreen* screen,
591 flutter::PointerData::Change change) {
592 const CGFloat scale = screen.scale;
593 flutter::PointerData pointer_data = [[engine
viewController] generatePointerDataForFake];
594 pointer_data.physical_x = location.x * scale;
595 pointer_data.physical_y = location.y * scale;
596 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
597 pointer_data.change = change;
598 packet->SetPointerData(0, pointer_data);
599 [engine dispatchPointerDataPacket:std::move(packet)];
602 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
606 CGPoint statusBarPoint = CGPointZero;
607 UIScreen* screen =
self.flutterScreenIfViewLoaded;
609 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kDown);
610 SendFakeTouchEvent(screen,
self.
engine, statusBarPoint, flutter::PointerData::Change::kUp);
615 #pragma mark - Managing launch views
617 - (void)installSplashScreenViewIfNecessary {
620 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
621 [
self.splashScreenView removeFromSuperview];
622 self.splashScreenView = nil;
627 UIView* splashScreenView =
self.splashScreenView;
628 if (splashScreenView == nil) {
631 splashScreenView.frame =
self.view.bounds;
632 [
self.view addSubview:splashScreenView];
635 + (BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
639 - (void)setDisplayingFlutterUI:(BOOL)displayingFlutterUI {
640 if (_displayingFlutterUI != displayingFlutterUI) {
641 if (displayingFlutterUI == YES) {
642 if (!
self.viewIfLoaded.window) {
646 [
self willChangeValueForKey:@"displayingFlutterUI"];
647 _displayingFlutterUI = displayingFlutterUI;
648 [
self didChangeValueForKey:@"displayingFlutterUI"];
652 - (void)callViewRenderedCallback {
653 self.displayingFlutterUI = YES;
654 if (
self.flutterViewRenderedCallback) {
655 self.flutterViewRenderedCallback();
656 self.flutterViewRenderedCallback = nil;
660 - (void)removeSplashScreenWithCompletion:(dispatch_block_t _Nullable)onComplete {
661 NSAssert(
self.splashScreenView,
@"The splash screen view must not be nil");
662 UIView* splashScreen =
self.splashScreenView;
664 _splashScreenView = nil;
665 [UIView animateWithDuration:0.2
667 splashScreen.alpha = 0;
669 completion:^(BOOL finished) {
670 [splashScreen removeFromSuperview];
677 - (void)onFirstFrameRendered {
678 if (
self.splashScreenView) {
680 [
self removeSplashScreenWithCompletion:^{
681 [weakSelf callViewRenderedCallback];
684 [
self callViewRenderedCallback];
688 - (void)installFirstFrameCallback {
693 [
self.engine installFirstFrameCallback:^{
694 [weakSelf onFirstFrameRendered];
698 #pragma mark - Properties
700 - (int64_t)viewIdentifier {
703 return flutter::kFlutterImplicitViewId;
706 - (BOOL)loadDefaultSplashScreenView {
707 NSString* launchscreenName =
708 [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UILaunchStoryboardName"];
709 if (launchscreenName == nil) {
712 UIView* splashView = [
self splashScreenFromStoryboard:launchscreenName];
714 splashView = [
self splashScreenFromXib:launchscreenName];
723 - (UIView*)splashScreenFromStoryboard:(NSString*)name {
724 UIStoryboard* storyboard = nil;
726 storyboard = [UIStoryboard storyboardWithName:name bundle:nil];
727 }
@catch (NSException* exception) {
731 UIViewController* splashScreenViewController = [storyboard instantiateInitialViewController];
732 return splashScreenViewController.view;
737 - (UIView*)splashScreenFromXib:(NSString*)name {
738 NSArray* objects = nil;
740 objects = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
741 }
@catch (NSException* exception) {
744 if ([objects count] != 0) {
745 UIView* view = [objects objectAtIndex:0];
751 - (void)setSplashScreenView:(UIView*)view {
752 if (view == _splashScreenView) {
758 if (_splashScreenView) {
759 [
self removeSplashScreenWithCompletion:nil];
764 _splashScreenView = view;
765 _splashScreenView.autoresizingMask =
766 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
769 - (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
770 _flutterViewRenderedCallback = callback;
773 - (UISceneActivationState)activationState {
774 return self.flutterWindowSceneIfViewLoaded.activationState;
777 - (BOOL)stateIsActive {
780 BOOL isActive = flutterApplication
781 ? [
self isApplicationStateMatching:UIApplicationStateActive
782 withApplication:flutterApplication]
783 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
787 - (BOOL)stateIsBackground {
790 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
791 withApplication:flutterApplication]
792 : [
self isSceneStateMatching:UISceneActivationStateBackground];
795 - (BOOL)isApplicationStateMatching:(UIApplicationState)match
796 withApplication:(UIApplication*)application {
797 switch (application.applicationState) {
798 case UIApplicationStateActive:
799 case UIApplicationStateInactive:
800 case UIApplicationStateBackground:
801 return application.applicationState == match;
805 - (BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
806 switch (
self.activationState) {
807 case UISceneActivationStateForegroundActive:
808 case UISceneActivationStateUnattached:
809 case UISceneActivationStateForegroundInactive:
810 case UISceneActivationStateBackground:
811 return self.activationState == match;
815 #pragma mark - Surface creation and teardown updates
817 - (void)surfaceUpdated:(BOOL)appeared {
825 [
self installFirstFrameCallback];
826 self.platformViewsController.flutterView =
self.flutterView;
827 self.platformViewsController.flutterViewController =
self;
828 [
self.engine notifyViewCreated];
830 self.displayingFlutterUI = NO;
831 [
self.engine notifyViewDestroyed];
832 self.platformViewsController.flutterView = nil;
833 self.platformViewsController.flutterViewController = nil;
837 #pragma mark - UIViewController lifecycle notifications
839 - (void)viewDidLoad {
840 TRACE_EVENT0(
"flutter",
"viewDidLoad");
842 if (
self.
engine &&
self.engineNeedsLaunch) {
843 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
844 [
self.engine setViewController:self];
845 self.engineNeedsLaunch = NO;
846 }
else if (
self.
engine.viewController ==
self) {
847 [
self.engine attachView];
851 [
self addInternalPlugins];
854 [
self createTouchRateCorrectionVSyncClientIfNeeded];
856 if (@available(iOS 13.4, *)) {
857 _hoverGestureRecognizer =
858 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
859 _hoverGestureRecognizer.delegate =
self;
860 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
862 _discreteScrollingPanGestureRecognizer =
863 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
864 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
869 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
870 _discreteScrollingPanGestureRecognizer.delegate =
self;
871 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
872 _continuousScrollingPanGestureRecognizer =
873 [[UIPanGestureRecognizer alloc] initWithTarget:self
874 action:@selector(continuousScrollEvent:)];
875 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
876 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
877 _continuousScrollingPanGestureRecognizer.delegate =
self;
878 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
879 _pinchGestureRecognizer =
880 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
881 _pinchGestureRecognizer.allowedTouchTypes = @[];
882 _pinchGestureRecognizer.delegate =
self;
883 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
884 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
885 _rotationGestureRecognizer.allowedTouchTypes = @[];
886 _rotationGestureRecognizer.delegate =
self;
887 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
893 - (void)addInternalPlugins {
897 ^(
const FlutterKeyEvent& event, FlutterKeyEventCallback callback,
void* userData) {
898 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
900 [
self.keyboardManager
904 [
self.keyboardManager addPrimaryResponder:responder];
907 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
909 if (
self.
engine.viewController ==
self) {
914 - (void)removeInternalPlugins {
915 self.keyboardManager = nil;
918 - (void)viewWillAppear:(BOOL)animated {
919 TRACE_EVENT0(
"flutter",
"viewWillAppear");
920 if (
self.
engine.viewController ==
self) {
922 [
self onUserSettingsChanged:nil];
926 if (_viewportMetrics.physical_width) {
927 [
self surfaceUpdated:YES];
929 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
930 [
self.engine.restorationPlugin markRestorationComplete];
933 [
super viewWillAppear:animated];
936 - (void)viewDidAppear:(BOOL)animated {
937 TRACE_EVENT0(
"flutter",
"viewDidAppear");
938 if (
self.
engine.viewController ==
self) {
939 [
self onUserSettingsChanged:nil];
940 [
self onAccessibilityStatusChanged:nil];
942 if (
self.stateIsActive) {
943 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
946 [
super viewDidAppear:animated];
949 - (void)viewWillDisappear:(BOOL)animated {
950 TRACE_EVENT0(
"flutter",
"viewWillDisappear");
951 if (
self.
engine.viewController ==
self) {
952 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
954 [
super viewWillDisappear:animated];
957 - (void)viewDidDisappear:(BOOL)animated {
958 TRACE_EVENT0(
"flutter",
"viewDidDisappear");
959 if (
self.
engine.viewController ==
self) {
960 [
self invalidateKeyboardAnimationVSyncClient];
961 [
self ensureViewportMetricsIsCorrect];
962 [
self surfaceUpdated:NO];
963 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
964 [
self flushOngoingTouches];
965 [
self.engine notifyLowMemory];
968 [
super viewDidDisappear:animated];
971 - (void)viewWillTransitionToSize:(CGSize)size
972 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
973 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
983 NSTimeInterval transitionDuration = coordinator.transitionDuration;
985 if (transitionDuration == 0) {
990 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
991 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
992 static_cast<int64_t
>(transitionDuration / 2.0 * NSEC_PER_SEC)),
993 dispatch_get_main_queue(), ^{
1001 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
1002 [strongSelf updateViewportMetricsIfNeeded];
1006 - (void)flushOngoingTouches {
1007 if (
self.
engine &&
self.ongoingTouches.count > 0) {
1008 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
1009 size_t pointer_index = 0;
1012 for (NSNumber* device in
self.ongoingTouches) {
1014 flutter::PointerData pointer_data = [
self generatePointerDataForFake];
1016 pointer_data.change = flutter::PointerData::Change::kCancel;
1017 pointer_data.device = device.longLongValue;
1018 pointer_data.pointer_identifier = 0;
1019 pointer_data.view_id =
self.viewIdentifier;
1022 pointer_data.physical_x = 0;
1023 pointer_data.physical_y = 0;
1024 pointer_data.physical_delta_x = 0.0;
1025 pointer_data.physical_delta_y = 0.0;
1026 pointer_data.pressure = 1.0;
1027 pointer_data.pressure_max = 1.0;
1029 packet->SetPointerData(pointer_index++, pointer_data);
1032 [
self.ongoingTouches removeAllObjects];
1033 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1037 - (void)deregisterNotifications {
1038 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
1041 [[NSNotificationCenter defaultCenter] removeObserver:self];
1047 [
self removeInternalPlugins];
1048 [
self deregisterNotifications];
1050 [
self invalidateKeyboardAnimationVSyncClient];
1051 [
self invalidateTouchRateCorrectionVSyncClient];
1055 _scrollView.delegate = nil;
1056 _hoverGestureRecognizer.delegate = nil;
1057 _discreteScrollingPanGestureRecognizer.delegate = nil;
1058 _continuousScrollingPanGestureRecognizer.delegate = nil;
1059 _pinchGestureRecognizer.delegate = nil;
1060 _rotationGestureRecognizer.delegate = nil;
1063 #pragma mark - Application lifecycle notifications
1065 - (void)applicationBecameActive:(NSNotification*)notification {
1066 TRACE_EVENT0(
"flutter",
"applicationBecameActive");
1067 [
self appOrSceneBecameActive];
1070 - (void)applicationWillResignActive:(NSNotification*)notification {
1071 TRACE_EVENT0(
"flutter",
"applicationWillResignActive");
1072 [
self appOrSceneWillResignActive];
1075 - (void)applicationWillTerminate:(NSNotification*)notification {
1076 [
self appOrSceneWillTerminate];
1079 - (void)applicationDidEnterBackground:(NSNotification*)notification {
1080 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
1081 [
self appOrSceneDidEnterBackground];
1084 - (void)applicationWillEnterForeground:(NSNotification*)notification {
1085 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
1086 [
self appOrSceneWillEnterForeground];
1089 #pragma mark - Scene lifecycle notifications
1091 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1092 TRACE_EVENT0(
"flutter",
"sceneBecameActive");
1093 [
self appOrSceneBecameActive];
1096 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1097 TRACE_EVENT0(
"flutter",
"sceneWillResignActive");
1098 [
self appOrSceneWillResignActive];
1101 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1102 [
self appOrSceneWillTerminate];
1105 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1106 TRACE_EVENT0(
"flutter",
"sceneDidEnterBackground");
1107 [
self appOrSceneDidEnterBackground];
1110 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1111 TRACE_EVENT0(
"flutter",
"sceneWillEnterForeground");
1112 [
self appOrSceneWillEnterForeground];
1115 #pragma mark - Lifecycle shared
1117 - (void)appOrSceneBecameActive {
1118 self.isKeyboardInOrTransitioningFromBackground = NO;
1119 if (_viewportMetrics.physical_width) {
1120 [
self surfaceUpdated:YES];
1122 [
self performSelector:@selector(goToApplicationLifecycle:)
1123 withObject:@"AppLifecycleState.resumed"
1127 - (void)appOrSceneWillResignActive {
1128 [NSObject cancelPreviousPerformRequestsWithTarget:self
1129 selector:@selector(goToApplicationLifecycle:)
1130 object:@"AppLifecycleState.resumed"];
1131 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1134 - (void)appOrSceneWillTerminate {
1135 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1136 [
self.engine destroyContext];
1139 - (void)appOrSceneDidEnterBackground {
1140 self.isKeyboardInOrTransitioningFromBackground = YES;
1141 [
self surfaceUpdated:NO];
1142 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1145 - (void)appOrSceneWillEnterForeground {
1146 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1150 - (void)goToApplicationLifecycle:(nonnull NSString*)state {
1153 if (
self.viewIfLoaded.window) {
1154 [
self.engine.lifecycleChannel sendMessage:state];
1158 #pragma mark - Touch event handling
1160 static flutter::PointerData::Change PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
1162 case UITouchPhaseBegan:
1163 return flutter::PointerData::Change::kDown;
1164 case UITouchPhaseMoved:
1165 case UITouchPhaseStationary:
1168 return flutter::PointerData::Change::kMove;
1169 case UITouchPhaseEnded:
1170 return flutter::PointerData::Change::kUp;
1171 case UITouchPhaseCancelled:
1172 return flutter::PointerData::Change::kCancel;
1175 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1179 return flutter::PointerData::Change::kCancel;
1182 static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) {
1183 switch (touch.type) {
1184 case UITouchTypeDirect:
1185 case UITouchTypeIndirect:
1186 return flutter::PointerData::DeviceKind::kTouch;
1187 case UITouchTypeStylus:
1188 return flutter::PointerData::DeviceKind::kStylus;
1189 case UITouchTypeIndirectPointer:
1190 return flutter::PointerData::DeviceKind::kMouse;
1192 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1196 return flutter::PointerData::DeviceKind::kTouch;
1203 - (void)dispatchTouches:(NSSet*)touches
1204 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1205 event:(UIEvent*)event {
1230 NSUInteger touches_to_remove_count = 0;
1231 for (UITouch* touch in touches) {
1232 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1233 touches_to_remove_count++;
1238 [
self triggerTouchRateCorrectionIfNeeded:touches];
1240 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1242 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1244 size_t pointer_index = 0;
1246 for (UITouch* touch in touches) {
1247 CGPoint windowCoordinates = [touch locationInView:self.view];
1249 flutter::PointerData pointer_data;
1250 pointer_data.Clear();
1255 pointer_data.change = overridden_change !=
nullptr
1256 ? *overridden_change
1257 : PointerDataChangeFromUITouchPhase(touch.phase);
1259 pointer_data.kind = DeviceKindFromTouchType(touch);
1261 pointer_data.device =
reinterpret_cast<int64_t
>(touch);
1263 pointer_data.view_id =
self.viewIdentifier;
1266 pointer_data.pointer_identifier = 0;
1268 pointer_data.physical_x = windowCoordinates.x * scale;
1269 pointer_data.physical_y = windowCoordinates.y * scale;
1272 pointer_data.physical_delta_x = 0.0;
1273 pointer_data.physical_delta_y = 0.0;
1275 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1278 switch (pointer_data.change) {
1279 case flutter::PointerData::Change::kDown:
1280 [
self.ongoingTouches addObject:deviceKey];
1282 case flutter::PointerData::Change::kCancel:
1283 case flutter::PointerData::Change::kUp:
1284 [
self.ongoingTouches removeObject:deviceKey];
1286 case flutter::PointerData::Change::kHover:
1287 case flutter::PointerData::Change::kMove:
1290 case flutter::PointerData::Change::kAdd:
1291 case flutter::PointerData::Change::kRemove:
1294 case flutter::PointerData::Change::kPanZoomStart:
1295 case flutter::PointerData::Change::kPanZoomUpdate:
1296 case flutter::PointerData::Change::kPanZoomEnd:
1302 pointer_data.pressure = touch.force;
1303 pointer_data.pressure_max = touch.maximumPossibleForce;
1304 pointer_data.radius_major = touch.majorRadius;
1305 pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1306 pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1321 pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
1341 pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1343 if (@available(iOS 13.4, *)) {
1344 if (event !=
nullptr) {
1345 pointer_data.buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1346 ? flutter::PointerButtonMouse::kPointerButtonMousePrimary
1348 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1349 ? flutter::PointerButtonMouse::kPointerButtonMouseSecondary
1354 packet->SetPointerData(pointer_index++, pointer_data);
1356 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1357 flutter::PointerData remove_pointer_data = pointer_data;
1358 remove_pointer_data.change = flutter::PointerData::Change::kRemove;
1359 packet->SetPointerData(pointer_index++, remove_pointer_data);
1363 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1366 - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1367 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1370 - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1371 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1374 - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1375 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1378 - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1379 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1382 - (void)forceTouchesCancelled:(NSSet*)touches {
1383 flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
1384 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1387 #pragma mark - Touch events rate correction
1389 - (void)createTouchRateCorrectionVSyncClientIfNeeded {
1390 if (_touchRateCorrectionVSyncClient != nil) {
1395 const double epsilon = 0.1;
1396 if (displayRefreshRate < 60.0 + epsilon) {
1404 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1407 _touchRateCorrectionVSyncClient =
1408 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1409 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1412 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1413 if (_touchRateCorrectionVSyncClient == nil) {
1421 BOOL isUserInteracting = NO;
1422 for (UITouch* touch in touches) {
1423 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1424 isUserInteracting = YES;
1429 if (isUserInteracting &&
self.
engine.viewController ==
self) {
1430 [_touchRateCorrectionVSyncClient await];
1432 [_touchRateCorrectionVSyncClient pause];
1436 - (void)invalidateTouchRateCorrectionVSyncClient {
1437 [_touchRateCorrectionVSyncClient invalidate];
1438 _touchRateCorrectionVSyncClient = nil;
1441 #pragma mark - Handle view resizing
1443 - (void)updateViewportMetricsIfNeeded {
1444 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1447 if (
self.
engine.viewController ==
self) {
1448 [
self.engine updateViewportMetrics:_viewportMetrics];
1452 - (void)viewDidLayoutSubviews {
1453 CGRect viewBounds =
self.view.bounds;
1454 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1457 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1461 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1462 _viewportMetrics.device_pixel_ratio = scale;
1463 [
self setViewportMetricsSize];
1464 [
self checkAndUpdateAutoResizeConstraints];
1465 [
self setViewportMetricsPaddings];
1466 [
self updateViewportMetricsIfNeeded];
1473 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.
engine) {
1474 [
self surfaceUpdated:YES];
1475 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1476 NSTimeInterval timeout = 0.2;
1478 NSTimeInterval timeout = 0.1;
1481 waitForFirstFrameSync:timeout
1482 callback:^(BOOL didTimeout) {
1484 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1485 "This may happen in unoptimized builds. If this is"
1486 "a release build, you should load a less complex "
1487 "frame to avoid the timeout."];
1493 - (BOOL)isAutoResizable {
1494 return self.flutterView.autoResizable;
1497 - (void)setAutoResizable:(BOOL)value {
1498 self.flutterView.autoResizable = value;
1499 self.flutterView.contentMode = UIViewContentModeCenter;
1502 - (void)checkAndUpdateAutoResizeConstraints {
1503 if (!
self.isAutoResizable) {
1507 [
self updateAutoResizeConstraints];
1531 - (void)updateAutoResizeConstraints {
1532 BOOL hasBeenAutoResized = NO;
1533 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1535 hasBeenAutoResized = YES;
1539 if (!hasBeenAutoResized) {
1540 self.sizeBeforeAutoResized =
self.view.frame.size;
1543 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1544 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1545 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1546 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1550 if (maxWidth == 0) {
1551 maxWidth = CGFLOAT_MAX;
1554 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1555 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1556 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1557 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1558 @"constraints (e.g., using SizedBox or Container)."];
1560 if (maxHeight == 0) {
1561 maxHeight = CGFLOAT_MAX;
1564 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1565 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1566 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1567 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1568 @"constraints (e.g., using SizedBox or Container)."];
1570 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1571 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1572 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1573 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1576 - (void)viewSafeAreaInsetsDidChange {
1577 [
self setViewportMetricsPaddings];
1578 [
self updateViewportMetricsIfNeeded];
1579 [
super viewSafeAreaInsetsDidChange];
1583 - (void)setViewportMetricsSize {
1584 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1589 CGFloat scale = screen.scale;
1590 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1591 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1593 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1594 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1595 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1596 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1602 - (void)setViewportMetricsPaddings {
1603 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1608 CGFloat scale = screen.scale;
1609 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1610 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1611 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1612 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1615 #pragma mark - Keyboard events
1617 - (void)keyboardWillShowNotification:(NSNotification*)notification {
1622 [
self handleKeyboardNotification:notification];
1625 - (void)keyboardWillChangeFrame:(NSNotification*)notification {
1630 [
self handleKeyboardNotification:notification];
1633 - (void)keyboardWillBeHidden:(NSNotification*)notification {
1637 [
self handleKeyboardNotification:notification];
1640 - (void)handleKeyboardNotification:(NSNotification*)notification {
1643 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1647 NSDictionary* info = notification.userInfo;
1648 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1649 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1650 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1651 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1652 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1659 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1660 [
self hideKeyboardImmediately];
1665 if (
self.targetViewInsetBottom == calculatedInset) {
1669 self.targetViewInsetBottom = calculatedInset;
1676 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1677 BOOL keyboardAnimationIsCompounding =
1678 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1681 self.keyboardAnimationIsShowing = keyboardWillShow;
1683 if (!keyboardAnimationIsCompounding) {
1684 [
self startKeyBoardAnimation:duration];
1685 }
else if (
self.keyboardSpringAnimation) {
1686 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1690 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1695 if (notification.name == UIKeyboardWillHideNotification) {
1704 NSDictionary* info = notification.userInfo;
1705 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1706 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1707 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1713 if (CGRectIsEmpty(keyboardFrame)) {
1718 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1724 - (BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1725 NSDictionary* info = notification.userInfo;
1729 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1730 if (isLocal && ![isLocal boolValue]) {
1733 return self.engine.viewController !=
self;
1736 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1744 NSDictionary* info = notification.userInfo;
1745 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1747 if (notification.name == UIKeyboardWillHideNotification) {
1748 return FlutterKeyboardModeHidden;
1753 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1754 return FlutterKeyboardModeFloating;
1757 if (CGRectIsEmpty(keyboardFrame)) {
1758 return FlutterKeyboardModeHidden;
1761 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1762 CGRect adjustedKeyboardFrame = keyboardFrame;
1763 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1764 keyboardFrame:keyboardFrame];
1769 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1770 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1771 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1772 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1774 CGFloat screenHeight = CGRectGetHeight(screenRect);
1775 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1776 if (round(adjustedKeyboardBottom) < screenHeight) {
1777 return FlutterKeyboardModeFloating;
1779 return FlutterKeyboardModeDocked;
1781 return FlutterKeyboardModeHidden;
1784 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1788 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1789 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1790 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1791 CGFloat screenHeight = CGRectGetHeight(screenRect);
1792 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1796 if (screenHeight == keyboardBottom) {
1799 CGRect viewRectRelativeToScreen =
1800 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1801 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1802 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1803 CGFloat offset = screenHeight - viewBottom;
1811 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1813 if (keyboardMode == FlutterKeyboardModeDocked) {
1815 CGRect viewRectRelativeToScreen =
1816 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1817 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1818 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1819 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1824 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1825 return portionOfKeyboardInView * scale;
1830 - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1832 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1838 if (!
self.keyboardAnimationView) {
1839 UIView* keyboardAnimationView = [[UIView alloc] init];
1840 keyboardAnimationView.hidden = YES;
1841 self.keyboardAnimationView = keyboardAnimationView;
1844 if (!
self.keyboardAnimationView.superview) {
1845 [
self.view addSubview:self.keyboardAnimationView];
1849 [
self.keyboardAnimationView.layer removeAllAnimations];
1852 self.keyboardAnimationView.frame =
1853 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1854 self.keyboardAnimationStartTime = fml::TimePoint().Now();
1855 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1858 [
self invalidateKeyboardAnimationVSyncClient];
1861 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1862 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1864 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1866 [UIView animateWithDuration:duration
1874 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1877 CAAnimation* keyboardAnimation =
1878 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1879 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1881 completion:^(BOOL finished) {
1882 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1891 [strongSelf invalidateKeyboardAnimationVSyncClient];
1892 [strongSelf removeKeyboardAnimationView];
1893 [strongSelf ensureViewportMetricsIsCorrect];
1898 - (void)hideKeyboardImmediately {
1899 [
self invalidateKeyboardAnimationVSyncClient];
1900 if (
self.keyboardAnimationView) {
1901 [
self.keyboardAnimationView.layer removeAllAnimations];
1902 [
self removeKeyboardAnimationView];
1903 self.keyboardAnimationView = nil;
1905 if (
self.keyboardSpringAnimation) {
1906 self.keyboardSpringAnimation = nil;
1909 self.targetViewInsetBottom = 0.0;
1910 [
self ensureViewportMetricsIsCorrect];
1913 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1915 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation
class]]) {
1916 _keyboardSpringAnimation = nil;
1921 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1922 _keyboardSpringAnimation =
1923 [[SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1924 damping:keyboardCASpringAnimation.damping
1925 mass:keyboardCASpringAnimation.mass
1926 initialVelocity:keyboardCASpringAnimation.initialVelocity
1927 fromValue:self.originalViewInsetBottom
1928 toValue:self.targetViewInsetBottom];
1931 - (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::TimePoint)targetTime {
1933 if (!
self.isViewLoaded) {
1938 if (!
self.keyboardAnimationView) {
1943 if (!
self.keyboardAnimationVSyncClient) {
1947 if (!
self.keyboardAnimationView.superview) {
1949 [
self.view addSubview:self.keyboardAnimationView];
1952 if (!
self.keyboardSpringAnimation) {
1953 if (
self.keyboardAnimationView.layer.presentationLayer) {
1954 self->_viewportMetrics.physical_view_inset_bottom =
1955 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1956 [
self updateViewportMetricsIfNeeded];
1959 fml::TimeDelta timeElapsed = targetTime -
self.keyboardAnimationStartTime;
1960 self->_viewportMetrics.physical_view_inset_bottom =
1961 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1962 [
self updateViewportMetricsIfNeeded];
1966 - (void)setUpKeyboardAnimationVsyncClient:
1968 if (!keyboardAnimationCallback) {
1971 NSAssert(_keyboardAnimationVSyncClient == nil,
1972 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1976 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1977 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1978 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1979 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1980 animationCallback(targetTime);
1984 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1985 callback:uiCallback];
1986 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1987 [_keyboardAnimationVSyncClient await];
1990 - (void)invalidateKeyboardAnimationVSyncClient {
1991 [_keyboardAnimationVSyncClient invalidate];
1992 _keyboardAnimationVSyncClient = nil;
1995 - (void)removeKeyboardAnimationView {
1996 if (
self.keyboardAnimationView.superview != nil) {
1997 [
self.keyboardAnimationView removeFromSuperview];
2001 - (void)ensureViewportMetricsIsCorrect {
2002 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
2004 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
2005 [
self updateViewportMetricsIfNeeded];
2009 - (void)handlePressEvent:(FlutterUIPressProxy*)press
2010 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
2011 if (@available(iOS 13.4, *)) {
2016 [
self.keyboardManager handlePress:press nextAction:next];
2032 - (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2033 [
super pressesBegan:presses withEvent:event];
2036 - (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2037 [
super pressesChanged:presses withEvent:event];
2040 - (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2041 [
super pressesEnded:presses withEvent:event];
2044 - (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2045 [
super pressesCancelled:presses withEvent:event];
2053 - (void)pressesBegan:(NSSet<UIPress*>*)presses
2054 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2055 if (@available(iOS 13.4, *)) {
2057 for (UIPress* press in presses) {
2058 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2060 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
2064 [
super pressesBegan:presses withEvent:event];
2068 - (void)pressesChanged:(NSSet<UIPress*>*)presses
2069 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2070 if (@available(iOS 13.4, *)) {
2072 for (UIPress* press in presses) {
2073 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2075 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
2079 [
super pressesChanged:presses withEvent:event];
2083 - (void)pressesEnded:(NSSet<UIPress*>*)presses
2084 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2085 if (@available(iOS 13.4, *)) {
2087 for (UIPress* press in presses) {
2088 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2090 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2094 [
super pressesEnded:presses withEvent:event];
2098 - (void)pressesCancelled:(NSSet<UIPress*>*)presses
2099 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2100 if (@available(iOS 13.4, *)) {
2102 for (UIPress* press in presses) {
2103 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2105 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2109 [
super pressesCancelled:presses withEvent:event];
2113 #pragma mark - Orientation updates
2115 - (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2118 dispatch_async(dispatch_get_main_queue(), ^{
2119 NSDictionary* info = notification.userInfo;
2120 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2121 if (update == nil) {
2124 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2128 - (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2129 API_AVAILABLE(ios(16.0)) {
2130 for (UIScene* windowScene in windowScenes) {
2131 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene
class]]);
2132 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2133 initWithInterfaceOrientations:self.orientationPreferences];
2134 [(UIWindowScene*)windowScene
2135 requestGeometryUpdateWithPreferences:preference
2136 errorHandler:^(NSError* error) {
2137 os_log_error(OS_LOG_DEFAULT,
2138 "Failed to change device orientation: %@", error);
2140 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2144 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2145 if (new_preferences !=
self.orientationPreferences) {
2146 self.orientationPreferences = new_preferences;
2148 if (@available(iOS 16.0, *)) {
2150 NSSet<UIScene*>* scenes = [NSSet set];
2151 if (flutterApplication) {
2152 scenes = [flutterApplication.connectedScenes
2153 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2154 id scene, NSDictionary* bindings) {
2155 return [scene isKindOfClass:[UIWindowScene class]];
2157 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2158 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2160 [
self requestGeometryUpdateForWindowScenes:scenes];
2162 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2163 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2167 @"Accessing the interface orientation when the window scene is unavailable."];
2170 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2171 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2172 [UIViewController attemptRotationToDeviceOrientation];
2174 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2178 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2179 forKey:@"orientation"];
2180 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2181 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2182 forKey:@"orientation"];
2183 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2184 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2185 forKey:@"orientation"];
2186 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2187 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2188 forKey:@"orientation"];
2195 - (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2196 self.isHomeIndicatorHidden = YES;
2199 - (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2200 self.isHomeIndicatorHidden = NO;
2203 - (void)setIsHomeIndicatorHidden:(BOOL)hideHomeIndicator {
2204 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2205 _isHomeIndicatorHidden = hideHomeIndicator;
2206 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2210 - (BOOL)prefersHomeIndicatorAutoHidden {
2211 return self.isHomeIndicatorHidden;
2214 - (BOOL)shouldAutorotate {
2218 - (NSUInteger)supportedInterfaceOrientations {
2219 return self.orientationPreferences;
2222 #pragma mark - Accessibility
2224 - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2229 int32_t flags =
self.accessibilityFlags;
2230 #if TARGET_OS_SIMULATOR
2236 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2237 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2239 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kAccessibleNavigation);
2241 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2243 [
self.engine enableSemantics:enabled withFlags:flags];
2246 - (int32_t)accessibilityFlags {
2248 if (UIAccessibilityIsInvertColorsEnabled()) {
2249 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kInvertColors);
2251 if (UIAccessibilityIsReduceMotionEnabled()) {
2252 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kReduceMotion);
2254 if (UIAccessibilityIsBoldTextEnabled()) {
2255 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kBoldText);
2257 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2258 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kHighContrast);
2261 flags |=
static_cast<int32_t
>(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels);
2267 - (BOOL)accessibilityPerformEscape {
2269 if (navigationChannel) {
2276 + (BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2277 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2280 #pragma mark - Set user settings
2282 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2283 [
super traitCollectionDidChange:previousTraitCollection];
2284 [
self onUserSettingsChanged:nil];
2288 if (
self.isAutoResizable) {
2289 [
self.flutterView resetIntrinsicContentSize];
2293 - (void)onUserSettingsChanged:(NSNotification*)notification {
2294 [
self.engine.settingsChannel sendMessage:@{
2295 @"textScaleFactor" : @(
self.textScaleFactor),
2297 @"platformBrightness" :
self.brightnessMode,
2298 @"platformContrast" : self.contrastMode,
2299 @"nativeSpellCheckServiceDefined" : @YES,
2300 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2304 - (CGFloat)textScaleFactor {
2306 if (flutterApplication == nil) {
2307 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2311 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2317 const CGFloat xs = 14;
2318 const CGFloat s = 15;
2319 const CGFloat m = 16;
2320 const CGFloat l = 17;
2321 const CGFloat xl = 19;
2322 const CGFloat xxl = 21;
2323 const CGFloat xxxl = 23;
2326 const CGFloat ax1 = 28;
2327 const CGFloat ax2 = 33;
2328 const CGFloat ax3 = 40;
2329 const CGFloat ax4 = 47;
2330 const CGFloat ax5 = 53;
2334 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2336 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2338 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2340 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2342 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2344 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2346 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2348 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2350 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2352 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2354 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2356 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2363 - (BOOL)supportsShowingSystemContextMenu {
2364 if (@available(iOS 16.0, *)) {
2374 - (NSString*)brightnessMode {
2375 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2377 if (style == UIUserInterfaceStyleDark) {
2387 - (NSString*)contrastMode {
2388 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2390 if (contrast == UIAccessibilityContrastHigh) {
2397 #pragma mark - Status bar style
2399 - (UIStatusBarStyle)preferredStatusBarStyle {
2400 return self.statusBarStyle;
2403 - (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2406 dispatch_async(dispatch_get_main_queue(), ^{
2412 NSDictionary* info = notification.userInfo;
2413 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2414 if (update == nil) {
2418 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2419 if (style != strongSelf.statusBarStyle) {
2420 strongSelf.statusBarStyle = style;
2421 [strongSelf setNeedsStatusBarAppearanceUpdate];
2426 - (void)setPrefersStatusBarHidden:(BOOL)hidden {
2427 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2428 self.flutterPrefersStatusBarHidden = hidden;
2429 [
self setNeedsStatusBarAppearanceUpdate];
2433 - (BOOL)prefersStatusBarHidden {
2434 return self.flutterPrefersStatusBarHidden;
2437 #pragma mark - Platform views
2440 return self.engine.platformViewsController;
2444 return self.engine.binaryMessenger;
2447 #pragma mark - FlutterBinaryMessenger
2449 - (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2450 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2453 - (void)sendOnChannel:(NSString*)channel
2454 message:(NSData*)message
2456 NSAssert(channel,
@"The channel must not be null");
2457 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2461 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2465 binaryMessageHandler:
2467 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2471 setMessageHandlerOnChannel:(NSString*)channel
2474 NSAssert(channel,
@"The channel must not be null");
2475 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2476 binaryMessageHandler:handler
2477 taskQueue:taskQueue];
2481 [
self.engine.binaryMessenger cleanUpConnection:connection];
2484 #pragma mark - FlutterTextureRegistry
2487 return [
self.engine.textureRegistry registerTexture:texture];
2490 - (void)unregisterTexture:(int64_t)textureId {
2491 [
self.engine.textureRegistry unregisterTexture:textureId];
2494 - (void)textureFrameAvailable:(int64_t)textureId {
2495 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2498 - (NSString*)lookupKeyForAsset:(NSString*)asset {
2502 - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2506 - (id<FlutterPluginRegistry>)pluginRegistry {
2510 + (BOOL)isUIAccessibilityIsVoiceOverRunning {
2511 return UIAccessibilityIsVoiceOverRunning();
2514 #pragma mark - FlutterPluginRegistry
2517 return [
self.engine registrarForPlugin:pluginKey];
2520 - (BOOL)hasPlugin:(NSString*)pluginKey {
2521 return [
self.engine hasPlugin:pluginKey];
2524 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2525 return [
self.engine valuePublishedByPlugin:pluginKey];
2528 - (void)presentViewController:(UIViewController*)viewControllerToPresent
2530 completion:(
void (^)(
void))completion {
2531 self.isPresentingViewControllerAnimating = YES;
2533 [
super presentViewController:viewControllerToPresent
2536 weakSelf.isPresentingViewControllerAnimating = NO;
2543 - (BOOL)isPresentingViewController {
2544 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2547 - (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2548 API_AVAILABLE(ios(13.4)) {
2549 CGPoint location = [gestureRecognizer locationInView:self.view];
2550 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2552 flutter::PointerData pointer_data;
2553 pointer_data.Clear();
2557 return pointer_data;
2560 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2561 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2562 API_AVAILABLE(ios(13.4)) {
2566 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2567 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2568 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2569 event.type == UIEventTypeScroll) {
2571 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:gestureRecognizer];
2572 pointer_data.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2573 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2574 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2575 pointer_data.view_id =
self.viewIdentifier;
2577 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2580 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2581 packet->SetPointerData(0, pointer_data);
2582 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2583 self.scrollInertiaEventAppKitDeadline = 0;
2590 - (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2593 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2594 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2595 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2596 pointer_data.view_id =
self.viewIdentifier;
2598 switch (_hoverGestureRecognizer.state) {
2599 case UIGestureRecognizerStateBegan:
2600 pointer_data.change = flutter::PointerData::Change::kAdd;
2602 case UIGestureRecognizerStateChanged:
2603 pointer_data.change = flutter::PointerData::Change::kHover;
2605 case UIGestureRecognizerStateEnded:
2606 case UIGestureRecognizerStateCancelled:
2607 pointer_data.change = flutter::PointerData::Change::kRemove;
2612 pointer_data.change = flutter::PointerData::Change::kHover;
2616 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2617 BOOL isRunningOnMac = NO;
2618 if (@available(iOS 14.0, *)) {
2622 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2625 time >
self.scrollInertiaEventStartline) {
2629 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2630 packet->SetPointerData(0, pointer_data);
2631 flutter::PointerData inertia_cancel = pointer_data;
2632 inertia_cancel.device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2633 inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
2634 inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
2635 inertia_cancel.view_id =
self.viewIdentifier;
2636 packet->SetPointerData(1, inertia_cancel);
2637 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2638 self.scrollInertiaEventStartline = DBL_MAX;
2640 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2641 packet->SetPointerData(0, pointer_data);
2642 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2646 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2647 CGPoint translation = [recognizer translationInView:self.view];
2648 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2650 translation.x *= scale;
2651 translation.y *= scale;
2653 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2654 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2655 pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
2656 pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
2657 pointer_data.scroll_delta_x = (translation.x -
_mouseState.last_translation.x);
2658 pointer_data.scroll_delta_y = -(translation.y -
_mouseState.last_translation.y);
2659 pointer_data.view_id =
self.viewIdentifier;
2665 if (recognizer.state != UIGestureRecognizerStateEnded) {
2671 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2672 packet->SetPointerData(0, pointer_data);
2673 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2676 - (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2677 CGPoint translation = [recognizer translationInView:self.view];
2678 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2680 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2681 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2682 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2683 pointer_data.view_id =
self.viewIdentifier;
2684 switch (recognizer.state) {
2685 case UIGestureRecognizerStateBegan:
2686 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2688 case UIGestureRecognizerStateChanged:
2689 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2690 pointer_data.pan_x = translation.x * scale;
2691 pointer_data.pan_y = translation.y * scale;
2692 pointer_data.pan_delta_x = 0;
2693 pointer_data.pan_delta_y = 0;
2694 pointer_data.scale = 1;
2696 case UIGestureRecognizerStateEnded:
2697 case UIGestureRecognizerStateCancelled:
2698 self.scrollInertiaEventStartline =
2699 [[NSProcessInfo processInfo] systemUptime] +
2709 self.scrollInertiaEventAppKitDeadline =
2710 [[NSProcessInfo processInfo] systemUptime] +
2711 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2712 [recognizer velocityInView:self.view].y))) -
2714 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2718 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2719 (
long)recognizer.state);
2723 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2724 packet->SetPointerData(0, pointer_data);
2725 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2728 - (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2729 flutter::PointerData pointer_data = [
self updateMousePointerDataFrom:recognizer];
2730 pointer_data.device =
reinterpret_cast<int64_t
>(recognizer);
2731 pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
2732 pointer_data.view_id =
self.viewIdentifier;
2733 switch (recognizer.state) {
2734 case UIGestureRecognizerStateBegan:
2735 pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
2737 case UIGestureRecognizerStateChanged:
2738 pointer_data.change = flutter::PointerData::Change::kPanZoomUpdate;
2739 pointer_data.scale = recognizer.scale;
2740 pointer_data.rotation = _rotationGestureRecognizer.rotation;
2742 case UIGestureRecognizerStateEnded:
2743 case UIGestureRecognizerStateCancelled:
2744 pointer_data.change = flutter::PointerData::Change::kPanZoomEnd;
2748 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2749 (
long)recognizer.state);
2753 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2754 packet->SetPointerData(0, pointer_data);
2755 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2758 #pragma mark - State Restoration
2760 - (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2761 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2762 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2763 length:restorationData.length
2764 forKey:kFlutterRestorationStateAppData];
2765 [
super encodeRestorableStateWithCoder:coder];
2768 - (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2769 NSUInteger restorationDataLength;
2770 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2771 returnedLength:&restorationDataLength];
2772 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2773 [
self.engine.restorationPlugin setRestorationData:restorationData];
2777 return self.engine.restorationPlugin;
2781 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