1136 switch (touch.type) {
1137 case UITouchTypeDirect:
1138 case UITouchTypeIndirect:
1140 case UITouchTypeStylus:
1142 case UITouchTypeIndirectPointer:
1145 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1156- (void)dispatchTouches:(NSSet*)touches
1157 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1158 event:(UIEvent*)event {
1183 NSUInteger touches_to_remove_count = 0;
1184 for (UITouch* touch in touches) {
1185 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1186 touches_to_remove_count++;
1191 [
self triggerTouchRateCorrectionIfNeeded:touches];
1193 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1195 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1197 size_t pointer_index = 0;
1199 for (UITouch* touch in touches) {
1200 CGPoint windowCoordinates = [touch locationInView:self.view];
1203 pointer_data.
Clear();
1208 pointer_data.
change = overridden_change !=
nullptr
1209 ? *overridden_change
1210 : PointerDataChangeFromUITouchPhase(touch.phase);
1212 pointer_data.
kind = DeviceKindFromTouchType(touch);
1214 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1221 pointer_data.
physical_x = windowCoordinates.x * scale;
1222 pointer_data.
physical_y = windowCoordinates.y * scale;
1228 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1231 switch (pointer_data.
change) {
1233 [
self.ongoingTouches addObject:deviceKey];
1237 [
self.ongoingTouches removeObject:deviceKey];
1255 pointer_data.
pressure = touch.force;
1256 pointer_data.
pressure_max = touch.maximumPossibleForce;
1258 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1259 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1274 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1294 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1296 if (@available(iOS 13.4, *)) {
1297 if (event !=
nullptr) {
1298 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1301 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1307 packet->SetPointerData(pointer_index++, pointer_data);
1309 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1312 packet->SetPointerData(pointer_index++, remove_pointer_data);
1316 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1319- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1320 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1323- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1324 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1327- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1328 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1331- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1332 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1335- (void)forceTouchesCancelled:(NSSet*)touches {
1337 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1340#pragma mark - Touch events rate correction
1342- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1343 if (_touchRateCorrectionVSyncClient != nil) {
1348 const double epsilon = 0.1;
1349 if (displayRefreshRate < 60.0 + epsilon) {
1357 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1360 _touchRateCorrectionVSyncClient =
1361 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1362 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1365- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1366 if (_touchRateCorrectionVSyncClient == nil) {
1374 BOOL isUserInteracting = NO;
1375 for (UITouch* touch in touches) {
1376 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1377 isUserInteracting = YES;
1382 if (isUserInteracting &&
self.engine.viewController ==
self) {
1383 [_touchRateCorrectionVSyncClient await];
1385 [_touchRateCorrectionVSyncClient pause];
1389- (void)invalidateTouchRateCorrectionVSyncClient {
1390 [_touchRateCorrectionVSyncClient invalidate];
1391 _touchRateCorrectionVSyncClient = nil;
1394#pragma mark - Handle view resizing
1396- (void)updateViewportMetricsIfNeeded {
1397 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1400 if (
self.engine.viewController ==
self) {
1401 [
self.engine updateViewportMetrics:_viewportMetrics];
1405- (void)viewDidLayoutSubviews {
1406 CGRect viewBounds =
self.view.bounds;
1407 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1410 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1414 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1415 _viewportMetrics.device_pixel_ratio = scale;
1416 [
self setViewportMetricsSize];
1417 [
self checkAndUpdateAutoResizeConstraints];
1418 [
self setViewportMetricsPaddings];
1419 [
self updateViewportMetricsIfNeeded];
1426 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1427 [
self surfaceUpdated:YES];
1428#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1429 NSTimeInterval timeout = 0.2;
1431 NSTimeInterval timeout = 0.1;
1434 waitForFirstFrameSync:timeout
1435 callback:^(BOOL didTimeout) {
1437 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1438 "This may happen in unoptimized builds. If this is"
1439 "a release build, you should load a less complex "
1440 "frame to avoid the timeout."];
1446- (
BOOL)isAutoResizable {
1447 return self.flutterView.autoResizable;
1450- (void)setAutoResizable:(
BOOL)value {
1452 self.flutterView.contentMode = UIViewContentModeCenter;
1455- (void)checkAndUpdateAutoResizeConstraints {
1456 if (!
self.isAutoResizable) {
1460 [
self updateAutoResizeConstraints];
1484- (void)updateAutoResizeConstraints {
1485 BOOL hasBeenAutoResized = NO;
1486 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1488 hasBeenAutoResized = YES;
1492 if (!hasBeenAutoResized) {
1493 self.sizeBeforeAutoResized =
self.view.frame.size;
1496 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1497 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1498 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1499 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1503 if (maxWidth == 0) {
1504 maxWidth = CGFLOAT_MAX;
1507 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1508 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1509 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1510 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1511 @"constraints (e.g., using SizedBox or Container)."];
1513 if (maxHeight == 0) {
1514 maxHeight = CGFLOAT_MAX;
1517 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1518 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1519 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1520 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1521 @"constraints (e.g., using SizedBox or Container)."];
1523 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1524 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1525 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1526 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1529- (void)viewSafeAreaInsetsDidChange {
1530 [
self setViewportMetricsPaddings];
1531 [
self updateViewportMetricsIfNeeded];
1532 [
super viewSafeAreaInsetsDidChange];
1536- (void)setViewportMetricsSize {
1537 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1542 CGFloat scale = screen.scale;
1543 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1544 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1546 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1547 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1548 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1549 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1555- (void)setViewportMetricsPaddings {
1556 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1561 CGFloat scale = screen.scale;
1562 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1563 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1564 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1565 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1568#pragma mark - Keyboard events
1570- (void)keyboardWillShowNotification:(NSNotification*)notification {
1575 [
self handleKeyboardNotification:notification];
1578- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1583 [
self handleKeyboardNotification:notification];
1586- (void)keyboardWillBeHidden:(NSNotification*)notification {
1590 [
self handleKeyboardNotification:notification];
1593- (void)handleKeyboardNotification:(NSNotification*)notification {
1596 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1600 NSDictionary* info = notification.userInfo;
1601 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1602 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1603 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1604 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1605 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1612 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1613 [
self hideKeyboardImmediately];
1618 if (
self.targetViewInsetBottom == calculatedInset) {
1622 self.targetViewInsetBottom = calculatedInset;
1629 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1630 BOOL keyboardAnimationIsCompounding =
1631 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1634 self.keyboardAnimationIsShowing = keyboardWillShow;
1636 if (!keyboardAnimationIsCompounding) {
1637 [
self startKeyBoardAnimation:duration];
1638 }
else if (
self.keyboardSpringAnimation) {
1639 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1643- (
BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1648 if (notification.name == UIKeyboardWillHideNotification) {
1657 NSDictionary* info = notification.userInfo;
1658 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1659 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1660 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1666 if (CGRectIsEmpty(keyboardFrame)) {
1671 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1677- (
BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1678 NSDictionary* info = notification.userInfo;
1682 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1683 if (isLocal && ![isLocal boolValue]) {
1686 return self.engine.viewController !=
self;
1689- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1697 NSDictionary* info = notification.userInfo;
1698 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1700 if (notification.name == UIKeyboardWillHideNotification) {
1701 return FlutterKeyboardModeHidden;
1706 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1707 return FlutterKeyboardModeFloating;
1710 if (CGRectIsEmpty(keyboardFrame)) {
1711 return FlutterKeyboardModeHidden;
1714 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1715 CGRect adjustedKeyboardFrame = keyboardFrame;
1716 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1717 keyboardFrame:keyboardFrame];
1722 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1723 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1724 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1725 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1727 CGFloat screenHeight = CGRectGetHeight(screenRect);
1728 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1729 if (round(adjustedKeyboardBottom) < screenHeight) {
1730 return FlutterKeyboardModeFloating;
1732 return FlutterKeyboardModeDocked;
1734 return FlutterKeyboardModeHidden;
1737- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1741 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1742 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1743 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1744 CGFloat screenHeight = CGRectGetHeight(screenRect);
1745 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1749 if (screenHeight == keyboardBottom) {
1752 CGRect viewRectRelativeToScreen =
1753 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1754 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1755 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1756 CGFloat offset = screenHeight - viewBottom;
1764- (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1766 if (keyboardMode == FlutterKeyboardModeDocked) {
1768 CGRect viewRectRelativeToScreen =
1769 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1770 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1771 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1772 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1777 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1778 return portionOfKeyboardInView * scale;
1783- (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1785 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1791 if (!
self.keyboardAnimationView) {
1792 UIView* keyboardAnimationView = [[UIView alloc] init];
1793 keyboardAnimationView.hidden = YES;
1794 self.keyboardAnimationView = keyboardAnimationView;
1797 if (!
self.keyboardAnimationView.superview) {
1798 [
self.view addSubview:self.keyboardAnimationView];
1802 [
self.keyboardAnimationView.layer removeAllAnimations];
1805 self.keyboardAnimationView.frame =
1806 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1808 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1811 [
self invalidateKeyboardAnimationVSyncClient];
1814 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1815 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1817 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1819 [UIView animateWithDuration:duration
1827 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1830 CAAnimation* keyboardAnimation =
1831 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1832 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1834 completion:^(BOOL finished) {
1835 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1844 [strongSelf invalidateKeyboardAnimationVSyncClient];
1845 [strongSelf removeKeyboardAnimationView];
1846 [strongSelf ensureViewportMetricsIsCorrect];
1851- (void)hideKeyboardImmediately {
1852 [
self invalidateKeyboardAnimationVSyncClient];
1853 if (
self.keyboardAnimationView) {
1854 [
self.keyboardAnimationView.layer removeAllAnimations];
1855 [
self removeKeyboardAnimationView];
1856 self.keyboardAnimationView = nil;
1858 if (
self.keyboardSpringAnimation) {
1859 self.keyboardSpringAnimation = nil;
1862 self.targetViewInsetBottom = 0.0;
1863 [
self ensureViewportMetricsIsCorrect];
1866- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1868 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) {
1869 _keyboardSpringAnimation = nil;
1874 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1875 _keyboardSpringAnimation =
1876 [[
SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1877 damping:keyboardCASpringAnimation.damping
1878 mass:keyboardCASpringAnimation.mass
1879 initialVelocity:keyboardCASpringAnimation.initialVelocity
1880 fromValue:self.originalViewInsetBottom
1881 toValue:self.targetViewInsetBottom];
1884- (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::
TimePoint)targetTime {
1886 if (!
self.isViewLoaded) {
1891 if (!
self.keyboardAnimationView) {
1896 if (!
self.keyboardAnimationVSyncClient) {
1900 if (!
self.keyboardAnimationView.superview) {
1902 [
self.view addSubview:self.keyboardAnimationView];
1905 if (!
self.keyboardSpringAnimation) {
1906 if (
self.keyboardAnimationView.layer.presentationLayer) {
1907 self->_viewportMetrics.physical_view_inset_bottom =
1908 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1909 [
self updateViewportMetricsIfNeeded];
1913 self->_viewportMetrics.physical_view_inset_bottom =
1914 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1915 [
self updateViewportMetricsIfNeeded];
1919- (void)setUpKeyboardAnimationVsyncClient:
1921 if (!keyboardAnimationCallback) {
1924 NSAssert(_keyboardAnimationVSyncClient == nil,
1925 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1929 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1930 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1931 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1932 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1933 animationCallback(targetTime);
1937 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1938 callback:uiCallback];
1939 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1940 [_keyboardAnimationVSyncClient await];
1943- (void)invalidateKeyboardAnimationVSyncClient {
1944 [_keyboardAnimationVSyncClient invalidate];
1945 _keyboardAnimationVSyncClient = nil;
1948- (void)removeKeyboardAnimationView {
1949 if (
self.keyboardAnimationView.superview != nil) {
1950 [
self.keyboardAnimationView removeFromSuperview];
1954- (void)ensureViewportMetricsIsCorrect {
1955 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1957 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1958 [
self updateViewportMetricsIfNeeded];
1962- (void)handlePressEvent:(FlutterUIPressProxy*)press
1963 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1964 if (@available(iOS 13.4, *)) {
1969 [
self.keyboardManager handlePress:press nextAction:next];
1985- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1986 [
super pressesBegan:presses withEvent:event];
1989- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1990 [
super pressesChanged:presses withEvent:event];
1993- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1994 [
super pressesEnded:presses withEvent:event];
1997- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
1998 [
super pressesCancelled:presses withEvent:event];
2006- (void)pressesBegan:(NSSet<UIPress*>*)presses
2007 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2008 if (@available(iOS 13.4, *)) {
2010 for (UIPress* press in presses) {
2011 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2013 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
2017 [
super pressesBegan:presses withEvent:event];
2021- (void)pressesChanged:(NSSet<UIPress*>*)presses
2022 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2023 if (@available(iOS 13.4, *)) {
2025 for (UIPress* press in presses) {
2026 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2028 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
2032 [
super pressesChanged:presses withEvent:event];
2036- (void)pressesEnded:(NSSet<UIPress*>*)presses
2037 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2038 if (@available(iOS 13.4, *)) {
2040 for (UIPress* press in presses) {
2041 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2043 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2047 [
super pressesEnded:presses withEvent:event];
2051- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2052 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2053 if (@available(iOS 13.4, *)) {
2055 for (UIPress* press in presses) {
2056 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2058 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2062 [
super pressesCancelled:presses withEvent:event];
2066#pragma mark - Orientation updates
2068- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2071 dispatch_async(dispatch_get_main_queue(), ^{
2072 NSDictionary* info = notification.userInfo;
2073 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2074 if (update == nil) {
2077 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2081- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2082 API_AVAILABLE(ios(16.0)) {
2083 for (UIScene* windowScene in windowScenes) {
2084 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
2085 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2086 initWithInterfaceOrientations:self.orientationPreferences];
2087 [(UIWindowScene*)windowScene
2088 requestGeometryUpdateWithPreferences:preference
2089 errorHandler:^(NSError* error) {
2090 os_log_error(OS_LOG_DEFAULT,
2091 "Failed to change device orientation: %@", error);
2093 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2097- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2098 if (new_preferences !=
self.orientationPreferences) {
2099 self.orientationPreferences = new_preferences;
2101 if (@available(iOS 16.0, *)) {
2103 NSSet<UIScene*>* scenes = [NSSet set];
2104 if (flutterApplication) {
2105 scenes = [flutterApplication.connectedScenes
2106 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2107 id scene, NSDictionary* bindings) {
2108 return [scene isKindOfClass:[UIWindowScene class]];
2110 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2111 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2113 [
self requestGeometryUpdateForWindowScenes:scenes];
2115 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2116 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2120 @"Accessing the interface orientation when the window scene is unavailable."];
2123 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2124 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2125 [UIViewController attemptRotationToDeviceOrientation];
2127 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2131 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2132 forKey:@"orientation"];
2133 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2134 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2135 forKey:@"orientation"];
2136 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2137 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2138 forKey:@"orientation"];
2139 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2140 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2141 forKey:@"orientation"];
2148- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2149 self.isHomeIndicatorHidden = YES;
2152- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2153 self.isHomeIndicatorHidden = NO;
2156- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
2157 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2158 _isHomeIndicatorHidden = hideHomeIndicator;
2159 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2163- (
BOOL)prefersHomeIndicatorAutoHidden {
2164 return self.isHomeIndicatorHidden;
2167- (
BOOL)shouldAutorotate {
2171- (NSUInteger)supportedInterfaceOrientations {
2172 return self.orientationPreferences;
2175#pragma mark - Accessibility
2177- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2182 int32_t flags = [
self.accessibilityFeatures flags];
2183#if TARGET_OS_SIMULATOR
2189 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
2190 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
2191 [
self.accessibilityFeatures isSpeakScreenEnabled];
2193 [
self.engine enableSemantics:enabled withFlags:flags];
2196- (
BOOL)accessibilityPerformEscape {
2198 if (navigationChannel) {
2205#pragma mark - Set user settings
2207- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2208 [
super traitCollectionDidChange:previousTraitCollection];
2209 [
self onUserSettingsChanged:nil];
2213 if (
self.isAutoResizable) {
2214 [
self.flutterView resetIntrinsicContentSize];
2218- (void)onUserSettingsChanged:(NSNotification*)notification {
2219 [
self.engine.settingsChannel sendMessage:@{
2220 @"textScaleFactor" : @(
self.textScaleFactor),
2222 @"platformBrightness" :
self.brightnessMode,
2223 @"platformContrast" : self.contrastMode,
2224 @"nativeSpellCheckServiceDefined" : @YES,
2225 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2229- (CGFloat)textScaleFactor {
2231 if (flutterApplication == nil) {
2232 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2236 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2242 const CGFloat xs = 14;
2243 const CGFloat s = 15;
2244 const CGFloat m = 16;
2245 const CGFloat l = 17;
2246 const CGFloat xl = 19;
2247 const CGFloat xxl = 21;
2248 const CGFloat xxxl = 23;
2251 const CGFloat ax1 = 28;
2252 const CGFloat ax2 = 33;
2253 const CGFloat ax3 = 40;
2254 const CGFloat ax4 = 47;
2255 const CGFloat ax5 = 53;
2259 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2261 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2263 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2265 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2267 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2269 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2271 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2273 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2275 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2277 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2279 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2281 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2288- (
BOOL)supportsShowingSystemContextMenu {
2289 if (@available(iOS 16.0, *)) {
2299- (NSString*)brightnessMode {
2300 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2302 if (style == UIUserInterfaceStyleDark) {
2312- (NSString*)contrastMode {
2313 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2315 if (contrast == UIAccessibilityContrastHigh) {
2322#pragma mark - Status bar style
2324- (UIStatusBarStyle)preferredStatusBarStyle {
2325 return self.statusBarStyle;
2328- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2331 dispatch_async(dispatch_get_main_queue(), ^{
2337 NSDictionary* info = notification.userInfo;
2338 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2339 if (update == nil) {
2343 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2344 if (style != strongSelf.statusBarStyle) {
2345 strongSelf.statusBarStyle = style;
2346 [strongSelf setNeedsStatusBarAppearanceUpdate];
2351- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
2352 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2353 self.flutterPrefersStatusBarHidden = hidden;
2354 [
self setNeedsStatusBarAppearanceUpdate];
2358- (
BOOL)prefersStatusBarHidden {
2359 return self.flutterPrefersStatusBarHidden;
2362#pragma mark - Platform views
2365 return self.engine.platformViewsController;
2368- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
2369 return self.engine.binaryMessenger;
2372#pragma mark - FlutterBinaryMessenger
2374- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2375 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2378- (void)sendOnChannel:(NSString*)channel
2379 message:(NSData*)message
2381 NSAssert(
channel,
@"The channel must not be null");
2382 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2385- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
2386 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2390 binaryMessageHandler:
2392 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2396 setMessageHandlerOnChannel:(NSString*)channel
2398 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2399 NSAssert(
channel,
@"The channel must not be null");
2400 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2401 binaryMessageHandler:handler
2402 taskQueue:taskQueue];
2406 [
self.engine.binaryMessenger cleanUpConnection:connection];
2409#pragma mark - FlutterTextureRegistry
2412 return [
self.engine.textureRegistry registerTexture:texture];
2415- (void)unregisterTexture:(int64_t)textureId {
2416 [
self.engine.textureRegistry unregisterTexture:textureId];
2419- (void)textureFrameAvailable:(int64_t)textureId {
2420 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2423- (NSString*)lookupKeyForAsset:(NSString*)asset {
2427- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2431- (
id<FlutterPluginRegistry>)pluginRegistry {
2435+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2436 return UIAccessibilityIsVoiceOverRunning();