1180 switch (touch.type) {
1181 case UITouchTypeDirect:
1182 case UITouchTypeIndirect:
1184 case UITouchTypeStylus:
1186 case UITouchTypeIndirectPointer:
1189 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1200- (void)dispatchTouches:(NSSet*)touches
1201 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1202 event:(UIEvent*)event {
1227 NSUInteger touches_to_remove_count = 0;
1228 for (UITouch* touch in touches) {
1229 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1230 touches_to_remove_count++;
1235 [
self triggerTouchRateCorrectionIfNeeded:touches];
1237 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1239 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1241 size_t pointer_index = 0;
1243 for (UITouch* touch in touches) {
1244 CGPoint windowCoordinates = [touch locationInView:self.view];
1247 pointer_data.
Clear();
1252 pointer_data.
change = overridden_change !=
nullptr
1253 ? *overridden_change
1254 : PointerDataChangeFromUITouchPhase(touch.phase);
1256 pointer_data.
kind = DeviceKindFromTouchType(touch);
1258 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1265 pointer_data.
physical_x = windowCoordinates.x * scale;
1266 pointer_data.
physical_y = windowCoordinates.y * scale;
1272 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1275 switch (pointer_data.
change) {
1277 [
self.ongoingTouches addObject:deviceKey];
1281 [
self.ongoingTouches removeObject:deviceKey];
1299 pointer_data.
pressure = touch.force;
1300 pointer_data.
pressure_max = touch.maximumPossibleForce;
1302 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1303 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1318 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1338 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1340 if (@available(iOS 13.4, *)) {
1341 if (event !=
nullptr) {
1342 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1345 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1351 packet->SetPointerData(pointer_index++, pointer_data);
1353 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1356 packet->SetPointerData(pointer_index++, remove_pointer_data);
1360 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1363- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1364 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1367- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1368 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1371- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1372 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1375- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1376 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1379- (void)forceTouchesCancelled:(NSSet*)touches {
1381 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1384#pragma mark - Touch events rate correction
1386- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1387 if (_touchRateCorrectionVSyncClient != nil) {
1392 const double epsilon = 0.1;
1393 if (displayRefreshRate < 60.0 + epsilon) {
1401 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1404 _touchRateCorrectionVSyncClient =
1405 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1406 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1409- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1410 if (_touchRateCorrectionVSyncClient == nil) {
1418 BOOL isUserInteracting = NO;
1419 for (UITouch* touch in touches) {
1420 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1421 isUserInteracting = YES;
1426 if (isUserInteracting &&
self.engine.viewController ==
self) {
1427 [_touchRateCorrectionVSyncClient await];
1429 [_touchRateCorrectionVSyncClient pause];
1433- (void)invalidateTouchRateCorrectionVSyncClient {
1434 [_touchRateCorrectionVSyncClient invalidate];
1435 _touchRateCorrectionVSyncClient = nil;
1438#pragma mark - Handle view resizing
1440- (void)updateViewportMetricsIfNeeded {
1441 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1444 if (
self.engine.viewController ==
self) {
1445 [
self.engine updateViewportMetrics:_viewportMetrics];
1449- (void)viewDidLayoutSubviews {
1450 CGRect viewBounds =
self.view.bounds;
1451 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1454 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1458 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1459 _viewportMetrics.device_pixel_ratio = scale;
1460 [
self setViewportMetricsSize];
1461 [
self setViewportMetricsPaddings];
1462 [
self updateViewportMetricsIfNeeded];
1469 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1470 [
self surfaceUpdated:YES];
1471#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1472 NSTimeInterval timeout = 0.2;
1474 NSTimeInterval timeout = 0.1;
1477 waitForFirstFrameSync:timeout
1478 callback:^(BOOL didTimeout) {
1480 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1481 "This may happen in unoptimized builds. If this is"
1482 "a release build, you should load a less complex "
1483 "frame to avoid the timeout."];
1489- (void)viewSafeAreaInsetsDidChange {
1490 [
self setViewportMetricsPaddings];
1491 [
self updateViewportMetricsIfNeeded];
1492 [
super viewSafeAreaInsetsDidChange];
1496- (void)setViewportMetricsSize {
1497 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1502 CGFloat scale = screen.scale;
1503 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1504 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1506 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1507 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1508 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1509 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1515- (void)setViewportMetricsPaddings {
1516 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1521 CGFloat scale = screen.scale;
1522 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1523 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1524 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1525 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1528#pragma mark - Keyboard events
1530- (void)keyboardWillShowNotification:(NSNotification*)notification {
1535 [
self handleKeyboardNotification:notification];
1538- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1543 [
self handleKeyboardNotification:notification];
1546- (void)keyboardWillBeHidden:(NSNotification*)notification {
1550 [
self handleKeyboardNotification:notification];
1553- (void)handleKeyboardNotification:(NSNotification*)notification {
1556 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1560 NSDictionary* info = notification.userInfo;
1561 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1562 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1563 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1564 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1565 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1572 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1573 [
self hideKeyboardImmediately];
1578 if (
self.targetViewInsetBottom == calculatedInset) {
1582 self.targetViewInsetBottom = calculatedInset;
1589 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1590 BOOL keyboardAnimationIsCompounding =
1591 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1594 self.keyboardAnimationIsShowing = keyboardWillShow;
1596 if (!keyboardAnimationIsCompounding) {
1597 [
self startKeyBoardAnimation:duration];
1598 }
else if (
self.keyboardSpringAnimation) {
1599 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1603- (
BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1608 if (notification.name == UIKeyboardWillHideNotification) {
1617 NSDictionary* info = notification.userInfo;
1618 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1619 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1620 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1626 if (CGRectIsEmpty(keyboardFrame)) {
1631 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1637- (
BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1638 NSDictionary* info = notification.userInfo;
1642 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1643 if (isLocal && ![isLocal boolValue]) {
1646 return self.engine.viewController !=
self;
1649- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1657 NSDictionary* info = notification.userInfo;
1658 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1660 if (notification.name == UIKeyboardWillHideNotification) {
1661 return FlutterKeyboardModeHidden;
1666 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1667 return FlutterKeyboardModeFloating;
1670 if (CGRectIsEmpty(keyboardFrame)) {
1671 return FlutterKeyboardModeHidden;
1674 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1675 CGRect adjustedKeyboardFrame = keyboardFrame;
1676 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1677 keyboardFrame:keyboardFrame];
1682 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1683 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1684 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1685 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1687 CGFloat screenHeight = CGRectGetHeight(screenRect);
1688 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1689 if (round(adjustedKeyboardBottom) < screenHeight) {
1690 return FlutterKeyboardModeFloating;
1692 return FlutterKeyboardModeDocked;
1694 return FlutterKeyboardModeHidden;
1697- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1701 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1702 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1703 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1704 CGFloat screenHeight = CGRectGetHeight(screenRect);
1705 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1709 if (screenHeight == keyboardBottom) {
1712 CGRect viewRectRelativeToScreen =
1713 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1714 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1715 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1716 CGFloat offset = screenHeight - viewBottom;
1724- (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1726 if (keyboardMode == FlutterKeyboardModeDocked) {
1728 CGRect viewRectRelativeToScreen =
1729 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1730 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1731 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1732 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1737 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1738 return portionOfKeyboardInView * scale;
1743- (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1745 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1751 if (!
self.keyboardAnimationView) {
1752 UIView* keyboardAnimationView = [[UIView alloc] init];
1753 keyboardAnimationView.hidden = YES;
1754 self.keyboardAnimationView = keyboardAnimationView;
1757 if (!
self.keyboardAnimationView.superview) {
1758 [
self.view addSubview:self.keyboardAnimationView];
1762 [
self.keyboardAnimationView.layer removeAllAnimations];
1765 self.keyboardAnimationView.frame =
1766 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1768 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1771 [
self invalidateKeyboardAnimationVSyncClient];
1774 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1775 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1777 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1779 [UIView animateWithDuration:duration
1787 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1790 CAAnimation* keyboardAnimation =
1791 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1792 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1794 completion:^(BOOL finished) {
1795 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1804 [strongSelf invalidateKeyboardAnimationVSyncClient];
1805 [strongSelf removeKeyboardAnimationView];
1806 [strongSelf ensureViewportMetricsIsCorrect];
1811- (void)hideKeyboardImmediately {
1812 [
self invalidateKeyboardAnimationVSyncClient];
1813 if (
self.keyboardAnimationView) {
1814 [
self.keyboardAnimationView.layer removeAllAnimations];
1815 [
self removeKeyboardAnimationView];
1816 self.keyboardAnimationView = nil;
1818 if (
self.keyboardSpringAnimation) {
1819 self.keyboardSpringAnimation = nil;
1822 self.targetViewInsetBottom = 0.0;
1823 [
self ensureViewportMetricsIsCorrect];
1826- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1828 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) {
1829 _keyboardSpringAnimation = nil;
1834 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1835 _keyboardSpringAnimation =
1836 [[
SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1837 damping:keyboardCASpringAnimation.damping
1838 mass:keyboardCASpringAnimation.mass
1839 initialVelocity:keyboardCASpringAnimation.initialVelocity
1840 fromValue:self.originalViewInsetBottom
1841 toValue:self.targetViewInsetBottom];
1844- (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::
TimePoint)targetTime {
1846 if (!
self.isViewLoaded) {
1851 if (!
self.keyboardAnimationView) {
1856 if (!
self.keyboardAnimationVSyncClient) {
1860 if (!
self.keyboardAnimationView.superview) {
1862 [
self.view addSubview:self.keyboardAnimationView];
1865 if (!
self.keyboardSpringAnimation) {
1866 if (
self.keyboardAnimationView.layer.presentationLayer) {
1867 self->_viewportMetrics.physical_view_inset_bottom =
1868 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1869 [
self updateViewportMetricsIfNeeded];
1873 self->_viewportMetrics.physical_view_inset_bottom =
1874 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1875 [
self updateViewportMetricsIfNeeded];
1879- (void)setUpKeyboardAnimationVsyncClient:
1881 if (!keyboardAnimationCallback) {
1884 NSAssert(_keyboardAnimationVSyncClient == nil,
1885 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1889 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1890 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1891 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1892 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1893 animationCallback(targetTime);
1897 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1898 callback:uiCallback];
1899 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1900 [_keyboardAnimationVSyncClient await];
1903- (void)invalidateKeyboardAnimationVSyncClient {
1904 [_keyboardAnimationVSyncClient invalidate];
1905 _keyboardAnimationVSyncClient = nil;
1908- (void)removeKeyboardAnimationView {
1909 if (
self.keyboardAnimationView.superview != nil) {
1910 [
self.keyboardAnimationView removeFromSuperview];
1914- (void)ensureViewportMetricsIsCorrect {
1915 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1917 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1918 [
self updateViewportMetricsIfNeeded];
1922- (void)handlePressEvent:(FlutterUIPressProxy*)press
1923 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1924 if (@available(iOS 13.4, *)) {
1929 [
self.keyboardManager handlePress:press nextAction:next];
1945- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1946 [
super pressesBegan:presses withEvent:event];
1949- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1950 [
super pressesChanged:presses withEvent:event];
1953- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1954 [
super pressesEnded:presses withEvent:event];
1957- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1958 [
super pressesCancelled:presses withEvent:event];
1966- (void)pressesBegan:(NSSet<UIPress*>*)presses
1967 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1968 if (@available(iOS 13.4, *)) {
1970 for (UIPress* press in presses) {
1971 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1973 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
1977 [
super pressesBegan:presses withEvent:event];
1981- (void)pressesChanged:(NSSet<UIPress*>*)presses
1982 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1983 if (@available(iOS 13.4, *)) {
1985 for (UIPress* press in presses) {
1986 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
1988 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
1992 [
super pressesChanged:presses withEvent:event];
1996- (void)pressesEnded:(NSSet<UIPress*>*)presses
1997 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
1998 if (@available(iOS 13.4, *)) {
2000 for (UIPress* press in presses) {
2001 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2003 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2007 [
super pressesEnded:presses withEvent:event];
2011- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2012 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2013 if (@available(iOS 13.4, *)) {
2015 for (UIPress* press in presses) {
2016 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2018 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2022 [
super pressesCancelled:presses withEvent:event];
2026#pragma mark - Orientation updates
2028- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2031 dispatch_async(dispatch_get_main_queue(), ^{
2032 NSDictionary* info = notification.userInfo;
2033 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2034 if (update == nil) {
2037 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2041- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2042 API_AVAILABLE(ios(16.0)) {
2043 for (UIScene* windowScene in windowScenes) {
2044 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
2045 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2046 initWithInterfaceOrientations:self.orientationPreferences];
2047 [(UIWindowScene*)windowScene
2048 requestGeometryUpdateWithPreferences:preference
2049 errorHandler:^(NSError* error) {
2050 os_log_error(OS_LOG_DEFAULT,
2051 "Failed to change device orientation: %@", error);
2053 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2057- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2058 if (new_preferences !=
self.orientationPreferences) {
2059 self.orientationPreferences = new_preferences;
2061 if (@available(iOS 16.0, *)) {
2063 NSSet<UIScene*>* scenes = [NSSet set];
2064 if (flutterApplication) {
2065 scenes = [flutterApplication.connectedScenes
2066 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2067 id scene, NSDictionary* bindings) {
2068 return [scene isKindOfClass:[UIWindowScene class]];
2070 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2071 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2073 [
self requestGeometryUpdateForWindowScenes:scenes];
2075 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2076 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2080 @"Accessing the interface orientation when the window scene is unavailable."];
2083 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2084 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2085 [UIViewController attemptRotationToDeviceOrientation];
2087 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2091 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2092 forKey:@"orientation"];
2093 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2094 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2095 forKey:@"orientation"];
2096 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2097 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2098 forKey:@"orientation"];
2099 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2100 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2101 forKey:@"orientation"];
2108- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2109 self.isHomeIndicatorHidden = YES;
2112- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2113 self.isHomeIndicatorHidden = NO;
2116- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
2117 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2118 _isHomeIndicatorHidden = hideHomeIndicator;
2119 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2123- (
BOOL)prefersHomeIndicatorAutoHidden {
2124 return self.isHomeIndicatorHidden;
2127- (
BOOL)shouldAutorotate {
2131- (NSUInteger)supportedInterfaceOrientations {
2132 return self.orientationPreferences;
2135#pragma mark - Accessibility
2137- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2142 int32_t flags =
self.accessibilityFlags;
2143#if TARGET_OS_SIMULATOR
2149 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2150 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2154 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2156 [
self.engine enableSemantics:enabled withFlags:flags];
2159- (int32_t)accessibilityFlags {
2161 if (UIAccessibilityIsInvertColorsEnabled()) {
2164 if (UIAccessibilityIsReduceMotionEnabled()) {
2167 if (UIAccessibilityIsBoldTextEnabled()) {
2170 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2180- (
BOOL)accessibilityPerformEscape {
2182 if (navigationChannel) {
2189+ (
BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2190 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2193#pragma mark - Set user settings
2195- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2196 [
super traitCollectionDidChange:previousTraitCollection];
2197 [
self onUserSettingsChanged:nil];
2200- (void)onUserSettingsChanged:(NSNotification*)notification {
2201 [
self.engine.settingsChannel sendMessage:@{
2202 @"textScaleFactor" : @(
self.textScaleFactor),
2204 @"platformBrightness" :
self.brightnessMode,
2205 @"platformContrast" : self.contrastMode,
2206 @"nativeSpellCheckServiceDefined" : @YES,
2207 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2211- (CGFloat)textScaleFactor {
2213 if (flutterApplication == nil) {
2214 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2218 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2224 const CGFloat xs = 14;
2225 const CGFloat s = 15;
2226 const CGFloat m = 16;
2227 const CGFloat l = 17;
2228 const CGFloat xl = 19;
2229 const CGFloat xxl = 21;
2230 const CGFloat xxxl = 23;
2233 const CGFloat ax1 = 28;
2234 const CGFloat ax2 = 33;
2235 const CGFloat ax3 = 40;
2236 const CGFloat ax4 = 47;
2237 const CGFloat ax5 = 53;
2241 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2243 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2245 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2247 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2249 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2251 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2253 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2255 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2257 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2259 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2261 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2263 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2270- (
BOOL)supportsShowingSystemContextMenu {
2271 if (@available(iOS 16.0, *)) {
2281- (NSString*)brightnessMode {
2282 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2284 if (style == UIUserInterfaceStyleDark) {
2294- (NSString*)contrastMode {
2295 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2297 if (contrast == UIAccessibilityContrastHigh) {
2304#pragma mark - Status bar style
2306- (UIStatusBarStyle)preferredStatusBarStyle {
2307 return self.statusBarStyle;
2310- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2313 dispatch_async(dispatch_get_main_queue(), ^{
2319 NSDictionary* info = notification.userInfo;
2320 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2321 if (update == nil) {
2325 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2326 if (style != strongSelf.statusBarStyle) {
2327 strongSelf.statusBarStyle = style;
2328 [strongSelf setNeedsStatusBarAppearanceUpdate];
2333- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
2334 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2335 self.flutterPrefersStatusBarHidden = hidden;
2336 [
self setNeedsStatusBarAppearanceUpdate];
2340- (
BOOL)prefersStatusBarHidden {
2341 return self.flutterPrefersStatusBarHidden;
2344#pragma mark - Platform views
2347 return self.engine.platformViewsController;
2350- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
2351 return self.engine.binaryMessenger;
2354#pragma mark - FlutterBinaryMessenger
2356- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2357 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2360- (void)sendOnChannel:(NSString*)channel
2361 message:(NSData*)message
2363 NSAssert(
channel,
@"The channel must not be null");
2364 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2367- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
2368 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2372 binaryMessageHandler:
2374 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2378 setMessageHandlerOnChannel:(NSString*)channel
2380 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2381 NSAssert(
channel,
@"The channel must not be null");
2382 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2383 binaryMessageHandler:handler
2384 taskQueue:taskQueue];
2388 [
self.engine.binaryMessenger cleanUpConnection:connection];
2391#pragma mark - FlutterTextureRegistry
2394 return [
self.engine.textureRegistry registerTexture:texture];
2397- (void)unregisterTexture:(int64_t)textureId {
2398 [
self.engine.textureRegistry unregisterTexture:textureId];
2401- (void)textureFrameAvailable:(int64_t)textureId {
2402 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2405- (NSString*)lookupKeyForAsset:(NSString*)asset {
2409- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2413- (
id<FlutterPluginRegistry>)pluginRegistry {
2417+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2418 return UIAccessibilityIsVoiceOverRunning();