5#define FML_USED_ON_EMBEDDER
18#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
53 @"FlutterViewControllerHideHomeIndicator";
55 @"FlutterViewControllerShowHomeIndicator";
71 FlutterKeyboardInsetManagerDelegate>
80@property(nonatomic, strong)
void (^flutterViewRenderedCallback)(void);
82@property(nonatomic, strong) FlutterSplashScreenManager* splashScreenManager;
84@property(nonatomic, assign) UIInterfaceOrientationMask orientationPreferences;
85@property(nonatomic, assign) UIStatusBarStyle statusBarStyle;
86@property(nonatomic, assign)
BOOL initialized;
87@property(nonatomic, assign)
BOOL engineNeedsLaunch;
88@property(nonatomic, assign)
BOOL awokenFromNib;
91@property(nonatomic, assign)
BOOL isHomeIndicatorHidden;
92@property(nonatomic, assign)
BOOL isPresentingViewControllerAnimating;
95@property(nonatomic, assign)
BOOL flutterPrefersStatusBarHidden;
97@property(nonatomic, strong) NSMutableSet<NSNumber*>* ongoingTouches;
102@property(nonatomic, strong) UIScrollView* scrollView;
107@property(nonatomic, assign)
BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
113@property(nonatomic, assign) NSTimeInterval scrollInertiaEventStartline;
121@property(nonatomic, assign) NSTimeInterval scrollInertiaEventAppKitDeadline;
133@property(nonatomic, assign) CGSize sizeBeforeAutoResized;
139@property(nonatomic, strong)
142@property(nonatomic, strong)
143 UIPanGestureRecognizer* discreteScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
145@property(nonatomic, strong)
146 UIPanGestureRecognizer* continuousScrollingPanGestureRecognizer
API_AVAILABLE(ios(13.4));
148@property(nonatomic, strong)
151@property(nonatomic, strong)
152 UIRotationGestureRecognizer* rotationGestureRecognizer
API_AVAILABLE(ios(13.4));
155- (void)addInternalPlugins;
156- (void)deregisterNotifications;
159- (void)onFirstFrameRendered;
172@synthesize viewOpaque = _viewOpaque;
173@synthesize displayingFlutterUI = _displayingFlutterUI;
175- (FlutterSplashScreenManager*)splashScreenManager {
185@dynamic viewIdentifier;
187#pragma mark - Manage and override all designated initializers
190 nibName:(nullable NSString*)nibName
191 bundle:(nullable NSBundle*)nibBundle {
192 FML_CHECK(
engine) <<
"initWithEngine:nibName:bundle: must be called with non-nil engine";
193 self = [
super initWithNibName:nibName bundle:nibBundle];
197 NSString* errorMessage =
198 [NSString stringWithFormat:
199 @"The supplied FlutterEngine %@ is already used with FlutterViewController "
200 "instance %@. One instance of the FlutterEngine can only be attached to "
201 "one FlutterViewController at a time. Set FlutterEngine.viewController to "
202 "nil before attaching it to another FlutterViewController.",
203 engine.description, engine.viewController.description];
204 [FlutterLogger logError:errorMessage];
207 _engineNeedsLaunch = NO;
208 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
209 opaque:self.isViewOpaque
210 enableWideGamut:engine.project.isWideGamutEnabled];
211 _ongoingTouches = [[NSMutableSet alloc] init];
215 [
self performCommonViewControllerInitialization];
216 [engine setViewController:self];
223 nibName:(NSString*)nibName
224 bundle:(NSBundle*)nibBundle {
225 self = [
super initWithNibName:nibName bundle:nibBundle];
229 [
self sharedSetupWithProject:project initialRoute:nil];
236 initialRoute:(NSString*)initialRoute
237 nibName:(NSString*)nibName
238 bundle:(NSBundle*)nibBundle {
239 self = [
super initWithNibName:nibName bundle:nibBundle];
243 [
self sharedSetupWithProject:project initialRoute:initialRoute];
249- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
250 return [
self initWithProject:nil nibName:nil bundle:nil];
254 self = [
super initWithCoder:aDecoder];
258- (void)awakeFromNib {
259 [
super awakeFromNib];
260 self.awokenFromNib = YES;
262 [
self sharedSetupWithProject:nil initialRoute:nil];
266- (instancetype)init {
267 return [
self initWithProject:nil nibName:nil bundle:nil];
271 initialRoute:(nullable NSString*)initialRoute {
274 if ([appDelegate respondsToSelector:@selector(takeLaunchEngine)]) {
279 engine = [appDelegate takeLaunchEngine];
284 [appDelegate takeLaunchEngine];
296 allowHeadlessExecution:self.engineAllowHeadlessExecution
297 restorationEnabled:self.restorationIdentifier != nil];
305 _flutterView = [[
FlutterView alloc] initWithDelegate:_engine
307 enableWideGamut:engine.project.isWideGamutEnabled];
308 [_engine createShell:nil libraryURI:nil initialRoute:initialRoute];
314 BOOL performedCallback = [_engine performImplicitEngineCallback];
318 respondsToSelector:@selector(pluginRegistrant)]) {
319 NSObject<FlutterPluginRegistrant>* pluginRegistrant =
321 [pluginRegistrant registerWithRegistry:self];
322 performedCallback = YES;
330 id applicationLifeCycleDelegate = ((
FlutterAppDelegate*)appDelegate).lifeCycleDelegate;
331 [applicationLifeCycleDelegate
332 sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application];
333 [applicationLifeCycleDelegate
334 sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application];
337 _engineNeedsLaunch = YES;
338 _ongoingTouches = [[NSMutableSet alloc] init];
342 [
self.splashScreenManager loadDefaultSplashScreenView];
343 [
self performCommonViewControllerInitialization];
346- (
BOOL)isViewOpaque {
350- (void)setViewOpaque:(
BOOL)value {
352 if (
self.flutterView.layer.opaque != value) {
354 [
self.flutterView.layer setNeedsLayout];
358#pragma mark - Common view controller initialization tasks
360- (void)performCommonViewControllerInitialization {
366 _orientationPreferences = UIInterfaceOrientationMaskAll;
367 _statusBarStyle = UIStatusBarStyleDefault;
369 _accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
374 [
self setUpNotificationCenterObservers];
377- (void)setUpNotificationCenterObservers {
378 NSNotificationCenter*
center = [NSNotificationCenter defaultCenter];
379 [center addObserver:self
380 selector:@selector(onOrientationPreferencesUpdated:)
381 name:@(flutter::kOrientationUpdateNotificationName)
384 [center addObserver:self
385 selector:@selector(onPreferredStatusBarStyleUpdated:)
386 name:@(flutter::kOverlayStyleUpdateNotificationName)
390 [
self setUpApplicationLifecycleNotifications:center];
392 [
self setUpSceneLifecycleNotifications:center];
395 [center addObserver:self
396 selector:@selector(keyboardWillChangeFrame:)
397 name:UIKeyboardWillChangeFrameNotification
400 [center addObserver:self
401 selector:@selector(keyboardWillShowNotification:)
402 name:UIKeyboardWillShowNotification
405 [center addObserver:self
406 selector:@selector(keyboardWillBeHidden:)
407 name:UIKeyboardWillHideNotification
410 for (NSString* notification in [
self.accessibilityFeatures observedNotificationNames]) {
411 [center addObserver:self
412 selector:@selector(onAccessibilityStatusChanged:)
417 [center addObserver:self
418 selector:@selector(onUserSettingsChanged:)
419 name:UIContentSizeCategoryDidChangeNotification
422 [center addObserver:self
423 selector:@selector(onHideHomeIndicatorNotification:)
424 name:FlutterViewControllerHideHomeIndicator
427 [center addObserver:self
428 selector:@selector(onShowHomeIndicatorNotification:)
429 name:FlutterViewControllerShowHomeIndicator
433- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
434 [center addObserver:self
435 selector:@selector(sceneBecameActive:)
436 name:UISceneDidActivateNotification
439 [center addObserver:self
440 selector:@selector(sceneWillResignActive:)
441 name:UISceneWillDeactivateNotification
444 [center addObserver:self
445 selector:@selector(sceneWillDisconnect:)
446 name:UISceneDidDisconnectNotification
449 [center addObserver:self
450 selector:@selector(sceneDidEnterBackground:)
451 name:UISceneDidEnterBackgroundNotification
454 [center addObserver:self
455 selector:@selector(sceneWillEnterForeground:)
456 name:UISceneWillEnterForegroundNotification
460- (void)setUpApplicationLifecycleNotifications:(NSNotificationCenter*)center {
461 [center addObserver:self
462 selector:@selector(applicationBecameActive:)
463 name:UIApplicationDidBecomeActiveNotification
466 [center addObserver:self
467 selector:@selector(applicationWillResignActive:)
468 name:UIApplicationWillResignActiveNotification
471 [center addObserver:self
472 selector:@selector(applicationWillTerminate:)
473 name:UIApplicationWillTerminateNotification
476 [center addObserver:self
477 selector:@selector(applicationDidEnterBackground:)
478 name:UIApplicationDidEnterBackgroundNotification
481 [center addObserver:self
482 selector:@selector(applicationWillEnterForeground:)
483 name:UIApplicationWillEnterForegroundNotification
487- (void)setInitialRoute:(NSString*)route {
488 [
self.engine.navigationChannel invokeMethod:@"setInitialRoute" arguments:route];
492 [
self.engine.navigationChannel invokeMethod:@"popRoute" arguments:nil];
495- (void)pushRoute:(NSString*)route {
496 [
self.engine.navigationChannel invokeMethod:@"pushRoute" arguments:route];
499#pragma mark - Loading the view
501static UIView* GetViewOrPlaceholder(UIView* existing_view) {
503 return existing_view;
506 auto placeholder = [[UIView alloc] init];
508 placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
509 placeholder.backgroundColor = UIColor.systemBackgroundColor;
510 placeholder.autoresizesSubviews = YES;
516 auto messageLabel = [[UILabel alloc] init];
517 messageLabel.numberOfLines = 0u;
518 messageLabel.textAlignment = NSTextAlignmentCenter;
519 messageLabel.autoresizingMask =
520 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
522 @"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling, "
523 @"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
524 @"modes to enable launching from the home screen.";
525 [placeholder addSubview:messageLabel];
532 self.view = GetViewOrPlaceholder(
self.flutterView);
533 self.view.multipleTouchEnabled = YES;
534 self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
536 [
self installSplashScreenViewIfNecessary];
539 UIScrollView* scrollView = [[UIScrollView alloc] init];
540 scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
542 scrollView.backgroundColor = UIColor.whiteColor;
543 scrollView.delegate =
self;
549 [
self.view addSubview:scrollView];
550 self.scrollView = scrollView;
553- (
flutter::PointerData)generatePointerDataForFake {
555 pointer_data.
Clear();
564- (
BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
568 if (
self.isViewLoaded) {
570 [
self.engine onStatusBarTap];
575#pragma mark - Managing launch views
577- (void)installSplashScreenViewIfNecessary {
583 if (
self.splashScreenView && (
self.isBeingPresented ||
self.isMovingToParentViewController)) {
585 [
self.splashScreenView removeFromSuperview];
586 self.splashScreenView = nil;
591 [
self.splashScreenManager installSplashScreenViewAsSubviewOf:self.view];
594+ (
BOOL)automaticallyNotifiesObserversOfDisplayingFlutterUI {
598- (void)setDisplayingFlutterUI:(
BOOL)displayingFlutterUI {
599 if (_displayingFlutterUI != displayingFlutterUI) {
600 if (displayingFlutterUI == YES) {
601 if (!
self.viewIfLoaded.window) {
605 [
self willChangeValueForKey:@"displayingFlutterUI"];
606 _displayingFlutterUI = displayingFlutterUI;
607 [
self didChangeValueForKey:@"displayingFlutterUI"];
611- (void)callViewRenderedCallback {
612 self.displayingFlutterUI = YES;
613 if (
self.flutterViewRenderedCallback) {
614 self.flutterViewRenderedCallback();
615 self.flutterViewRenderedCallback = nil;
619- (void)onFirstFrameRendered {
620 if (
self.splashScreenView) {
622 [
self.splashScreenManager removeSplashScreenWithCompletion:^{
623 [weakSelf callViewRenderedCallback];
626 [
self callViewRenderedCallback];
630- (void)installFirstFrameCallback {
635 [
self.engine installFirstFrameCallback:^{
636 [weakSelf onFirstFrameRendered];
640#pragma mark - Properties
642- (int64_t)viewIdentifier {
648- (
BOOL)loadDefaultSplashScreenView {
649 return [
self.splashScreenManager loadDefaultSplashScreenView];
652- (UIView*)splashScreenView {
656- (void)setSplashScreenView:(UIView*)view {
657 self.splashScreenManager.splashScreenView =
view;
660- (void)setFlutterViewDidRenderCallback:(
void (^)(
void))callback {
661 _flutterViewRenderedCallback =
callback;
664- (UISceneActivationState)activationState {
665 return self.flutterWindowSceneIfViewLoaded.activationState;
668- (
BOOL)stateIsActive {
671 BOOL isActive = flutterApplication
672 ? [
self isApplicationStateMatching:UIApplicationStateActive
673 withApplication:flutterApplication]
674 : [
self isSceneStateMatching:UISceneActivationStateForegroundActive];
678- (
BOOL)stateIsBackground {
681 return flutterApplication ? [
self isApplicationStateMatching:UIApplicationStateBackground
682 withApplication:flutterApplication]
683 : [
self isSceneStateMatching:UISceneActivationStateBackground];
686- (
BOOL)isApplicationStateMatching:(UIApplicationState)match
687 withApplication:(UIApplication*)application {
688 switch (application.applicationState) {
689 case UIApplicationStateActive:
690 case UIApplicationStateInactive:
691 case UIApplicationStateBackground:
692 return application.applicationState == match;
696- (
BOOL)isSceneStateMatching:(UISceneActivationState)match API_AVAILABLE(ios(13.0)) {
697 switch (
self.activationState) {
698 case UISceneActivationStateForegroundActive:
699 case UISceneActivationStateUnattached:
700 case UISceneActivationStateForegroundInactive:
701 case UISceneActivationStateBackground:
702 return self.activationState == match;
706#pragma mark - Surface creation and teardown updates
708- (void)surfaceUpdated:(
BOOL)appeared {
716 [
self installFirstFrameCallback];
717 self.platformViewsController.flutterView =
self.flutterView;
718 self.platformViewsController.flutterViewController =
self;
719 [
self.engine notifyViewCreated];
721 self.displayingFlutterUI = NO;
722 [
self.engine notifyViewDestroyed];
723 self.platformViewsController.flutterView = nil;
724 self.platformViewsController.flutterViewController = nil;
728#pragma mark - UIViewController lifecycle notifications
733 if (
self.engine &&
self.engineNeedsLaunch) {
734 [
self.engine launchEngine:nil libraryURI:nil entrypointArgs:nil];
735 [
self.engine setViewController:self];
736 self.engineNeedsLaunch = NO;
737 }
else if (
self.engine.viewController ==
self) {
738 [
self.engine attachView];
742 [
self addInternalPlugins];
745 [
self createTouchRateCorrectionVSyncClientIfNeeded];
747 if (@available(iOS 13.4, *)) {
748 _hoverGestureRecognizer =
749 [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hoverEvent:)];
750 _hoverGestureRecognizer.delegate =
self;
751 [
self.flutterView addGestureRecognizer:_hoverGestureRecognizer];
753 _discreteScrollingPanGestureRecognizer =
754 [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(discreteScrollEvent:)];
755 _discreteScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskDiscrete;
760 _discreteScrollingPanGestureRecognizer.allowedTouchTypes = @[];
761 _discreteScrollingPanGestureRecognizer.delegate =
self;
762 [
self.flutterView addGestureRecognizer:_discreteScrollingPanGestureRecognizer];
763 _continuousScrollingPanGestureRecognizer =
764 [[UIPanGestureRecognizer alloc] initWithTarget:self
765 action:@selector(continuousScrollEvent:)];
766 _continuousScrollingPanGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskContinuous;
767 _continuousScrollingPanGestureRecognizer.allowedTouchTypes = @[];
768 _continuousScrollingPanGestureRecognizer.delegate =
self;
769 [
self.flutterView addGestureRecognizer:_continuousScrollingPanGestureRecognizer];
770 _pinchGestureRecognizer =
771 [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEvent:)];
772 _pinchGestureRecognizer.allowedTouchTypes = @[];
773 _pinchGestureRecognizer.delegate =
self;
774 [
self.flutterView addGestureRecognizer:_pinchGestureRecognizer];
775 _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] init];
776 _rotationGestureRecognizer.allowedTouchTypes = @[];
777 _rotationGestureRecognizer.delegate =
self;
778 [
self.flutterView addGestureRecognizer:_rotationGestureRecognizer];
784- (void)addInternalPlugins {
789 [weakSelf.engine sendKeyEvent:event callback:callback userData:userData];
791 [
self.keyboardManager
795 [
self.keyboardManager addPrimaryResponder:responder];
798 [
self.keyboardManager addSecondaryResponder:textInputPlugin];
800 if (
self.engine.viewController ==
self) {
805- (void)removeInternalPlugins {
806 self.keyboardManager = nil;
809- (void)viewWillAppear:(
BOOL)animated {
811 if (
self.engine.viewController ==
self) {
813 [
self onUserSettingsChanged:nil];
817 if (_viewportMetrics.physical_width) {
818 [
self surfaceUpdated:YES];
820 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
821 [
self.engine.restorationPlugin markRestorationComplete];
824 [
super viewWillAppear:animated];
827- (void)viewDidAppear:(
BOOL)animated {
829 if (
self.engine.viewController ==
self) {
830 [
self onUserSettingsChanged:nil];
831 [
self onAccessibilityStatusChanged:nil];
833 if (
self.stateIsActive) {
834 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.resumed"];
837 [
super viewDidAppear:animated];
840- (void)viewWillDisappear:(
BOOL)animated {
842 if (
self.engine.viewController ==
self) {
843 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.inactive"];
845 [
super viewWillDisappear:animated];
848- (void)viewDidDisappear:(
BOOL)animated {
850 if (
self.engine.viewController ==
self) {
851 [
self.keyboardInsetManager hideKeyboardImmediately];
852 [
self surfaceUpdated:NO];
853 [
self.engine.lifecycleChannel sendMessage:@"AppLifecycleState.paused"];
854 [
self flushOngoingTouches];
855 [
self.engine notifyLowMemory];
858 [
super viewDidDisappear:animated];
861- (void)viewWillTransitionToSize:(CGSize)size
862 withTransitionCoordinator:(
id<UIViewControllerTransitionCoordinator>)coordinator {
863 [
super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
873 NSTimeInterval transitionDuration = coordinator.transitionDuration;
875 if (transitionDuration == 0) {
880 _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES;
881 dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
882 static_cast<int64_t
>(transitionDuration / 2.0 *
NSEC_PER_SEC)),
883 dispatch_get_main_queue(), ^{
891 strongSelf.shouldIgnoreViewportMetricsUpdatesDuringRotation = NO;
892 [strongSelf updateViewportMetricsIfNeeded];
896- (void)flushOngoingTouches {
897 if (
self.engine &&
self.ongoingTouches.count > 0) {
898 auto packet = std::make_unique<flutter::PointerDataPacket>(
self.ongoingTouches.count);
899 size_t pointer_index = 0;
902 for (NSNumber*
device in
self.ongoingTouches) {
919 packet->SetPointerData(pointer_index++, pointer_data);
922 [
self.ongoingTouches removeAllObjects];
923 [
self.engine dispatchPointerDataPacket:std::move(packet)];
927- (void)deregisterNotifications {
928 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerWillDealloc
931 [[NSNotificationCenter defaultCenter] removeObserver:self];
937 [
self removeInternalPlugins];
938 [
self deregisterNotifications];
940 [
self.keyboardInsetManager invalidate];
941 [
self invalidateTouchRateCorrectionVSyncClient];
945 _scrollView.delegate = nil;
946 _hoverGestureRecognizer.delegate = nil;
947 _discreteScrollingPanGestureRecognizer.delegate = nil;
948 _continuousScrollingPanGestureRecognizer.delegate = nil;
949 _pinchGestureRecognizer.delegate = nil;
950 _rotationGestureRecognizer.delegate = nil;
953#pragma mark - Application lifecycle notifications
955- (void)applicationBecameActive:(NSNotification*)notification {
957 [
self appOrSceneBecameActive];
960- (void)applicationWillResignActive:(NSNotification*)notification {
962 [
self appOrSceneWillResignActive];
965- (void)applicationWillTerminate:(NSNotification*)notification {
966 [
self appOrSceneWillTerminate];
969- (void)applicationDidEnterBackground:(NSNotification*)notification {
970 TRACE_EVENT0(
"flutter",
"applicationDidEnterBackground");
971 [
self appOrSceneDidEnterBackground];
974- (void)applicationWillEnterForeground:(NSNotification*)notification {
975 TRACE_EVENT0(
"flutter",
"applicationWillEnterForeground");
976 [
self appOrSceneWillEnterForeground];
979#pragma mark - Scene lifecycle notifications
981- (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
983 [
self appOrSceneBecameActive];
986- (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
988 [
self appOrSceneWillResignActive];
991- (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
992 [
self appOrSceneWillTerminate];
995- (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
997 [
self appOrSceneDidEnterBackground];
1000- (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0)) {
1002 [
self appOrSceneWillEnterForeground];
1005#pragma mark - Lifecycle shared
1007- (void)appOrSceneBecameActive {
1008 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = NO;
1009 if (_viewportMetrics.physical_width) {
1010 [
self surfaceUpdated:YES];
1012 [
self performSelector:@selector(goToApplicationLifecycle:)
1013 withObject:@"AppLifecycleState.resumed"
1017- (void)appOrSceneWillResignActive {
1018 [NSObject cancelPreviousPerformRequestsWithTarget:self
1019 selector:@selector(goToApplicationLifecycle:)
1020 object:@"AppLifecycleState.resumed"];
1021 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1024- (void)appOrSceneWillTerminate {
1025 [
self goToApplicationLifecycle:@"AppLifecycleState.detached"];
1026 [
self.engine destroyContext];
1029- (void)appOrSceneDidEnterBackground {
1030 self.keyboardInsetManager.isKeyboardInOrTransitioningFromBackground = YES;
1031 [
self surfaceUpdated:NO];
1032 [
self goToApplicationLifecycle:@"AppLifecycleState.paused"];
1035- (void)appOrSceneWillEnterForeground {
1036 [
self goToApplicationLifecycle:@"AppLifecycleState.inactive"];
1040- (void)goToApplicationLifecycle:(nonnull NSString*)state {
1043 if (
self.viewIfLoaded.window) {
1044 [
self.engine.lifecycleChannel sendMessage:state];
1048#pragma mark - Touch event handling
1052 case UITouchPhaseBegan:
1054 case UITouchPhaseMoved:
1055 case UITouchPhaseStationary:
1059 case UITouchPhaseEnded:
1061 case UITouchPhaseCancelled:
1065 FML_DLOG(INFO) <<
"Unhandled touch phase: " << phase;
1073 switch (touch.type) {
1074 case UITouchTypeDirect:
1075 case UITouchTypeIndirect:
1077 case UITouchTypeStylus:
1079 case UITouchTypeIndirectPointer:
1082 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1093- (void)dispatchTouches:(NSSet*)touches
1094 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1095 event:(UIEvent*)event {
1120 NSUInteger touches_to_remove_count = 0;
1121 for (UITouch* touch in touches) {
1122 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1123 touches_to_remove_count++;
1128 [
self triggerTouchRateCorrectionIfNeeded:touches];
1130 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1132 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1134 size_t pointer_index = 0;
1136 for (UITouch* touch in touches) {
1137 CGPoint windowCoordinates = [touch locationInView:self.view];
1140 pointer_data.
Clear();
1145 pointer_data.
change = overridden_change !=
nullptr
1146 ? *overridden_change
1147 : PointerDataChangeFromUITouchPhase(touch.phase);
1149 pointer_data.
kind = DeviceKindFromTouchType(touch);
1151 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1158 pointer_data.
physical_x = windowCoordinates.x * scale;
1159 pointer_data.
physical_y = windowCoordinates.y * scale;
1165 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1168 switch (pointer_data.
change) {
1170 [
self.ongoingTouches addObject:deviceKey];
1174 [
self.ongoingTouches removeObject:deviceKey];
1192 pointer_data.
pressure = touch.force;
1193 pointer_data.
pressure_max = touch.maximumPossibleForce;
1195 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1196 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1211 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1231 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1233 if (@available(iOS 13.4, *)) {
1234 if (event !=
nullptr) {
1235 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1238 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1244 packet->SetPointerData(pointer_index++, pointer_data);
1246 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1249 packet->SetPointerData(pointer_index++, remove_pointer_data);
1253 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1256- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1257 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1260- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1261 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1264- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1265 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1268- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1269 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1272- (void)forceTouchesCancelled:(NSSet*)touches {
1274 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1277- (
BOOL)platformViewShouldAcceptTouchAtTouchBeganLocation:(CGPoint)location {
1279 return [
self.engine platformViewShouldAcceptTouchAtTouchBeganLocation:point
1280 viewId:self.viewIdentifier];
1283#pragma mark - Touch events rate correction
1285- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1286 if (_touchRateCorrectionVSyncClient != nil) {
1291 const double epsilon = 0.1;
1292 if (displayRefreshRate < 60.0 + epsilon) {
1300 void (^
callback)(CFTimeInterval, CFTimeInterval) =
1301 ^(CFTimeInterval startTime, CFTimeInterval targetTime) {
1305 initWithTaskRunner:self.engine.platformTaskRunner
1306 isVariableRefreshRateEnabled:FlutterDisplayLinkManager.maxRefreshRateEnabledOnIPhone
1307 maxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate
1309 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1312- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1313 if (_touchRateCorrectionVSyncClient == nil) {
1321 BOOL isUserInteracting = NO;
1322 for (UITouch* touch in touches) {
1323 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1324 isUserInteracting = YES;
1329 if (isUserInteracting &&
self.engine.viewController ==
self) {
1330 [_touchRateCorrectionVSyncClient await];
1332 [_touchRateCorrectionVSyncClient pause];
1336- (void)invalidateTouchRateCorrectionVSyncClient {
1337 [_touchRateCorrectionVSyncClient invalidate];
1338 _touchRateCorrectionVSyncClient = nil;
1341#pragma mark - Handle view resizing
1343- (void)updateViewportMetricsIfNeeded {
1344 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1347 if (
self.engine.viewController ==
self) {
1348 [
self.engine updateViewportMetrics:_viewportMetrics];
1352- (void)viewDidLayoutSubviews {
1353 CGRect viewBounds =
self.view.bounds;
1354 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1357 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1361 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1362 _viewportMetrics.device_pixel_ratio = scale;
1363 [
self setViewportMetricsSize];
1364 [
self checkAndUpdateAutoResizeConstraints];
1365 [
self setViewportMetricsPaddings];
1366 [
self updateViewportMetricsIfNeeded];
1373 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1374 [
self surfaceUpdated:YES];
1375#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1376 NSTimeInterval timeout = 0.2;
1378 NSTimeInterval timeout = 0.1;
1381 waitForFirstFrameSync:timeout
1382 callback:^(BOOL didTimeout) {
1384 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1385 "This may happen in unoptimized builds. If this is"
1386 "a release build, you should load a less complex "
1387 "frame to avoid the timeout."];
1393- (
BOOL)isAutoResizable {
1394 return self.flutterView.autoResizable;
1397- (void)setAutoResizable:(
BOOL)value {
1399 self.flutterView.contentMode = UIViewContentModeCenter;
1402- (void)checkAndUpdateAutoResizeConstraints {
1403 if (!
self.isAutoResizable) {
1407 [
self updateAutoResizeConstraints];
1431- (void)updateAutoResizeConstraints {
1432 BOOL hasBeenAutoResized = NO;
1433 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1435 hasBeenAutoResized = YES;
1439 if (!hasBeenAutoResized) {
1440 self.sizeBeforeAutoResized =
self.view.frame.size;
1443 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1444 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1445 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1446 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1450 if (maxWidth == 0) {
1451 maxWidth = CGFLOAT_MAX;
1454 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1455 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1456 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1457 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1458 @"constraints (e.g., using SizedBox or Container)."];
1460 if (maxHeight == 0) {
1461 maxHeight = CGFLOAT_MAX;
1464 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1465 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1466 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1467 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1468 @"constraints (e.g., using SizedBox or Container)."];
1470 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1471 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1472 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1473 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1476- (void)viewSafeAreaInsetsDidChange {
1477 [
self setViewportMetricsPaddings];
1478 [
self updateViewportMetricsIfNeeded];
1479 [
super viewSafeAreaInsetsDidChange];
1483- (void)setViewportMetricsSize {
1484 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1489 CGFloat scale = screen.scale;
1490 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1491 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1493 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1494 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1495 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1496 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1502- (void)setViewportMetricsPaddings {
1503 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1508 CGFloat scale = screen.scale;
1509 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1510 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1511 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1512 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1515#pragma mark - Keyboard events
1517- (void)keyboardWillShowNotification:(NSNotification*)notification {
1522 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1525- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1530 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1533- (void)keyboardWillBeHidden:(NSNotification*)notification {
1537 [
self.keyboardInsetManager handleKeyboardNotification:notification];
1540- (void)handlePressEvent:(FlutterUIPressProxy*)press
1541 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1542 if (@available(iOS 13.4, *)) {
1547 [
self.keyboardManager handlePress:press nextAction:next];
1563- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1564 [
super pressesBegan:presses withEvent:event];
1567- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1568 [
super pressesChanged:presses withEvent:event];
1571- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1572 [
super pressesEnded:presses withEvent:event];
1575- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1576 [
super pressesCancelled:presses withEvent:event];
1584- (void)pressesBegan:(NSSet<UIPress*>*)presses
1585 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1586 if (@available(iOS 13.4, *)) {
1588 for (UIPress* press in presses) {
1589 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1591 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1595 [
super pressesBegan:presses withEvent:event];
1599- (void)pressesChanged:(NSSet<UIPress*>*)presses
1600 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1601 if (@available(iOS 13.4, *)) {
1603 for (UIPress* press in presses) {
1604 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1606 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1610 [
super pressesChanged:presses withEvent:event];
1614- (void)pressesEnded:(NSSet<UIPress*>*)presses
1615 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1616 if (@available(iOS 13.4, *)) {
1618 for (UIPress* press in presses) {
1619 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1621 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
1625 [
super pressesEnded:presses withEvent:event];
1629- (void)pressesCancelled:(NSSet<UIPress*>*)presses
1630 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1631 if (@available(iOS 13.4, *)) {
1633 for (UIPress* press in presses) {
1634 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1636 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
1640 [
super pressesCancelled:presses withEvent:event];
1644#pragma mark - Orientation updates
1646- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
1649 dispatch_async(dispatch_get_main_queue(), ^{
1650 NSDictionary* info = notification.userInfo;
1651 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
1652 if (update == nil) {
1655 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
1659- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
1660 API_AVAILABLE(ios(16.0)) {
1661 for (UIScene* windowScene in windowScenes) {
1662 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
1663 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
1664 initWithInterfaceOrientations:self.orientationPreferences];
1665 [(UIWindowScene*)windowScene
1666 requestGeometryUpdateWithPreferences:preference
1667 errorHandler:^(NSError* error) {
1668 os_log_error(OS_LOG_DEFAULT,
1669 "Failed to change device orientation: %@", error);
1671 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
1675- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
1676 if (new_preferences !=
self.orientationPreferences) {
1677 self.orientationPreferences = new_preferences;
1679 if (@available(iOS 16.0, *)) {
1681 NSSet<UIScene*>* scenes = [NSSet set];
1682 if (flutterApplication) {
1683 scenes = [flutterApplication.connectedScenes
1684 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
1685 id scene, NSDictionary* bindings) {
1686 return [scene isKindOfClass:[UIWindowScene class]];
1688 }
else if (
self.flutterWindowSceneIfViewLoaded) {
1689 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
1691 [
self requestGeometryUpdateForWindowScenes:scenes];
1693 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
1694 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
1698 @"Accessing the interface orientation when the window scene is unavailable."];
1701 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
1702 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
1703 [UIViewController attemptRotationToDeviceOrientation];
1705 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
1709 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
1710 forKey:@"orientation"];
1711 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
1712 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
1713 forKey:@"orientation"];
1714 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
1715 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
1716 forKey:@"orientation"];
1717 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
1718 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
1719 forKey:@"orientation"];
1726- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
1727 self.isHomeIndicatorHidden = YES;
1730- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
1731 self.isHomeIndicatorHidden = NO;
1734- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
1735 if (hideHomeIndicator != _isHomeIndicatorHidden) {
1736 _isHomeIndicatorHidden = hideHomeIndicator;
1737 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
1741- (
BOOL)prefersHomeIndicatorAutoHidden {
1742 return self.isHomeIndicatorHidden;
1745- (
BOOL)shouldAutorotate {
1749- (NSUInteger)supportedInterfaceOrientations {
1750 return self.orientationPreferences;
1753#pragma mark - Accessibility
1755- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
1760 int32_t flags = [
self.accessibilityFeatures flags];
1761#if TARGET_OS_SIMULATOR
1767 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
1768 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
1769 [
self.accessibilityFeatures isSpeakScreenEnabled];
1771 [
self.engine enableSemantics:enabled withFlags:flags];
1774- (
BOOL)accessibilityPerformEscape {
1776 if (navigationChannel) {
1783#pragma mark - Set user settings
1785- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1786 [
super traitCollectionDidChange:previousTraitCollection];
1787 [
self onUserSettingsChanged:nil];
1791 if (
self.isAutoResizable) {
1792 [
self.flutterView resetIntrinsicContentSize];
1796- (void)onUserSettingsChanged:(NSNotification*)notification {
1797 [
self.engine.settingsChannel sendMessage:@{
1798 @"textScaleFactor" : @(
self.textScaleFactor),
1800 @"platformBrightness" :
self.brightnessMode,
1801 @"platformContrast" : self.contrastMode,
1802 @"nativeSpellCheckServiceDefined" : @YES,
1803 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
1807- (CGFloat)textScaleFactor {
1809 if (flutterApplication == nil) {
1810 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
1814 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
1820 const CGFloat xs = 14;
1821 const CGFloat s = 15;
1822 const CGFloat m = 16;
1823 const CGFloat l = 17;
1824 const CGFloat xl = 19;
1825 const CGFloat xxl = 21;
1826 const CGFloat xxxl = 23;
1829 const CGFloat ax1 = 28;
1830 const CGFloat ax2 = 33;
1831 const CGFloat ax3 = 40;
1832 const CGFloat ax4 = 47;
1833 const CGFloat ax5 = 53;
1837 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
1839 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
1841 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
1843 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
1845 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
1847 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
1849 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
1851 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
1853 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
1855 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
1857 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
1859 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
1866- (
BOOL)supportsShowingSystemContextMenu {
1867 if (@available(iOS 16.0, *)) {
1877- (NSString*)brightnessMode {
1878 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
1880 if (style == UIUserInterfaceStyleDark) {
1890- (NSString*)contrastMode {
1891 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
1893 if (contrast == UIAccessibilityContrastHigh) {
1900#pragma mark - Status bar style
1902- (UIStatusBarStyle)preferredStatusBarStyle {
1903 return self.statusBarStyle;
1906- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
1909 dispatch_async(dispatch_get_main_queue(), ^{
1915 NSDictionary* info = notification.userInfo;
1916 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
1917 if (update == nil) {
1921 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
1922 if (style != strongSelf.statusBarStyle) {
1923 strongSelf.statusBarStyle = style;
1924 [strongSelf setNeedsStatusBarAppearanceUpdate];
1929- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
1930 if (hidden !=
self.flutterPrefersStatusBarHidden) {
1931 self.flutterPrefersStatusBarHidden = hidden;
1932 [
self setNeedsStatusBarAppearanceUpdate];
1936- (
BOOL)prefersStatusBarHidden {
1937 return self.flutterPrefersStatusBarHidden;
1940#pragma mark - Platform views
1943 return self.engine.platformViewsController;
1946- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
1947 return self.engine.binaryMessenger;
1950#pragma mark - FlutterBinaryMessenger
1952- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
1953 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
1956- (void)sendOnChannel:(NSString*)channel
1957 message:(NSData*)message
1959 NSAssert(
channel,
@"The channel must not be null");
1960 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
1963- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
1964 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
1968 binaryMessageHandler:
1970 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
1974 setMessageHandlerOnChannel:(NSString*)channel
1976 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
1977 NSAssert(
channel,
@"The channel must not be null");
1978 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
1979 binaryMessageHandler:handler
1980 taskQueue:taskQueue];
1984 [
self.engine.binaryMessenger cleanUpConnection:connection];
1987#pragma mark - FlutterTextureRegistry
1990 return [
self.engine.textureRegistry registerTexture:texture];
1993- (void)unregisterTexture:(int64_t)textureId {
1994 [
self.engine.textureRegistry unregisterTexture:textureId];
1997- (void)textureFrameAvailable:(int64_t)textureId {
1998 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2001- (NSString*)lookupKeyForAsset:(NSString*)asset {
2005- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2009- (
id<FlutterPluginRegistry>)pluginRegistry {
2013+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2014 return UIAccessibilityIsVoiceOverRunning();
2017#pragma mark - FlutterPluginRegistry
2019- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
2020 return [
self.engine registrarForPlugin:pluginKey];
2023- (
BOOL)hasPlugin:(NSString*)pluginKey {
2024 return [
self.engine hasPlugin:pluginKey];
2027- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
2028 return [
self.engine valuePublishedByPlugin:pluginKey];
2031- (void)presentViewController:(UIViewController*)viewControllerToPresent
2033 completion:(
void (^)(
void))completion {
2034 self.isPresentingViewControllerAnimating = YES;
2036 [
super presentViewController:viewControllerToPresent
2039 weakSelf.isPresentingViewControllerAnimating = NO;
2046- (
BOOL)isPresentingViewController {
2047 return self.presentedViewController != nil ||
self.isPresentingViewControllerAnimating;
2050- (
flutter::PointerData)updateMousePointerDataFrom:(UIGestureRecognizer*)gestureRecognizer
2051 API_AVAILABLE(ios(13.4)) {
2052 CGPoint
location = [gestureRecognizer locationInView:self.view];
2053 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2056 pointer_data.
Clear();
2060 return pointer_data;
2063- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2064 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
2065 API_AVAILABLE(ios(13.4)) {
2069- (
BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2070 shouldReceiveEvent:(UIEvent*)event API_AVAILABLE(ios(13.4)) {
2071 if (gestureRecognizer == _continuousScrollingPanGestureRecognizer &&
2072 event.type == UIEventTypeScroll) {
2075 pointer_data.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2080 if (event.timestamp <
self.scrollInertiaEventAppKitDeadline) {
2083 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2084 packet->SetPointerData(0, pointer_data);
2085 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2086 self.scrollInertiaEventAppKitDeadline = 0;
2093- (void)hoverEvent:(UIHoverGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2097 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2101 switch (_hoverGestureRecognizer.state) {
2102 case UIGestureRecognizerStateBegan:
2105 case UIGestureRecognizerStateChanged:
2108 case UIGestureRecognizerStateEnded:
2109 case UIGestureRecognizerStateCancelled:
2119 NSTimeInterval time = [NSProcessInfo processInfo].systemUptime;
2120 BOOL isRunningOnMac = NO;
2121 if (@available(iOS 14.0, *)) {
2125 isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
2128 time >
self.scrollInertiaEventStartline) {
2132 auto packet = std::make_unique<flutter::PointerDataPacket>(2);
2133 packet->SetPointerData(0, pointer_data);
2135 inertia_cancel.
device =
reinterpret_cast<int64_t
>(_continuousScrollingPanGestureRecognizer);
2139 packet->SetPointerData(1, inertia_cancel);
2140 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2141 self.scrollInertiaEventStartline = DBL_MAX;
2143 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2144 packet->SetPointerData(0, pointer_data);
2145 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2149- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2150 CGPoint translation = [recognizer translationInView:self.view];
2151 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2153 translation.x *= scale;
2154 translation.y *= scale;
2157 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2168 if (recognizer.state != UIGestureRecognizerStateEnded) {
2174 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2175 packet->SetPointerData(0, pointer_data);
2176 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2179- (void)continuousScrollEvent:(UIPanGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2180 CGPoint translation = [recognizer translationInView:self.view];
2181 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
2184 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2187 switch (recognizer.state) {
2188 case UIGestureRecognizerStateBegan:
2191 case UIGestureRecognizerStateChanged:
2193 pointer_data.
pan_x = translation.x * scale;
2194 pointer_data.
pan_y = translation.y * scale;
2197 pointer_data.
scale = 1;
2199 case UIGestureRecognizerStateEnded:
2200 case UIGestureRecognizerStateCancelled:
2201 self.scrollInertiaEventStartline =
2202 [[NSProcessInfo processInfo] systemUptime] +
2212 self.scrollInertiaEventAppKitDeadline =
2213 [[NSProcessInfo processInfo] systemUptime] +
2214 (0.1821 * log(fmax([recognizer velocityInView:self.view].x,
2215 [recognizer velocityInView:self.view].y))) -
2221 NSAssert(NO,
@"Trackpad pan event occured with unexpected phase 0x%lx",
2222 (
long)recognizer.state);
2226 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2227 packet->SetPointerData(0, pointer_data);
2228 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2231- (void)pinchEvent:(UIPinchGestureRecognizer*)recognizer API_AVAILABLE(ios(13.4)) {
2233 pointer_data.
device =
reinterpret_cast<int64_t
>(recognizer);
2236 switch (recognizer.state) {
2237 case UIGestureRecognizerStateBegan:
2240 case UIGestureRecognizerStateChanged:
2242 pointer_data.
scale = recognizer.scale;
2243 pointer_data.
rotation = _rotationGestureRecognizer.rotation;
2245 case UIGestureRecognizerStateEnded:
2246 case UIGestureRecognizerStateCancelled:
2251 NSAssert(NO,
@"Trackpad pinch event occured with unexpected phase 0x%lx",
2252 (
long)recognizer.state);
2256 auto packet = std::make_unique<flutter::PointerDataPacket>(1);
2257 packet->SetPointerData(0, pointer_data);
2258 [
self.engine dispatchPointerDataPacket:std::move(packet)];
2261#pragma mark - State Restoration
2263- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
2264 NSData* restorationData = [
self.engine.restorationPlugin restorationData];
2265 [coder encodeBytes:(const unsigned char*)restorationData.bytes
2266 length:restorationData.length
2267 forKey:kFlutterRestorationStateAppData];
2268 [
super encodeRestorableStateWithCoder:coder];
2271- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
2272 NSUInteger restorationDataLength;
2273 const unsigned char* restorationBytes = [coder decodeBytesForKey:kFlutterRestorationStateAppData
2274 returnedLength:&restorationDataLength];
2275 NSData* restorationData = [NSData dataWithBytes:restorationBytes length:restorationDataLength];
2276 [
self.engine.restorationPlugin setRestorationData:restorationData];
2280 return self.engine.restorationPlugin;
2284 return self.engine.textInputPlugin;
2287#pragma mark - FlutterKeyboardInsetManagerDelegate
2289- (void)updateViewportMetricsWithInset:(CGFloat)inset {
2290 _viewportMetrics.physical_view_inset_bottom = inset;
2291 [
self updateViewportMetricsIfNeeded];
2294- (CGFloat)physicalViewInsetBottom {
2295 return _viewportMetrics.physical_view_inset_bottom;
2298- (
BOOL)isPadInSlideOverOrStageManagerMode {
2299 if (
self.view.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
2300 self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
2301 self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
2307- (CGRect)convertViewRectToScreen:(CGRect)rect {
2308 return [
self.view convertRect:rect
2309 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)
A manager type that queries display characteristics, such as high refresh rate capabilities.
double displayRefreshRate
The maximum display refresh rate used for reporting purposes. This is intended to return either the h...
FlutterViewController * viewController
Coordinates the animation of the bottom viewport inset in response to system keyboard visibility chan...
UIApplication * application
void setUpIndirectScribbleInteraction:(id< FlutterViewResponder > viewResponder)
A client that wraps a CADisplayLink to deliver synchronized vsync signals.
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)