1183 switch (touch.type) {
1184 case UITouchTypeDirect:
1185 case UITouchTypeIndirect:
1187 case UITouchTypeStylus:
1189 case UITouchTypeIndirectPointer:
1192 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1203- (void)dispatchTouches:(NSSet*)touches
1204 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1205 event:(UIEvent*)event {
1230 NSUInteger touches_to_remove_count = 0;
1231 for (UITouch* touch in touches) {
1232 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1233 touches_to_remove_count++;
1238 [
self triggerTouchRateCorrectionIfNeeded:touches];
1240 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1242 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1244 size_t pointer_index = 0;
1246 for (UITouch* touch in touches) {
1247 CGPoint windowCoordinates = [touch locationInView:self.view];
1250 pointer_data.
Clear();
1255 pointer_data.
change = overridden_change !=
nullptr
1256 ? *overridden_change
1257 : PointerDataChangeFromUITouchPhase(touch.phase);
1259 pointer_data.
kind = DeviceKindFromTouchType(touch);
1261 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1268 pointer_data.
physical_x = windowCoordinates.x * scale;
1269 pointer_data.
physical_y = windowCoordinates.y * scale;
1275 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1278 switch (pointer_data.
change) {
1280 [
self.ongoingTouches addObject:deviceKey];
1284 [
self.ongoingTouches removeObject:deviceKey];
1302 pointer_data.
pressure = touch.force;
1303 pointer_data.
pressure_max = touch.maximumPossibleForce;
1305 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1306 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1321 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1341 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1343 if (@available(iOS 13.4, *)) {
1344 if (event !=
nullptr) {
1345 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1348 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1354 packet->SetPointerData(pointer_index++, pointer_data);
1356 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1359 packet->SetPointerData(pointer_index++, remove_pointer_data);
1363 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1366- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1367 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1370- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1371 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1374- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1375 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1378- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1379 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1382- (void)forceTouchesCancelled:(NSSet*)touches {
1384 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1387#pragma mark - Touch events rate correction
1389- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1390 if (_touchRateCorrectionVSyncClient != nil) {
1395 const double epsilon = 0.1;
1396 if (displayRefreshRate < 60.0 + epsilon) {
1404 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1407 _touchRateCorrectionVSyncClient =
1408 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1409 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1412- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1413 if (_touchRateCorrectionVSyncClient == nil) {
1421 BOOL isUserInteracting = NO;
1422 for (UITouch* touch in touches) {
1423 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1424 isUserInteracting = YES;
1429 if (isUserInteracting &&
self.engine.viewController ==
self) {
1430 [_touchRateCorrectionVSyncClient await];
1432 [_touchRateCorrectionVSyncClient pause];
1436- (void)invalidateTouchRateCorrectionVSyncClient {
1437 [_touchRateCorrectionVSyncClient invalidate];
1438 _touchRateCorrectionVSyncClient = nil;
1441#pragma mark - Handle view resizing
1443- (void)updateViewportMetricsIfNeeded {
1444 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1447 if (
self.engine.viewController ==
self) {
1448 [
self.engine updateViewportMetrics:_viewportMetrics];
1452- (void)viewDidLayoutSubviews {
1453 CGRect viewBounds =
self.view.bounds;
1454 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1457 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1461 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1462 _viewportMetrics.device_pixel_ratio = scale;
1463 [
self setViewportMetricsSize];
1464 [
self checkAndUpdateAutoResizeConstraints];
1465 [
self setViewportMetricsPaddings];
1466 [
self updateViewportMetricsIfNeeded];
1473 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1474 [
self surfaceUpdated:YES];
1475#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1476 NSTimeInterval timeout = 0.2;
1478 NSTimeInterval timeout = 0.1;
1481 waitForFirstFrameSync:timeout
1482 callback:^(BOOL didTimeout) {
1484 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1485 "This may happen in unoptimized builds. If this is"
1486 "a release build, you should load a less complex "
1487 "frame to avoid the timeout."];
1493- (
BOOL)isAutoResizable {
1494 return self.flutterView.autoResizable;
1497- (void)setAutoResizable:(
BOOL)value {
1499 self.flutterView.contentMode = UIViewContentModeCenter;
1502- (void)checkAndUpdateAutoResizeConstraints {
1503 if (!
self.isAutoResizable) {
1507 [
self updateAutoResizeConstraints];
1531- (void)updateAutoResizeConstraints {
1532 BOOL hasBeenAutoResized = NO;
1533 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1535 hasBeenAutoResized = YES;
1539 if (!hasBeenAutoResized) {
1540 self.sizeBeforeAutoResized =
self.view.frame.size;
1543 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1544 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1545 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1546 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1550 if (maxWidth == 0) {
1551 maxWidth = CGFLOAT_MAX;
1554 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1555 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1556 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1557 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1558 @"constraints (e.g., using SizedBox or Container)."];
1560 if (maxHeight == 0) {
1561 maxHeight = CGFLOAT_MAX;
1564 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1565 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1566 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1567 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1568 @"constraints (e.g., using SizedBox or Container)."];
1570 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1571 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1572 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1573 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1576- (void)viewSafeAreaInsetsDidChange {
1577 [
self setViewportMetricsPaddings];
1578 [
self updateViewportMetricsIfNeeded];
1579 [
super viewSafeAreaInsetsDidChange];
1583- (void)setViewportMetricsSize {
1584 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1589 CGFloat scale = screen.scale;
1590 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1591 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1593 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1594 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1595 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1596 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1602- (void)setViewportMetricsPaddings {
1603 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1608 CGFloat scale = screen.scale;
1609 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1610 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1611 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1612 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1615#pragma mark - Keyboard events
1617- (void)keyboardWillShowNotification:(NSNotification*)notification {
1622 [
self handleKeyboardNotification:notification];
1625- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1630 [
self handleKeyboardNotification:notification];
1633- (void)keyboardWillBeHidden:(NSNotification*)notification {
1637 [
self handleKeyboardNotification:notification];
1640- (void)handleKeyboardNotification:(NSNotification*)notification {
1643 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1647 NSDictionary* info = notification.userInfo;
1648 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1649 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1650 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1651 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1652 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1659 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1660 [
self hideKeyboardImmediately];
1665 if (
self.targetViewInsetBottom == calculatedInset) {
1669 self.targetViewInsetBottom = calculatedInset;
1676 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1677 BOOL keyboardAnimationIsCompounding =
1678 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1681 self.keyboardAnimationIsShowing = keyboardWillShow;
1683 if (!keyboardAnimationIsCompounding) {
1684 [
self startKeyBoardAnimation:duration];
1685 }
else if (
self.keyboardSpringAnimation) {
1686 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1690- (
BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1695 if (notification.name == UIKeyboardWillHideNotification) {
1704 NSDictionary* info = notification.userInfo;
1705 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1706 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1707 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1713 if (CGRectIsEmpty(keyboardFrame)) {
1718 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1724- (
BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1725 NSDictionary* info = notification.userInfo;
1729 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1730 if (isLocal && ![isLocal boolValue]) {
1733 return self.engine.viewController !=
self;
1736- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1744 NSDictionary* info = notification.userInfo;
1745 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1747 if (notification.name == UIKeyboardWillHideNotification) {
1748 return FlutterKeyboardModeHidden;
1753 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1754 return FlutterKeyboardModeFloating;
1757 if (CGRectIsEmpty(keyboardFrame)) {
1758 return FlutterKeyboardModeHidden;
1761 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1762 CGRect adjustedKeyboardFrame = keyboardFrame;
1763 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1764 keyboardFrame:keyboardFrame];
1769 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1770 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1771 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1772 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1774 CGFloat screenHeight = CGRectGetHeight(screenRect);
1775 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1776 if (round(adjustedKeyboardBottom) < screenHeight) {
1777 return FlutterKeyboardModeFloating;
1779 return FlutterKeyboardModeDocked;
1781 return FlutterKeyboardModeHidden;
1784- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1788 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1789 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1790 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1791 CGFloat screenHeight = CGRectGetHeight(screenRect);
1792 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1796 if (screenHeight == keyboardBottom) {
1799 CGRect viewRectRelativeToScreen =
1800 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1801 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1802 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1803 CGFloat offset = screenHeight - viewBottom;
1811- (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1813 if (keyboardMode == FlutterKeyboardModeDocked) {
1815 CGRect viewRectRelativeToScreen =
1816 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1817 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1818 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1819 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1824 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1825 return portionOfKeyboardInView * scale;
1830- (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1832 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1838 if (!
self.keyboardAnimationView) {
1839 UIView* keyboardAnimationView = [[UIView alloc] init];
1840 keyboardAnimationView.hidden = YES;
1841 self.keyboardAnimationView = keyboardAnimationView;
1844 if (!
self.keyboardAnimationView.superview) {
1845 [
self.view addSubview:self.keyboardAnimationView];
1849 [
self.keyboardAnimationView.layer removeAllAnimations];
1852 self.keyboardAnimationView.frame =
1853 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1855 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1858 [
self invalidateKeyboardAnimationVSyncClient];
1861 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1862 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1864 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1866 [UIView animateWithDuration:duration
1874 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1877 CAAnimation* keyboardAnimation =
1878 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1879 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1881 completion:^(BOOL finished) {
1882 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1891 [strongSelf invalidateKeyboardAnimationVSyncClient];
1892 [strongSelf removeKeyboardAnimationView];
1893 [strongSelf ensureViewportMetricsIsCorrect];
1898- (void)hideKeyboardImmediately {
1899 [
self invalidateKeyboardAnimationVSyncClient];
1900 if (
self.keyboardAnimationView) {
1901 [
self.keyboardAnimationView.layer removeAllAnimations];
1902 [
self removeKeyboardAnimationView];
1903 self.keyboardAnimationView = nil;
1905 if (
self.keyboardSpringAnimation) {
1906 self.keyboardSpringAnimation = nil;
1909 self.targetViewInsetBottom = 0.0;
1910 [
self ensureViewportMetricsIsCorrect];
1913- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1915 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) {
1916 _keyboardSpringAnimation = nil;
1921 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1922 _keyboardSpringAnimation =
1923 [[
SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1924 damping:keyboardCASpringAnimation.damping
1925 mass:keyboardCASpringAnimation.mass
1926 initialVelocity:keyboardCASpringAnimation.initialVelocity
1927 fromValue:self.originalViewInsetBottom
1928 toValue:self.targetViewInsetBottom];
1931- (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::
TimePoint)targetTime {
1933 if (!
self.isViewLoaded) {
1938 if (!
self.keyboardAnimationView) {
1943 if (!
self.keyboardAnimationVSyncClient) {
1947 if (!
self.keyboardAnimationView.superview) {
1949 [
self.view addSubview:self.keyboardAnimationView];
1952 if (!
self.keyboardSpringAnimation) {
1953 if (
self.keyboardAnimationView.layer.presentationLayer) {
1954 self->_viewportMetrics.physical_view_inset_bottom =
1955 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1956 [
self updateViewportMetricsIfNeeded];
1960 self->_viewportMetrics.physical_view_inset_bottom =
1961 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1962 [
self updateViewportMetricsIfNeeded];
1966- (void)setUpKeyboardAnimationVsyncClient:
1968 if (!keyboardAnimationCallback) {
1971 NSAssert(_keyboardAnimationVSyncClient == nil,
1972 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1976 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1977 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1978 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1979 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1980 animationCallback(targetTime);
1984 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1985 callback:uiCallback];
1986 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1987 [_keyboardAnimationVSyncClient await];
1990- (void)invalidateKeyboardAnimationVSyncClient {
1991 [_keyboardAnimationVSyncClient invalidate];
1992 _keyboardAnimationVSyncClient = nil;
1995- (void)removeKeyboardAnimationView {
1996 if (
self.keyboardAnimationView.superview != nil) {
1997 [
self.keyboardAnimationView removeFromSuperview];
2001- (void)ensureViewportMetricsIsCorrect {
2002 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
2004 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
2005 [
self updateViewportMetricsIfNeeded];
2009- (void)handlePressEvent:(FlutterUIPressProxy*)press
2010 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
2011 if (@available(iOS 13.4, *)) {
2016 [
self.keyboardManager handlePress:press nextAction:next];
2032- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2033 [
super pressesBegan:presses withEvent:event];
2036- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2037 [
super pressesChanged:presses withEvent:event];
2040- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2041 [
super pressesEnded:presses withEvent:event];
2044- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2045 [
super pressesCancelled:presses withEvent:event];
2053- (void)pressesBegan:(NSSet<UIPress*>*)presses
2054 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2055 if (@available(iOS 13.4, *)) {
2057 for (UIPress* press in presses) {
2058 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2060 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
2064 [
super pressesBegan:presses withEvent:event];
2068- (void)pressesChanged:(NSSet<UIPress*>*)presses
2069 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2070 if (@available(iOS 13.4, *)) {
2072 for (UIPress* press in presses) {
2073 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2075 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
2079 [
super pressesChanged:presses withEvent:event];
2083- (void)pressesEnded:(NSSet<UIPress*>*)presses
2084 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2085 if (@available(iOS 13.4, *)) {
2087 for (UIPress* press in presses) {
2088 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2090 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2094 [
super pressesEnded:presses withEvent:event];
2098- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2099 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2100 if (@available(iOS 13.4, *)) {
2102 for (UIPress* press in presses) {
2103 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2105 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2109 [
super pressesCancelled:presses withEvent:event];
2113#pragma mark - Orientation updates
2115- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2118 dispatch_async(dispatch_get_main_queue(), ^{
2119 NSDictionary* info = notification.userInfo;
2120 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2121 if (update == nil) {
2124 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2128- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2129 API_AVAILABLE(ios(16.0)) {
2130 for (UIScene* windowScene in windowScenes) {
2131 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
2132 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2133 initWithInterfaceOrientations:self.orientationPreferences];
2134 [(UIWindowScene*)windowScene
2135 requestGeometryUpdateWithPreferences:preference
2136 errorHandler:^(NSError* error) {
2137 os_log_error(OS_LOG_DEFAULT,
2138 "Failed to change device orientation: %@", error);
2140 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2144- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2145 if (new_preferences !=
self.orientationPreferences) {
2146 self.orientationPreferences = new_preferences;
2148 if (@available(iOS 16.0, *)) {
2150 NSSet<UIScene*>* scenes = [NSSet set];
2151 if (flutterApplication) {
2152 scenes = [flutterApplication.connectedScenes
2153 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2154 id scene, NSDictionary* bindings) {
2155 return [scene isKindOfClass:[UIWindowScene class]];
2157 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2158 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2160 [
self requestGeometryUpdateForWindowScenes:scenes];
2162 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2163 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2167 @"Accessing the interface orientation when the window scene is unavailable."];
2170 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2171 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2172 [UIViewController attemptRotationToDeviceOrientation];
2174 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2178 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2179 forKey:@"orientation"];
2180 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2181 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2182 forKey:@"orientation"];
2183 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2184 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2185 forKey:@"orientation"];
2186 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2187 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2188 forKey:@"orientation"];
2195- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2196 self.isHomeIndicatorHidden = YES;
2199- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2200 self.isHomeIndicatorHidden = NO;
2203- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
2204 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2205 _isHomeIndicatorHidden = hideHomeIndicator;
2206 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2210- (
BOOL)prefersHomeIndicatorAutoHidden {
2211 return self.isHomeIndicatorHidden;
2214- (
BOOL)shouldAutorotate {
2218- (NSUInteger)supportedInterfaceOrientations {
2219 return self.orientationPreferences;
2222#pragma mark - Accessibility
2224- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2229 int32_t flags =
self.accessibilityFlags;
2230#if TARGET_OS_SIMULATOR
2236 _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning();
2237 enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning();
2241 enabled |= UIAccessibilityIsSpeakScreenEnabled();
2243 [
self.engine enableSemantics:enabled withFlags:flags];
2246- (int32_t)accessibilityFlags {
2248 if (UIAccessibilityIsInvertColorsEnabled()) {
2251 if (UIAccessibilityIsReduceMotionEnabled()) {
2254 if (UIAccessibilityIsBoldTextEnabled()) {
2257 if (UIAccessibilityDarkerSystemColorsEnabled()) {
2267- (
BOOL)accessibilityPerformEscape {
2269 if (navigationChannel) {
2276+ (
BOOL)accessibilityIsOnOffSwitchLabelsEnabled {
2277 return UIAccessibilityIsOnOffSwitchLabelsEnabled();
2280#pragma mark - Set user settings
2282- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2283 [
super traitCollectionDidChange:previousTraitCollection];
2284 [
self onUserSettingsChanged:nil];
2288 if (
self.isAutoResizable) {
2289 [
self.flutterView resetIntrinsicContentSize];
2293- (void)onUserSettingsChanged:(NSNotification*)notification {
2294 [
self.engine.settingsChannel sendMessage:@{
2295 @"textScaleFactor" : @(
self.textScaleFactor),
2297 @"platformBrightness" :
self.brightnessMode,
2298 @"platformContrast" : self.contrastMode,
2299 @"nativeSpellCheckServiceDefined" : @YES,
2300 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2304- (CGFloat)textScaleFactor {
2306 if (flutterApplication == nil) {
2307 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2311 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2317 const CGFloat xs = 14;
2318 const CGFloat s = 15;
2319 const CGFloat m = 16;
2320 const CGFloat l = 17;
2321 const CGFloat xl = 19;
2322 const CGFloat xxl = 21;
2323 const CGFloat xxxl = 23;
2326 const CGFloat ax1 = 28;
2327 const CGFloat ax2 = 33;
2328 const CGFloat ax3 = 40;
2329 const CGFloat ax4 = 47;
2330 const CGFloat ax5 = 53;
2334 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2336 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2338 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2340 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2342 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2344 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2346 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2348 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2350 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2352 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2354 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2356 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2363- (
BOOL)supportsShowingSystemContextMenu {
2364 if (@available(iOS 16.0, *)) {
2374- (NSString*)brightnessMode {
2375 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2377 if (style == UIUserInterfaceStyleDark) {
2387- (NSString*)contrastMode {
2388 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2390 if (contrast == UIAccessibilityContrastHigh) {
2397#pragma mark - Status bar style
2399- (UIStatusBarStyle)preferredStatusBarStyle {
2400 return self.statusBarStyle;
2403- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2406 dispatch_async(dispatch_get_main_queue(), ^{
2412 NSDictionary* info = notification.userInfo;
2413 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2414 if (update == nil) {
2418 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2419 if (style != strongSelf.statusBarStyle) {
2420 strongSelf.statusBarStyle = style;
2421 [strongSelf setNeedsStatusBarAppearanceUpdate];
2426- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
2427 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2428 self.flutterPrefersStatusBarHidden = hidden;
2429 [
self setNeedsStatusBarAppearanceUpdate];
2433- (
BOOL)prefersStatusBarHidden {
2434 return self.flutterPrefersStatusBarHidden;
2437#pragma mark - Platform views
2440 return self.engine.platformViewsController;
2443- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
2444 return self.engine.binaryMessenger;
2447#pragma mark - FlutterBinaryMessenger
2449- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2450 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2453- (void)sendOnChannel:(NSString*)channel
2454 message:(NSData*)message
2456 NSAssert(
channel,
@"The channel must not be null");
2457 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2460- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
2461 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2465 binaryMessageHandler:
2467 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2471 setMessageHandlerOnChannel:(NSString*)channel
2473 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2474 NSAssert(
channel,
@"The channel must not be null");
2475 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2476 binaryMessageHandler:handler
2477 taskQueue:taskQueue];
2481 [
self.engine.binaryMessenger cleanUpConnection:connection];
2484#pragma mark - FlutterTextureRegistry
2487 return [
self.engine.textureRegistry registerTexture:texture];
2490- (void)unregisterTexture:(int64_t)textureId {
2491 [
self.engine.textureRegistry unregisterTexture:textureId];
2494- (void)textureFrameAvailable:(int64_t)textureId {
2495 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2498- (NSString*)lookupKeyForAsset:(NSString*)asset {
2502- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2506- (
id<FlutterPluginRegistry>)pluginRegistry {
2510+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2511 return UIAccessibilityIsVoiceOverRunning();