5#define FML_USED_ON_EMBEDDER
18#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
52 @"FlutterViewControllerHideHomeIndicator";
54 @"FlutterViewControllerShowHomeIndicator";
70 FlutterKeyboardInsetManagerDelegate>
79@property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
81@property(nonatomic, strong) FlutterSplashScreenManager* splashScreenManager;
83@property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
84@property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
85@property(nonatomic, assign)
BOOL initialized;
86@property(nonatomic, assign)
BOOL engineNeedsLaunch;
87@property(nonatomic, assign)
BOOL awokenFromNib;
90@property(nonatomic, assign)
BOOL isHomeIndicatorHidden;
91@property(nonatomic, assign)
BOOL isPresentingViewControllerAnimating;
94@property(nonatomic, assign)
BOOL flutterPrefersStatusBarHidden;
96@property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
101@property(nonatomic, strong) UIScrollView* scrollView;
106@property(nonatomic, assign)
BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
112@property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
120@property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
128@property(nonatomic, strong) FlutterVSyncClient* touchRateCorrectionVSyncClient;
132@property(nonatomic, assign) CGSize sizeBeforeAutoResized;
138@property(nonatomic, strong)
141@property(nonatomic, strong)
142 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
144@property(nonatomic, strong)
145 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
147@property(nonatomic, strong)
150@property(nonatomic, strong)
151 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
154- (void)addInternalPlugins;
155- (void)deregisterNotifications;
158- (void)onFirstFrameRendered;
171@synthesize viewOpaque = _viewOpaque;
172@synthesize displayingFlutterUI = _displayingFlutterUI;
174- (FlutterSplashScreenManager*)splashScreenManager {
184@dynamic viewIdentifier;
186#pragma mark - Manage and override all designated initializers
189 nibName:(nullable NSString*)nibName
190 bundle:(nullable NSBundle*)nibBundle {
191 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
192 self = [
super initWithNibName:nibName bundle:nibBundle];
196 NSString* errorMessage =
197 [NSString stringWithFormat:
198 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
199 "instance %@. One instance of the FlutterEngine can only be attached to "
200 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
201 "nil before attaching it to another FlutterViewController.",
202 engine.description, engine.viewController.description];
203 [FlutterLogger logError:errorMessage];
206 _engineNeedsLaunch = NO;
207 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
208 opaque:self.isViewOpaque
209 enableWideGamut:engine.project.isWideGamutEnabled];
210 _ongoingTouches = [[NSMutableSet alloc] init];
214 [
self performCommonViewControllerInitialization];
215 [engine setViewController:self];
222 nibName:(NSString*)nibName
223 bundle:(NSBundle*)nibBundle {
224 self = [
super initWithNibName:nibName bundle:nibBundle];
228 [
self sharedSetupWithProject:project initialRoute:nil];
235 initialRoute:(NSString*)initialRoute
236 nibName:(NSString*)nibName
237 bundle:(NSBundle*)nibBundle {
238 self = [
super initWithNibName:nibName bundle:nibBundle];
242 [
self sharedSetupWithProject:project initialRoute:initialRoute];
248- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
249 return [
self initWithProject:nil nibName:nil bundle:nil];
253 self = [
super initWithCoder:aDecoder];
257- (void)awakeFromNib {
258 [
super awakeFromNib];
259 self.awokenFromNib = YES;
261 [
self sharedSetupWithProject:nil initialRoute:nil];
265- (instancetype)init {
266 return [
self initWithProject:nil nibName:nil bundle:nil];
270 initialRoute:(nullable NSString*)initialRoute {
273 if ([appDelegate respondsToSelector:@selector(takeLaunchEngine)]) {
278 engine = [appDelegate takeLaunchEngine];
283 [appDelegate takeLaunchEngine];
295 allowHeadlessExecution:self.engineAllowHeadlessExecution
296 restorationEnabled:self.restorationIdentifier != nil];
304 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
306 enableWideGamut:engine.project.isWideGamutEnabled];
307 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
313 BOOL performedCallback = [_engine performImplicitEngineCallback];
317 respondsToSelector:@selector(pluginRegistrant)]) {
318 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
320 [pluginRegistrant registerWithRegistry:self];
321 performedCallback = YES;
329 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
330 [applicationLifeCycleDelegate
331 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
332 [applicationLifeCycleDelegate
333 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
336 _engineNeedsLaunch = YES;
337 _ongoingTouches = [[NSMutableSet alloc] init];
341 [
self.splashScreenManager loadDefaultSplashScreenView];
342 [
self performCommonViewControllerInitialization];
345- (
BOOL)isViewOpaque {
349- (void)setViewOpaque:(
BOOL)value {
351 if (
self.flutterView.layer.opaque != value) {
353 [
self.flutterView.layer setNeedsLayout];
357#pragma mark - Common view controller initialization tasks
359- (void)performCommonViewControllerInitialization {
365 _orientationPreferences = UIInterfaceOrientationMaskAll;
366 _statusBarStyle = UIStatusBarStyleDefault;
368 _accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
373 [
self setUpNotificationCenterObservers];
376- (void)setUpNotificationCenterObservers {
377 NSNotificationCenter*
center = [NSNotificationCenter defaultCenter];
378 [center addObserver:self
379 selector:@selector(onOrientationPreferencesUpdated:)
380 name:@(flutter::kOrientationUpdateNotificationName)
383 [center addObserver:self
384 selector:@selector(onPreferredStatusBarStyleUpdated:)
385 name:@(flutter::kOverlayStyleUpdateNotificationName)
389 [
self setUpApplicationLifecycleNotifications:center];
391 [
self setUpSceneLifecycleNotifications:center];
394 [center addObserver:self
395 selector:@selector(keyboardWillChangeFrame:)
396 name:UIKeyboardWillChangeFrameNotification
399 [center addObserver:self
400 selector:@selector(keyboardWillShowNotification:)
401 name:UIKeyboardWillShowNotification
404 [center addObserver:self
405 selector:@selector(keyboardWillBeHidden:)
406 name:UIKeyboardWillHideNotification
409 for (NSString* notification in [
self.accessibilityFeatures observedNotificationNames]) {
410 [center addObserver:self
411 selector:@selector(onAccessibilityStatusChanged:)
416 [center addObserver:self
417 selector:@selector(onUserSettingsChanged:)
418 name:UIContentSizeCategoryDidChangeNotification
421 [center addObserver:self
422 selector:@selector(onHideHomeIndicatorNotification:)
423 name:FlutterViewControllerHideHomeIndicator
426 [center addObserver:self
427 selector:@selector(onShowHomeIndicatorNotification:)
428 name:FlutterViewControllerShowHomeIndicator
432- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
433 [center addObserver:self
434 selector:@selector(sceneBecameActive:)
435 name:UISceneDidActivateNotification
438 [center addObserver:self
439 selector:@selector(sceneWillResignActive:)
440 name:UISceneWillDeactivateNotification
443 [center addObserver:self
444 selector:@selector(sceneWillDisconnect:)
445 name:UISceneDidDisconnectNotification
448 [center addObserver:self
449 selector:@selector(sceneDidEnterBackground:)
450 name:UISceneDidEnterBackgroundNotification
453 [center addObserver:self
454 selector:@selector(sceneWillEnterForeground:)
455 name:UISceneWillEnterForegroundNotification
459- (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
460 [center addObserver:self
461 selector:@selector(applicationBecameActive:)
462 name:UIApplicationDidBecomeActiveNotification
465 [center addObserver:self
466 selector:@selector(applicationWillResignActive:)
467 name:UIApplicationWillResignActiveNotification
470 [center addObserver:self
471 selector:@selector(applicationWillTerminate:)
472 name:UIApplicationWillTerminateNotification
475 [center addObserver:self
476 selector:@selector(applicationDidEnterBackground:)
477 name:UIApplicationDidEnterBackgroundNotification
480 [center addObserver:self
481 selector:@selector(applicationWillEnterForeground:)
482 name:UIApplicationWillEnterForegroundNotification
486- (void)setInitialRoute:(NSString*)route {
487 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
491 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
494- (void)pushRoute:(NSString*)route {
495 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
498#pragma mark - Loading the view
500static UIView* GetViewOrPlaceholder(UIView* existing_view) {
502 return existing_view;
505 auto placeholder = [[UIView alloc] init];
507 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
508 placeholder.backgroundColor = UIColor.systemBackgroundColor;
509 placeholder.autoresizesSubviews = YES;
515 auto messageLabel = [[UILabel alloc] init];
516 messageLabel.numberOfLines = 0u;
517 messageLabel.textAlignment = NSTextAlignmentCenter;
518 messageLabel.autoresizingMask =
519 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
521 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
522 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
523 @"modes to enable launching from the home screen.";
524 [placeholder addSubview:messageLabel];
531 self.view = GetViewOrPlaceholder(
self.flutterView);
532 self.view.multipleTouchEnabled = YES;
533 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
535 [
self installSplashScreenViewIfNecessary];
538 UIScrollView* scrollView = [[UIScrollView alloc] init];
539 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
541 scrollView.backgroundColor = UIColor.whiteColor;
542 scrollView.delegate =
self;
548 [
self.view addSubview:scrollView];
549 self.scrollView = scrollView;
552- (
flutter::PointerData)generatePointerDataForFake {
554 pointer_data.
Clear();
563- (
BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
567 if (
self.isViewLoaded) {
569 [
self.engine onStatusBarTap];
574#pragma mark - Managing launch views
576- (void)installSplashScreenViewIfNecessary {
582 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
584 [
self.splashScreenView removeFromSuperview];
585 self.splashScreenView = nil;
590 [
self.splashScreenManager installSplashScreenViewAsSubviewOf:self.view];
593+ (
BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
597- (void)setDisplayingFlutterUI:(
BOOL)displayingFlutterUI {
598 if (_displayingFlutterUI != displayingFlutterUI) {
599 if (displayingFlutterUI == YES) {
600 if (!
self.viewIfLoaded.window) {
604 [
self willChangeValueForKey:@"displayingFlutterUI"];
605 _displayingFlutterUI = displayingFlutterUI;
606 [
self didChangeValueForKey:@"displayingFlutterUI"];
610- (void)callViewRenderedCallback {
611 self.displayingFlutterUI = YES;
612 if (
self.flutterViewRenderedCallback) {
613 self.flutterViewRenderedCallback();
614 self.flutterViewRenderedCallback = nil;
618- (void)onFirstFrameRendered {
619 if (
self.splashScreenView) {
621 [
self.splashScreenManager removeSplashScreenWithCompletion:^{
622 [weakSelf callViewRenderedCallback];
625 [
self callViewRenderedCallback];
629- (void)installFirstFrameCallback {
634 [
self.engine installFirstFrameCallback:^{
635 [weakSelf onFirstFrameRendered];
639#pragma mark - Properties
641- (int64_t)viewIdentifier {
647- (
BOOL)loadDefaultSplashScreenView {
648 return [
self.splashScreenManager loadDefaultSplashScreenView];
651- (UIView*)splashScreenView {
655- (void)setSplashScreenView:(UIView*)view {
656 self.splashScreenManager.splashScreenView =
view;
659- (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
660 _flutterViewRenderedCallback =
callback;
663- (UISceneActivationState)activationState {
664 return self.flutterWindowSceneIfViewLoaded.activationState;
667- (
BOOL)stateIsActive {
670 BOOL isActive = flutterApplication
671 ? [
self isApplicationStateMatching:UIApplicationStateActive
672 withApplication:flutterApplication]
673 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
677- (
BOOL)stateIsBackground {
680 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
681 withApplication:flutterApplication]
682 : [
self isSceneStateMatching:UISceneActivationStateBackground];
685- (
BOOL)shouldHandleSceneNotification:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
686 if (notification.object == nil) {
689 UIWindowScene* scene =
self.flutterWindowSceneIfViewLoaded;
693 return notification.object == scene;
696- (
BOOL)isApplicationStateMatching:(UIApplicationState)match
697 withApplication:(UIApplication*)application {
698 switch (application.applicationState) {
699 case UIApplicationStateActive:
700 case UIApplicationStateInactive:
701 case UIApplicationStateBackground:
702 return application.applicationState == match;
706- (
BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
707 switch (
self.activationState) {
708 case UISceneActivationStateForegroundActive:
709 case UISceneActivationStateUnattached:
710 case UISceneActivationStateForegroundInactive:
711 case UISceneActivationStateBackground:
712 return self.activationState == match;
716#pragma mark - Surface creation and teardown updates
718- (void)surfaceUpdated:(
BOOL)appeared {
726 [
self installFirstFrameCallback];
727 self.platformViewsController.flutterView =
self.flutterView;
728 self.platformViewsController.flutterViewController =
self;
729 [
self.engine notifyViewCreated];
731 self.displayingFlutterUI = NO;
732 [
self.engine notifyViewDestroyed];
733 self.platformViewsController.flutterView = nil;
734 self.platformViewsController.flutterViewController = nil;
738#pragma mark - UIViewController lifecycle notifications
743 if (
self.engine &&
self.engineNeedsLaunch) {
744 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
745 [
self.engine setViewController:self];
746 self.engineNeedsLaunch = NO;
747 }
else if (
self.engine.viewController ==
self) {
748 [
self.engine attachView];
752 [
self addInternalPlugins];
755 [
self createTouchRateCorrectionVSyncClientIfNeeded];
757 if (@available(iOS 13.4, *)) {
758 _hoverGestureRecognizer =
759 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
760 _hoverGestureRecognizer.delegate =
self;
761 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
763 _discreteScrollingPanGestureRecognizer =
764 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
765 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
770 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
771 _discreteScrollingPanGestureRecognizer.delegate =
self;
772 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
773 _continuousScrollingPanGestureRecognizer =
774 [[UIPanGestureRecognizer alloc] initWithTarget:self
775 action:@selector(continuousScrollEvent:)];
776 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
777 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
778 _continuousScrollingPanGestureRecognizer.delegate =
self;
779 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
780 _pinchGestureRecognizer =
781 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
782 _pinchGestureRecognizer.allowedTouchTypes = @[];
783 _pinchGestureRecognizer.delegate =
self;
784 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
785 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
786 _rotationGestureRecognizer.allowedTouchTypes = @[];
787 _rotationGestureRecognizer.delegate =
self;
788 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
794- (void)addInternalPlugins {
799 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
801 [
self.keyboardManager
805 [
self.keyboardManager addPrimaryResponder:responder];
808 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
810 if (
self.engine.viewController ==
self) {
815- (void)removeInternalPlugins {
816 self.keyboardManager = nil;
819- (void)viewWillAppear:(
BOOL)animated {
821 if (
self.engine.viewController ==
self) {
823 [
self onUserSettingsChanged:nil];
827 if (_viewportMetrics.physical_width) {
828 [
self surfaceUpdated:YES];
830 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
831 [
self.engine.restorationPlugin markRestorationComplete];
834 [
super viewWillAppear:animated];
837- (void)viewDidAppear:(
BOOL)animated {
839 if (
self.engine.viewController ==
self) {
840 [
self onUserSettingsChanged:nil];
841 [
self onAccessibilityStatusChanged:nil];
843 if (
self.stateIsActive) {
844 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
847 [
super viewDidAppear:animated];
850- (void)viewWillDisappear:(
BOOL)animated {
852 if (
self.engine.viewController ==
self) {
853 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
855 [
super viewWillDisappear:animated];
858- (void)viewDidDisappear:(
BOOL)animated {
860 if (
self.engine.viewController ==
self) {
861 [
self.keyboardInsetManager hideKeyboardImmediately];
862 [
self surfaceUpdated:NO];
863 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
864 [
self flushOngoingTouches];
865 [
self.engine notifyLowMemory];
868 [
super viewDidDisappear:animated];
871- (void)viewWillTransitionToSize:(CGSize)size
872 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
873 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
883 NSTimeInterval transitionDuration = coordinator.transitionDuration;
885 if (transitionDuration == 0) {
890 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
891 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
892 static_cast<int64_t
>(transitionDuration / 2.0 *
NSEC_PER_SEC)),
893 dispatch_get_main_queue(), ^{
901 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
902 [strongSelf updateViewportMetricsIfNeeded];
906- (void)flushOngoingTouches {
907 if (
self.engine &&
self.ongoingTouches.count > 0) {
908 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
909 size_t pointer_index = 0;
912 for (NSNumber*
device in
self.ongoingTouches) {
929 packet->SetPointerData(pointer_index++, pointer_data);
932 [
self.ongoingTouches removeAllObjects];
933 [
self.engine dispatchPointerDataPacket:std::move(packet)];
937- (void)deregisterNotifications {
938 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
941 [[NSNotificationCenter defaultCenter] removeObserver:self];
947 [
self removeInternalPlugins];
948 [
self deregisterNotifications];
950 [
self.keyboardInsetManager invalidate];
951 [
self invalidateTouchRateCorrectionVSyncClient];
955 _scrollView.delegate = nil;
956 _hoverGestureRecognizer.delegate = nil;
957 _discreteScrollingPanGestureRecognizer.delegate = nil;
958 _continuousScrollingPanGestureRecognizer.delegate = nil;
959 _pinchGestureRecognizer.delegate = nil;
960 _rotationGestureRecognizer.delegate = nil;
963#pragma mark - Application lifecycle notifications
965- (void)applicationBecameActive:(NSNotification*)notification {
967 [
self appOrSceneBecameActive];
970- (void)applicationWillResignActive:(NSNotification*)notification {
972 [
self appOrSceneWillResignActive];
975- (void)applicationWillTerminate:(NSNotification*)notification {
976 [
self appOrSceneWillTerminate];
979- (void)applicationDidEnterBackground:(NSNotification*)notification {
980 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
981 [
self appOrSceneDidEnterBackground];
984- (void)applicationWillEnterForeground:(NSNotification*)notification {
985 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
986 [
self appOrSceneWillEnterForeground];
989#pragma mark - Scene lifecycle notifications
991- (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
992 if (![
self shouldHandleSceneNotification:notification]) {
996 [
self appOrSceneBecameActive];
999- (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1000 if (![
self shouldHandleSceneNotification:notification]) {
1004 [
self appOrSceneWillResignActive];
1007- (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1008 if (![
self shouldHandleSceneNotification:notification]) {
1011 [
self appOrSceneWillTerminate];
1014- (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1015 if (![
self shouldHandleSceneNotification:notification]) {
1019 [
self appOrSceneDidEnterBackground];
1022- (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1023 if (![
self shouldHandleSceneNotification:notification]) {
1027 [
self appOrSceneWillEnterForeground];
1030#pragma mark - Lifecycle shared
1032- (void)appOrSceneBecameActive {
1033 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = NO;
1034 if (_viewportMetrics.physical_width) {
1035 [
self surfaceUpdated:YES];
1037 [
self performSelector:@selector(goToApplicationLifecycle:)
1038 withObject:@"AppLifecycleState.resumed"
1042- (void)appOrSceneWillResignActive {
1043 [NSObject cancelPreviousPerformRequestsWithTarget:self
1044 selector:@selector(goToApplicationLifecycle:)
1045 object:@"AppLifecycleState.resumed"];
1046 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1049- (void)appOrSceneWillTerminate {
1050 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1051 [
self.engine destroyContext];
1054- (void)appOrSceneDidEnterBackground {
1055 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = YES;
1056 [
self surfaceUpdated:NO];
1057 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1060- (void)appOrSceneWillEnterForeground {
1061 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1065- (void)goToApplicationLifecycle:(nonnull NSString*)state {
1068 if (
self.viewIfLoaded.window) {
1069 [
self.engine.lifecycleChannel sendMessage:state];
1073#pragma mark - Touch event handling
1077 case UITouchPhaseBegan:
1079 case UITouchPhaseMoved:
1080 case UITouchPhaseStationary:
1084 case UITouchPhaseEnded:
1086 case UITouchPhaseCancelled:
1090 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1098 switch (touch.type) {
1099 case UITouchTypeDirect:
1100 case UITouchTypeIndirect:
1102 case UITouchTypeStylus:
1104 case UITouchTypeIndirectPointer:
1107 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1118- (void)dispatchTouches:(NSSet*)touches
1119 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1120 event:(UIEvent*)event {
1145 NSUInteger touches_to_remove_count = 0;
1146 for (UITouch* touch in touches) {
1147 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1148 touches_to_remove_count++;
1153 [
self triggerTouchRateCorrectionIfNeeded:touches];
1155 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1157 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1159 size_t pointer_index = 0;
1161 for (UITouch* touch in touches) {
1162 CGPoint windowCoordinates = [touch locationInView:self.view];
1165 pointer_data.
Clear();
1170 pointer_data.
change = overridden_change !=
nullptr
1171 ? *overridden_change
1172 : PointerDataChangeFromUITouchPhase(touch.phase);
1174 pointer_data.
kind = DeviceKindFromTouchType(touch);
1176 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1183 pointer_data.
physical_x = windowCoordinates.x * scale;
1184 pointer_data.
physical_y = windowCoordinates.y * scale;
1190 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1193 switch (pointer_data.
change) {
1195 [
self.ongoingTouches addObject:deviceKey];
1199 [
self.ongoingTouches removeObject:deviceKey];
1217 pointer_data.
pressure = touch.force;
1218 pointer_data.
pressure_max = touch.maximumPossibleForce;
1220 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1221 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1236 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1256 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1258 if (@available(iOS 13.4, *)) {
1259 if (event !=
nullptr) {
1260 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1263 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1269 packet->SetPointerData(pointer_index++, pointer_data);
1271 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1274 packet->SetPointerData(pointer_index++, remove_pointer_data);
1278 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1281- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1282 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1285- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1286 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1289- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1290 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1293- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1294 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1297- (void)forceTouchesCancelled:(NSSet*)touches {
1299 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1302- (
BOOL)platformViewShouldAcceptTouchAtTouchBeganLocation:(CGPoint)location {
1304 return [
self.engine platformViewShouldAcceptTouchAtTouchBeganLocation:point
1305 viewId:self.viewIdentifier];
1308#pragma mark - Touch events rate correction
1310- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1311 if (_touchRateCorrectionVSyncClient != nil) {
1315 double displayRefreshRate = FlutterDisplayLinkManager.displayRefreshRate;
1316 const double epsilon = 0.1;
1317 if (displayRefreshRate < 60.0 + epsilon) {
1325 void (^
callback)(CFTimeInterval, CFTimeInterval) =
1326 ^(CFTimeInterval startTime, CFTimeInterval targetTime) {
1329 _touchRateCorrectionVSyncClient = [[FlutterVSyncClient alloc]
1330 initWithTaskRunner:self.engine.platformTaskRunner
1331 isVariableRefreshRateEnabled:FlutterDisplayLinkManager.maxRefreshRateEnabledOnIPhone
1332 maxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate
1334 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1337- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1338 if (_touchRateCorrectionVSyncClient == nil) {
1346 BOOL isUserInteracting = NO;
1347 for (UITouch* touch in touches) {
1348 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1349 isUserInteracting = YES;
1354 if (isUserInteracting &&
self.engine.viewController ==
self) {
1355 [_touchRateCorrectionVSyncClient await];
1357 [_touchRateCorrectionVSyncClient pause];
1361- (void)invalidateTouchRateCorrectionVSyncClient {
1362 [_touchRateCorrectionVSyncClient invalidate];
1363 _touchRateCorrectionVSyncClient = nil;
1366#pragma mark - Handle view resizing
1368- (void)updateViewportMetricsIfNeeded {
1369 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1372 if (
self.engine.viewController ==
self) {
1373 [
self.engine updateViewportMetrics:_viewportMetrics];
1377- (void)viewDidLayoutSubviews {
1378 CGRect viewBounds =
self.view.bounds;
1379 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1382 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1386 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1387 _viewportMetrics.device_pixel_ratio = scale;
1388 [
self setViewportMetricsSize];
1389 [
self checkAndUpdateAutoResizeConstraints];
1390 [
self setViewportMetricsPaddings];
1391 [
self updateViewportMetricsIfNeeded];
1398 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1399 [
self surfaceUpdated:YES];
1400#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1401 NSTimeInterval timeout = 0.2;
1403 NSTimeInterval timeout = 0.1;
1406 waitForFirstFrameSync:timeout
1407 callback:^(BOOL didTimeout) {
1409 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1410 "This may happen in unoptimized builds. If this is"
1411 "a release build, you should load a less complex "
1412 "frame to avoid the timeout."];
1418- (
BOOL)isAutoResizable {
1419 return self.flutterView.autoResizable;
1422- (void)setAutoResizable:(
BOOL)value {
1424 self.flutterView.contentMode = UIViewContentModeCenter;
1427- (void)checkAndUpdateAutoResizeConstraints {
1428 if (!
self.isAutoResizable) {
1432 [
self updateAutoResizeConstraints];
1456- (void)updateAutoResizeConstraints {
1457 BOOL hasBeenAutoResized = NO;
1458 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1460 hasBeenAutoResized = YES;
1464 if (!hasBeenAutoResized) {
1465 self.sizeBeforeAutoResized =
self.view.frame.size;
1468 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1469 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1470 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1471 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1475 if (maxWidth == 0) {
1476 maxWidth = CGFLOAT_MAX;
1479 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1480 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1481 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1482 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1483 @"constraints (e.g., using SizedBox or Container)."];
1485 if (maxHeight == 0) {
1486 maxHeight = CGFLOAT_MAX;
1489 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1490 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1491 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1492 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1493 @"constraints (e.g., using SizedBox or Container)."];
1495 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1496 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1497 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1498 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1501- (void)viewSafeAreaInsetsDidChange {
1502 [
self setViewportMetricsPaddings];
1503 [
self updateViewportMetricsIfNeeded];
1504 [
super viewSafeAreaInsetsDidChange];
1508- (void)setViewportMetricsSize {
1509 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1514 CGFloat scale = screen.scale;
1515 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1516 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1518 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1519 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1520 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1521 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1527- (void)setViewportMetricsPaddings {
1528 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1533 CGFloat scale = screen.scale;
1534 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1535 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1536 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1537 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1540#pragma mark - Keyboard events
1542- (void)keyboardWillShowNotification:(NSNotification*)notification {
1547 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1550- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1555 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1558- (void)keyboardWillBeHidden:(NSNotification*)notification {
1562 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1565- (void)handlePressEvent:(FlutterUIPressProxy*)press
1566 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1567 if (@available(iOS 13.4, *)) {
1572 [
self.keyboardManager handlePress:press nextAction:next];
1588- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1589 [
super pressesBegan:presses withEvent:event];
1592- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1593 [
super pressesChanged:presses withEvent:event];
1596- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1597 [
super pressesEnded:presses withEvent:event];
1600- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1601 [
super pressesCancelled:presses withEvent:event];
1609- (void)pressesBegan:(NSSet<UIPress*>*)presses
1610 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1611 if (@available(iOS 13.4, *)) {
1613 for (UIPress* press in presses) {
1614 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1616 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1620 [
super pressesBegan:presses withEvent:event];
1624- (void)pressesChanged:(NSSet<UIPress*>*)presses
1625 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1626 if (@available(iOS 13.4, *)) {
1628 for (UIPress* press in presses) {
1629 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1631 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1635 [
super pressesChanged:presses withEvent:event];
1639- (void)pressesEnded:(NSSet<UIPress*>*)presses
1640 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1641 if (@available(iOS 13.4, *)) {
1643 for (UIPress* press in presses) {
1644 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1646 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1650 [
super pressesEnded:presses withEvent:event];
1654- (void)pressesCancelled:(NSSet<UIPress*>*)presses
1655 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1656 if (@available(iOS 13.4, *)) {
1658 for (UIPress* press in presses) {
1659 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1661 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1665 [
super pressesCancelled:presses withEvent:event];
1669#pragma mark - Orientation updates
1671- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1674 dispatch_async(dispatch_get_main_queue(), ^{
1675 NSDictionary* info = notification.userInfo;
1676 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1677 if (update == nil) {
1680 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1684- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1685 API_AVAILABLE(ios(16.0)) {
1686 for (UIScene* windowScene in windowScenes) {
1687 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
1688 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1689 initWithInterfaceOrientations:self.orientationPreferences];
1690 [(UIWindowScene*)windowScene
1691 requestGeometryUpdateWithPreferences:preference
1692 errorHandler:^(NSError* error) {
1693 os_log_error(OS_LOG_DEFAULT,
1694 "Failed to change device orientation: %@", error);
1696 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
1700- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
1701 if (new_preferences !=
self.orientationPreferences) {
1702 self.orientationPreferences = new_preferences;
1704 if (@available(iOS 16.0, *)) {
1706 NSSet<UIScene*>* scenes = [NSSet set];
1707 if (flutterApplication) {
1708 scenes = [flutterApplication.connectedScenes
1709 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
1710 id scene, NSDictionary* bindings) {
1711 return [scene isKindOfClass:[UIWindowScene class]];
1713 }
else if (
self.flutterWindowSceneIfViewLoaded) {
1714 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
1716 [
self requestGeometryUpdateForWindowScenes:scenes];
1718 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
1719 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
1723 @"Accessing the interface orientation when the window scene is unavailable."];
1726 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
1727 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
1728 [UIViewController attemptRotationToDeviceOrientation];
1730 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
1734 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
1735 forKey:@"orientation"];
1736 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
1737 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
1738 forKey:@"orientation"];
1739 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
1740 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
1741 forKey:@"orientation"];
1742 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
1743 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
1744 forKey:@"orientation"];
1751- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
1752 self.isHomeIndicatorHidden = YES;
1755- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
1756 self.isHomeIndicatorHidden = NO;
1759- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
1760 if (hideHomeIndicator != _isHomeIndicatorHidden) {
1761 _isHomeIndicatorHidden = hideHomeIndicator;
1762 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
1766- (
BOOL)prefersHomeIndicatorAutoHidden {
1767 return self.isHomeIndicatorHidden;
1770- (
BOOL)shouldAutorotate {
1774- (NSUInteger)supportedInterfaceOrientations {
1775 return self.orientationPreferences;
1778#pragma mark - Accessibility
1780- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1785 int32_t flags = [
self.accessibilityFeatures flags];
1786#if TARGET_OS_SIMULATOR
1792 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
1793 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
1794 [
self.accessibilityFeatures isSpeakScreenEnabled];
1796 [
self.engine enableSemantics:enabled withFlags:flags];
1799- (
BOOL)accessibilityPerformEscape {
1801 if (navigationChannel) {
1808#pragma mark - Set user settings
1810- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1811 [
super traitCollectionDidChange:previousTraitCollection];
1812 [
self onUserSettingsChanged:nil];
1816 if (
self.isAutoResizable) {
1817 [
self.flutterView resetIntrinsicContentSize];
1821- (void)onUserSettingsChanged:(NSNotification*)notification {
1822 [
self.engine.settingsChannel sendMessage:@{
1823 @"textScaleFactor" : @(
self.textScaleFactor),
1825 @"platformBrightness" :
self.brightnessMode,
1826 @"platformContrast" : self.contrastMode,
1827 @"nativeSpellCheckServiceDefined" : @YES,
1828 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
1832- (CGFloat)textScaleFactor {
1834 if (flutterApplication == nil) {
1835 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
1839 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
1845 const CGFloat xs = 14;
1846 const CGFloat s = 15;
1847 const CGFloat m = 16;
1848 const CGFloat l = 17;
1849 const CGFloat xl = 19;
1850 const CGFloat xxl = 21;
1851 const CGFloat xxxl = 23;
1854 const CGFloat ax1 = 28;
1855 const CGFloat ax2 = 33;
1856 const CGFloat ax3 = 40;
1857 const CGFloat ax4 = 47;
1858 const CGFloat ax5 = 53;
1862 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
1864 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
1866 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
1868 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
1870 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
1872 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
1874 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
1876 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
1878 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
1880 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
1882 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
1884 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
1891- (
BOOL)supportsShowingSystemContextMenu {
1892 if (@available(iOS 16.0, *)) {
1902- (NSString*)brightnessMode {
1903 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
1905 if (style == UIUserInterfaceStyleDark) {
1915- (NSString*)contrastMode {
1916 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
1918 if (contrast == UIAccessibilityContrastHigh) {
1925#pragma mark - Status bar style
1927- (UIStatusBarStyle)preferredStatusBarStyle {
1928 return self.statusBarStyle;
1931- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
1934 dispatch_async(dispatch_get_main_queue(), ^{
1940 NSDictionary* info = notification.userInfo;
1941 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
1942 if (update == nil) {
1946 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
1947 if (style != strongSelf.statusBarStyle) {
1948 strongSelf.statusBarStyle = style;
1949 [strongSelf setNeedsStatusBarAppearanceUpdate];
1954- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
1955 if (hidden !=
self.flutterPrefersStatusBarHidden) {
1956 self.flutterPrefersStatusBarHidden = hidden;
1957 [
self setNeedsStatusBarAppearanceUpdate];
1961- (
BOOL)prefersStatusBarHidden {
1962 return self.flutterPrefersStatusBarHidden;
1965#pragma mark - Platform views
1968 return self.engine.platformViewsController;
1971- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1972 return self.engine.binaryMessenger;
1975#pragma mark - FlutterBinaryMessenger
1977- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1978 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
1981- (void)sendOnChannel:(NSString*)channel
1982 message:(NSData*)message
1984 NSAssert(
channel,
@"The channel must not be null");
1985 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
1988- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1989 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
1993 binaryMessageHandler:
1995 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
1999 setMessageHandlerOnChannel:(NSString*)channel
2001 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2002 NSAssert(
channel,
@"The channel must not be null");
2003 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2004 binaryMessageHandler:handler
2005 taskQueue:taskQueue];
2009 [
self.engine.binaryMessenger cleanUpConnection:connection];
2012#pragma mark - FlutterTextureRegistry
2015 return [
self.engine.textureRegistry registerTexture:texture];
2018- (void)unregisterTexture:(int64_t)textureId {
2019 [
self.engine.textureRegistry unregisterTexture:textureId];
2022- (void)textureFrameAvailable:(int64_t)textureId {
2023 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2026- (NSString*)lookupKeyForAsset:(NSString*)asset {
2030- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2034- (
id<FlutterPluginRegistry>)pluginRegistry {
2038+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2039 return UIAccessibilityIsVoiceOverRunning();
2042#pragma mark - FlutterPluginRegistry
2044- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
2045 return [
self.engine registrarForPlugin:pluginKey];
2048- (
BOOL)hasPlugin:(NSString*)pluginKey {
2049 return [
self.engine hasPlugin:pluginKey];
2052- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2053 return [
self.engine valuePublishedByPlugin:pluginKey];
2056- (void)presentViewController:(UIViewController*)viewControllerToPresent
2058 completion:(
void (^)(
void))completion {
2059 self.isPresentingViewControllerAnimating = YES;
2061 [
super presentViewController:viewControllerToPresent
2064 weakSelf.isPresentingViewControllerAnimating = NO;
2071- (
BOOL)isPresentingViewController {
2072 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2075- (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2076 API_AVAILABLE(ios(13.4)) {
2077 CGPoint
location = [gestureRecognizer locationInView:self.view];
2078 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2081 pointer_data.
Clear();
2085 return pointer_data;
2088- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2089 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2090 API_AVAILABLE(ios(13.4)) {
2094- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2095 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2096 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2097 event.type == UIEventTypeScroll) {
2100 pointer_data.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2105 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2108 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2109 packet->SetPointerData(0, pointer_data);
2110 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2111 self.scrollInertiaEventAppKitDeadline = 0;
2118- (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2122 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2126 switch (_hoverGestureRecognizer.state) {
2127 case UIGestureRecognizerStateBegan:
2130 case UIGestureRecognizerStateChanged:
2133 case UIGestureRecognizerStateEnded:
2134 case UIGestureRecognizerStateCancelled:
2144 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2145 BOOL isRunningOnMac = NO;
2149 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2151 time >
self.scrollInertiaEventStartline) {
2155 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2156 packet->SetPointerData(0, pointer_data);
2158 inertia_cancel.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2162 packet->SetPointerData(1, inertia_cancel);
2163 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2164 self.scrollInertiaEventStartline = DBL_MAX;
2166 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2167 packet->SetPointerData(0, pointer_data);
2168 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2172- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2173 CGPoint translation = [recognizer translationInView:self.view];
2174 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2176 translation.x *= scale;
2177 translation.y *= scale;
2180 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2191 if (recognizer.state != UIGestureRecognizerStateEnded) {
2197 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2198 packet->SetPointerData(0, pointer_data);
2199 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2202- (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2203 CGPoint translation = [recognizer translationInView:self.view];
2204 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2207 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2210 switch (recognizer.state) {
2211 case UIGestureRecognizerStateBegan:
2214 case UIGestureRecognizerStateChanged:
2216 pointer_data.
pan_x = translation.x * scale;
2217 pointer_data.
pan_y = translation.y * scale;
2220 pointer_data.
scale = 1;
2222 case UIGestureRecognizerStateEnded:
2223 case UIGestureRecognizerStateCancelled:
2224 self.scrollInertiaEventStartline =
2225 [[NSProcessInfo processInfo] systemUptime] +
2235 self.scrollInertiaEventAppKitDeadline =
2236 [[NSProcessInfo processInfo] systemUptime] +
2237 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2238 [recognizer velocityInView:self.view].y))) -
2244 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2245 (
long)recognizer.state);
2249 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2250 packet->SetPointerData(0, pointer_data);
2251 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2254- (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2256 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2259 switch (recognizer.state) {
2260 case UIGestureRecognizerStateBegan:
2263 case UIGestureRecognizerStateChanged:
2265 pointer_data.
scale = recognizer.scale;
2266 pointer_data.
rotation = _rotationGestureRecognizer.rotation;
2268 case UIGestureRecognizerStateEnded:
2269 case UIGestureRecognizerStateCancelled:
2274 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2275 (
long)recognizer.state);
2279 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2280 packet->SetPointerData(0, pointer_data);
2281 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2284#pragma mark - State Restoration
2286- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2287 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2288 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2289 length:restorationData.length
2290 forKey:kFlutterRestorationStateAppData];
2291 [
super encodeRestorableStateWithCoder:coder];
2294- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2295 NSUInteger restorationDataLength;
2296 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2297 returnedLength:&restorationDataLength];
2298 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2299 [
self.engine.restorationPlugin setRestorationData:restorationData];
2303 return self.engine.restorationPlugin;
2307 return self.engine.textInputPlugin;
2310#pragma mark - FlutterKeyboardInsetManagerDelegate
2312- (void)updateViewportMetricsWithInset:(CGFloat)inset {
2313 _viewportMetrics.physical_view_inset_bottom = inset;
2314 [
self updateViewportMetricsIfNeeded];
2317- (CGFloat)physicalViewInsetBottom {
2318 return _viewportMetrics.physical_view_inset_bottom;
2321- (
BOOL)isPadInSlideOverOrStageManagerMode {
2322 if (
self.view.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
2323 self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
2324 self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
2330- (CGRect)convertViewRectToScreen:(CGRect)rect {
2331 return [
self.view convertRect:rect
2332 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
int64_t FlutterBinaryMessengerConnection
UIPanGestureRecognizer *discreteScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
UIPinchGestureRecognizer *pinchGestureRecognizer API_AVAILABLE(ios(13.4))
UIHoverGestureRecognizer *hoverGestureRecognizer API_AVAILABLE(ios(13.4))
UIPanGestureRecognizer *continuousScrollingPanGestureRecognizer API_AVAILABLE(ios(13.4))
void(* FlutterKeyEventCallback)(bool, void *)
FlutterDesktopBinaryReply callback
#define FML_DLOG(severity)
#define FML_CHECK(condition)
#define FML_DCHECK(condition)
NSString * lookupKeyForAsset:fromPackage:(NSString *asset,[fromPackage] NSString *package)
NSString * lookupKeyForAsset:(NSString *asset)
FlutterViewController * viewController
Coordinates the animation of the bottom viewport inset in response to system keyboard visibility chan...
UIApplication * application
void setUpIndirectScribbleInteraction:(id< FlutterViewResponder > viewResponder)
UIView * splashScreenView
FlutterViewIdentifier viewIdentifier
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
instancetype initWithCoder
FlutterTextInputPlugin * textInputPlugin
NSNotificationName const FlutterViewControllerHideHomeIndicator
FlutterSplashScreenManager * _splashScreenManager
static NSString *const kFlutterRestorationStateAppData
static FLUTTER_ASSERT_ARC constexpr int kMicrosecondsPerSecond
NSNotificationName const FlutterViewControllerShowHomeIndicator
NSNotificationName const FlutterSemanticsUpdateNotification
static constexpr CGFloat kScrollViewContentSize
NSNotificationName const FlutterViewControllerWillDealloc
constexpr int64_t kFlutterImplicitViewId
@ kPointerButtonMouseSecondary
@ kPointerButtonMousePrimary
TracingResult GetTracingResult()
Returns if a tracing check has been performed and its result. To enable tracing, the Settings object ...
int64_t pointer_identifier
#define TRACE_EVENT0(category_group, name)