1152 switch (touch.type) {
1153 case UITouchTypeDirect:
1154 case UITouchTypeIndirect:
1156 case UITouchTypeStylus:
1158 case UITouchTypeIndirectPointer:
1161 FML_DLOG(INFO) <<
"Unhandled touch type: " << touch.type;
1172- (void)dispatchTouches:(NSSet*)touches
1173 pointerDataChangeOverride:(
flutter::PointerData::Change*)overridden_change
1174 event:(UIEvent*)event {
1199 NSUInteger touches_to_remove_count = 0;
1200 for (UITouch* touch in touches) {
1201 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1202 touches_to_remove_count++;
1207 [
self triggerTouchRateCorrectionIfNeeded:touches];
1209 const CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1211 std::make_unique<flutter::PointerDataPacket>(touches.count + touches_to_remove_count);
1213 size_t pointer_index = 0;
1215 for (UITouch* touch in touches) {
1216 CGPoint windowCoordinates = [touch locationInView:self.view];
1219 pointer_data.
Clear();
1224 pointer_data.
change = overridden_change !=
nullptr
1225 ? *overridden_change
1226 : PointerDataChangeFromUITouchPhase(touch.phase);
1228 pointer_data.
kind = DeviceKindFromTouchType(touch);
1230 pointer_data.
device =
reinterpret_cast<int64_t
>(touch);
1237 pointer_data.
physical_x = windowCoordinates.x * scale;
1238 pointer_data.
physical_y = windowCoordinates.y * scale;
1244 NSNumber* deviceKey = [NSNumber numberWithLongLong:pointer_data.device];
1247 switch (pointer_data.
change) {
1249 [
self.ongoingTouches addObject:deviceKey];
1253 [
self.ongoingTouches removeObject:deviceKey];
1271 pointer_data.
pressure = touch.force;
1272 pointer_data.
pressure_max = touch.maximumPossibleForce;
1274 pointer_data.
radius_min = touch.majorRadius - touch.majorRadiusTolerance;
1275 pointer_data.
radius_max = touch.majorRadius + touch.majorRadiusTolerance;
1290 pointer_data.
tilt = M_PI_2 - touch.altitudeAngle;
1310 pointer_data.
orientation = [touch azimuthAngleInView:nil] - M_PI_2;
1312 if (@available(iOS 13.4, *)) {
1313 if (event !=
nullptr) {
1314 pointer_data.
buttons = (((
event.buttonMask & UIEventButtonMaskPrimary) > 0)
1317 (((event.buttonMask & UIEventButtonMaskSecondary) > 0)
1323 packet->SetPointerData(pointer_index++, pointer_data);
1325 if (touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled) {
1328 packet->SetPointerData(pointer_index++, remove_pointer_data);
1332 [
self.engine dispatchPointerDataPacket:std::move(packet)];
1335- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
1336 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1339- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
1340 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1343- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
1344 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1347- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
1348 [
self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
1351- (void)forceTouchesCancelled:(NSSet*)touches {
1353 [
self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
1356#pragma mark - Touch events rate correction
1358- (void)createTouchRateCorrectionVSyncClientIfNeeded {
1359 if (_touchRateCorrectionVSyncClient != nil) {
1364 const double epsilon = 0.1;
1365 if (displayRefreshRate < 60.0 + epsilon) {
1373 auto callback = [](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1376 _touchRateCorrectionVSyncClient =
1377 [[
VSyncClient alloc] initWithTaskRunner:self.engine.platformTaskRunner callback:callback];
1378 _touchRateCorrectionVSyncClient.allowPauseAfterVsync = NO;
1381- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches {
1382 if (_touchRateCorrectionVSyncClient == nil) {
1390 BOOL isUserInteracting = NO;
1391 for (UITouch* touch in touches) {
1392 if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
1393 isUserInteracting = YES;
1398 if (isUserInteracting &&
self.engine.viewController ==
self) {
1399 [_touchRateCorrectionVSyncClient await];
1401 [_touchRateCorrectionVSyncClient pause];
1405- (void)invalidateTouchRateCorrectionVSyncClient {
1406 [_touchRateCorrectionVSyncClient invalidate];
1407 _touchRateCorrectionVSyncClient = nil;
1410#pragma mark - Handle view resizing
1412- (void)updateViewportMetricsIfNeeded {
1413 if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1416 if (
self.engine.viewController ==
self) {
1417 [
self.engine updateViewportMetrics:_viewportMetrics];
1421- (void)viewDidLayoutSubviews {
1422 CGRect viewBounds =
self.view.bounds;
1423 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1426 self.scrollView.frame = CGRectMake(0.0, 0.0, viewBounds.size.width, 0.0);
1430 bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
1431 _viewportMetrics.device_pixel_ratio = scale;
1432 [
self setViewportMetricsSize];
1433 [
self checkAndUpdateAutoResizeConstraints];
1434 [
self setViewportMetricsPaddings];
1435 [
self updateViewportMetricsIfNeeded];
1442 if (firstViewBoundsUpdate &&
self.stateIsActive &&
self.engine) {
1443 [
self surfaceUpdated:YES];
1444#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
1445 NSTimeInterval timeout = 0.2;
1447 NSTimeInterval timeout = 0.1;
1450 waitForFirstFrameSync:timeout
1451 callback:^(BOOL didTimeout) {
1453 [FlutterLogger logInfo:@"Timeout waiting for the first frame to render. "
1454 "This may happen in unoptimized builds. If this is"
1455 "a release build, you should load a less complex "
1456 "frame to avoid the timeout."];
1462- (
BOOL)isAutoResizable {
1463 return self.flutterView.autoResizable;
1466- (void)setAutoResizable:(
BOOL)value {
1468 self.flutterView.contentMode = UIViewContentModeCenter;
1471- (void)checkAndUpdateAutoResizeConstraints {
1472 if (!
self.isAutoResizable) {
1476 [
self updateAutoResizeConstraints];
1500- (void)updateAutoResizeConstraints {
1501 BOOL hasBeenAutoResized = NO;
1502 for (NSLayoutConstraint* constraint in
self.view.constraints) {
1504 hasBeenAutoResized = YES;
1508 if (!hasBeenAutoResized) {
1509 self.sizeBeforeAutoResized =
self.view.frame.size;
1512 CGFloat maxWidth =
self.sizeBeforeAutoResized.width;
1513 CGFloat maxHeight =
self.sizeBeforeAutoResized.height;
1514 CGFloat minWidth =
self.sizeBeforeAutoResized.width;
1515 CGFloat minHeight =
self.sizeBeforeAutoResized.height;
1519 if (maxWidth == 0) {
1520 maxWidth = CGFLOAT_MAX;
1523 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1524 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1525 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1526 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1527 @"constraints (e.g., using SizedBox or Container)."];
1529 if (maxHeight == 0) {
1530 maxHeight = CGFLOAT_MAX;
1533 @"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
1534 @"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
1535 @"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
1536 @"rendering failure. Please ensure your top-level Flutter widget has explicit "
1537 @"constraints (e.g., using SizedBox or Container)."];
1539 _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio;
1540 _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio;
1541 _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio;
1542 _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio;
1545- (void)viewSafeAreaInsetsDidChange {
1546 [
self setViewportMetricsPaddings];
1547 [
self updateViewportMetricsIfNeeded];
1548 [
super viewSafeAreaInsetsDidChange];
1552- (void)setViewportMetricsSize {
1553 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1558 CGFloat scale = screen.scale;
1559 _viewportMetrics.physical_width =
self.view.bounds.size.width * scale;
1560 _viewportMetrics.physical_height =
self.view.bounds.size.height * scale;
1562 _viewportMetrics.physical_min_width_constraint = _viewportMetrics.physical_width;
1563 _viewportMetrics.physical_max_width_constraint = _viewportMetrics.physical_width;
1564 _viewportMetrics.physical_min_height_constraint = _viewportMetrics.physical_height;
1565 _viewportMetrics.physical_max_height_constraint = _viewportMetrics.physical_height;
1571- (void)setViewportMetricsPaddings {
1572 UIScreen* screen =
self.flutterScreenIfViewLoaded;
1577 CGFloat scale = screen.scale;
1578 _viewportMetrics.physical_padding_top =
self.view.safeAreaInsets.top * scale;
1579 _viewportMetrics.physical_padding_left =
self.view.safeAreaInsets.left * scale;
1580 _viewportMetrics.physical_padding_right =
self.view.safeAreaInsets.right * scale;
1581 _viewportMetrics.physical_padding_bottom =
self.view.safeAreaInsets.bottom * scale;
1584#pragma mark - Keyboard events
1586- (void)keyboardWillShowNotification:(NSNotification*)notification {
1591 [
self handleKeyboardNotification:notification];
1594- (void)keyboardWillChangeFrame:(NSNotification*)notification {
1599 [
self handleKeyboardNotification:notification];
1602- (void)keyboardWillBeHidden:(NSNotification*)notification {
1606 [
self handleKeyboardNotification:notification];
1609- (void)handleKeyboardNotification:(NSNotification*)notification {
1612 if ([
self shouldIgnoreKeyboardNotification:notification]) {
1616 NSDictionary* info = notification.userInfo;
1617 CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
1618 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1619 FlutterKeyboardMode keyboardMode = [
self calculateKeyboardAttachMode:notification];
1620 CGFloat calculatedInset = [
self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
1621 NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1628 if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
1629 [
self hideKeyboardImmediately];
1634 if (
self.targetViewInsetBottom == calculatedInset) {
1638 self.targetViewInsetBottom = calculatedInset;
1645 BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
1646 BOOL keyboardAnimationIsCompounding =
1647 self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;
1650 self.keyboardAnimationIsShowing = keyboardWillShow;
1652 if (!keyboardAnimationIsCompounding) {
1653 [
self startKeyBoardAnimation:duration];
1654 }
else if (
self.keyboardSpringAnimation) {
1655 self.keyboardSpringAnimation.toValue =
self.targetViewInsetBottom;
1659- (
BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
1664 if (notification.name == UIKeyboardWillHideNotification) {
1673 NSDictionary* info = notification.userInfo;
1674 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1675 if (notification.name == UIKeyboardWillChangeFrameNotification &&
1676 CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1682 if (CGRectIsEmpty(keyboardFrame)) {
1687 if ([
self isKeyboardNotificationForDifferentView:notification]) {
1693- (
BOOL)isKeyboardNotificationForDifferentView:(NSNotification*)notification {
1694 NSDictionary* info = notification.userInfo;
1698 id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1699 if (isLocal && ![isLocal boolValue]) {
1702 return self.engine.viewController !=
self;
1705- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification {
1713 NSDictionary* info = notification.userInfo;
1714 CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
1716 if (notification.name == UIKeyboardWillHideNotification) {
1717 return FlutterKeyboardModeHidden;
1722 if (CGRectEqualToRect(keyboardFrame, CGRectZero)) {
1723 return FlutterKeyboardModeFloating;
1726 if (CGRectIsEmpty(keyboardFrame)) {
1727 return FlutterKeyboardModeHidden;
1730 CGRect screenRect =
self.flutterScreenIfViewLoaded.bounds;
1731 CGRect adjustedKeyboardFrame = keyboardFrame;
1732 adjustedKeyboardFrame.origin.y += [
self calculateMultitaskingAdjustment:screenRect
1733 keyboardFrame:keyboardFrame];
1738 CGRect intersection = CGRectIntersection(adjustedKeyboardFrame, screenRect);
1739 CGFloat intersectionHeight = CGRectGetHeight(intersection);
1740 CGFloat intersectionWidth = CGRectGetWidth(intersection);
1741 if (round(intersectionHeight) > 0 && intersectionWidth > 0) {
1743 CGFloat screenHeight = CGRectGetHeight(screenRect);
1744 CGFloat adjustedKeyboardBottom = CGRectGetMaxY(adjustedKeyboardFrame);
1745 if (round(adjustedKeyboardBottom) < screenHeight) {
1746 return FlutterKeyboardModeFloating;
1748 return FlutterKeyboardModeDocked;
1750 return FlutterKeyboardModeHidden;
1753- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame {
1757 if (
self.viewIfLoaded.traitCollection.userInterfaceIdiom == UIUserInterfaceIdiomPad &&
1758 self.viewIfLoaded.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact &&
1759 self.viewIfLoaded.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular) {
1760 CGFloat screenHeight = CGRectGetHeight(screenRect);
1761 CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame);
1765 if (screenHeight == keyboardBottom) {
1768 CGRect viewRectRelativeToScreen =
1769 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1770 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1771 CGFloat viewBottom = CGRectGetMaxY(viewRectRelativeToScreen);
1772 CGFloat offset = screenHeight - viewBottom;
1780- (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)keyboardMode {
1782 if (keyboardMode == FlutterKeyboardModeDocked) {
1784 CGRect viewRectRelativeToScreen =
1785 [
self.viewIfLoaded convertRect:self.viewIfLoaded.frame
1786 toCoordinateSpace:self.flutterScreenIfViewLoaded.coordinateSpace];
1787 CGRect intersection = CGRectIntersection(keyboardFrame, viewRectRelativeToScreen);
1788 CGFloat portionOfKeyboardInView = CGRectGetHeight(intersection);
1793 CGFloat scale =
self.flutterScreenIfViewLoaded.scale;
1794 return portionOfKeyboardInView * scale;
1799- (void)startKeyBoardAnimation:(NSTimeInterval)duration {
1801 if (_viewportMetrics.physical_view_inset_bottom ==
self.targetViewInsetBottom) {
1807 if (!
self.keyboardAnimationView) {
1808 UIView* keyboardAnimationView = [[UIView alloc] init];
1809 keyboardAnimationView.hidden = YES;
1810 self.keyboardAnimationView = keyboardAnimationView;
1813 if (!
self.keyboardAnimationView.superview) {
1814 [
self.view addSubview:self.keyboardAnimationView];
1818 [
self.keyboardAnimationView.layer removeAllAnimations];
1821 self.keyboardAnimationView.frame =
1822 CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
1824 self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;
1827 [
self invalidateKeyboardAnimationVSyncClient];
1830 [
self setUpKeyboardAnimationVsyncClient:^(fml::TimePoint targetTime) {
1831 [weakSelf handleKeyboardAnimationCallbackWithTargetTime:targetTime];
1833 VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
1835 [UIView animateWithDuration:duration
1843 strongSelf.keyboardAnimationView.frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);
1846 CAAnimation* keyboardAnimation =
1847 [strongSelf.keyboardAnimationView.layer animationForKey:@"position"];
1848 [strongSelf setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation];
1850 completion:^(BOOL finished) {
1851 if (_keyboardAnimationVSyncClient == currentVsyncClient) {
1860 [strongSelf invalidateKeyboardAnimationVSyncClient];
1861 [strongSelf removeKeyboardAnimationView];
1862 [strongSelf ensureViewportMetricsIsCorrect];
1867- (void)hideKeyboardImmediately {
1868 [
self invalidateKeyboardAnimationVSyncClient];
1869 if (
self.keyboardAnimationView) {
1870 [
self.keyboardAnimationView.layer removeAllAnimations];
1871 [
self removeKeyboardAnimationView];
1872 self.keyboardAnimationView = nil;
1874 if (
self.keyboardSpringAnimation) {
1875 self.keyboardSpringAnimation = nil;
1878 self.targetViewInsetBottom = 0.0;
1879 [
self ensureViewportMetricsIsCorrect];
1882- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
1884 if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) {
1885 _keyboardSpringAnimation = nil;
1890 CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
1891 _keyboardSpringAnimation =
1892 [[
SpringAnimation alloc] initWithStiffness:keyboardCASpringAnimation.stiffness
1893 damping:keyboardCASpringAnimation.damping
1894 mass:keyboardCASpringAnimation.mass
1895 initialVelocity:keyboardCASpringAnimation.initialVelocity
1896 fromValue:self.originalViewInsetBottom
1897 toValue:self.targetViewInsetBottom];
1900- (void)handleKeyboardAnimationCallbackWithTargetTime:(
fml::
TimePoint)targetTime {
1902 if (!
self.isViewLoaded) {
1907 if (!
self.keyboardAnimationView) {
1912 if (!
self.keyboardAnimationVSyncClient) {
1916 if (!
self.keyboardAnimationView.superview) {
1918 [
self.view addSubview:self.keyboardAnimationView];
1921 if (!
self.keyboardSpringAnimation) {
1922 if (
self.keyboardAnimationView.layer.presentationLayer) {
1923 self->_viewportMetrics.physical_view_inset_bottom =
1924 self.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1925 [
self updateViewportMetricsIfNeeded];
1929 self->_viewportMetrics.physical_view_inset_bottom =
1930 [
self.keyboardSpringAnimation curveFunction:timeElapsed.ToSecondsF()];
1931 [
self updateViewportMetricsIfNeeded];
1935- (void)setUpKeyboardAnimationVsyncClient:
1937 if (!keyboardAnimationCallback) {
1940 NSAssert(_keyboardAnimationVSyncClient == nil,
1941 @"_keyboardAnimationVSyncClient must be nil when setting up.");
1945 auto uiCallback = [animationCallback](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1946 fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1947 fml::TimePoint targetTime = recorder->GetVsyncTargetTime() + frameInterval;
1948 dispatch_async(dispatch_get_main_queue(), ^(
void) {
1949 animationCallback(targetTime);
1953 _keyboardAnimationVSyncClient = [[
VSyncClient alloc] initWithTaskRunner:self.engine.uiTaskRunner
1954 callback:uiCallback];
1955 _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
1956 [_keyboardAnimationVSyncClient await];
1959- (void)invalidateKeyboardAnimationVSyncClient {
1960 [_keyboardAnimationVSyncClient invalidate];
1961 _keyboardAnimationVSyncClient = nil;
1964- (void)removeKeyboardAnimationView {
1965 if (
self.keyboardAnimationView.superview != nil) {
1966 [
self.keyboardAnimationView removeFromSuperview];
1970- (void)ensureViewportMetricsIsCorrect {
1971 if (_viewportMetrics.physical_view_inset_bottom !=
self.targetViewInsetBottom) {
1973 _viewportMetrics.physical_view_inset_bottom =
self.targetViewInsetBottom;
1974 [
self updateViewportMetricsIfNeeded];
1978- (void)handlePressEvent:(FlutterUIPressProxy*)press
1979 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4)) {
1980 if (@available(iOS 13.4, *)) {
1985 [
self.keyboardManager handlePress:press nextAction:next];
2001- (void)superPressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2002 [
super pressesBegan:presses withEvent:event];
2005- (void)superPressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2006 [
super pressesChanged:presses withEvent:event];
2009- (void)superPressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2010 [
super pressesEnded:presses withEvent:event];
2013- (void)superPressesCancelled:(NSSet<UIPress*>*)presses withEvent:(UIPressesEvent*)event {
2014 [
super pressesCancelled:presses withEvent:event];
2022- (void)pressesBegan:(NSSet<UIPress*>*)presses
2023 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2024 if (@available(iOS 13.4, *)) {
2026 for (UIPress* press in presses) {
2027 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2029 [weakSelf superPressesBegan:[NSSet setWithObject:press] withEvent:event];
2033 [
super pressesBegan:presses withEvent:event];
2037- (void)pressesChanged:(NSSet<UIPress*>*)presses
2038 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2039 if (@available(iOS 13.4, *)) {
2041 for (UIPress* press in presses) {
2042 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2044 [weakSelf superPressesChanged:[NSSet setWithObject:press] withEvent:event];
2048 [
super pressesChanged:presses withEvent:event];
2052- (void)pressesEnded:(NSSet<UIPress*>*)presses
2053 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2054 if (@available(iOS 13.4, *)) {
2056 for (UIPress* press in presses) {
2057 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2059 [weakSelf superPressesEnded:[NSSet setWithObject:press] withEvent:event];
2063 [
super pressesEnded:presses withEvent:event];
2067- (void)pressesCancelled:(NSSet<UIPress*>*)presses
2068 withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) {
2069 if (@available(iOS 13.4, *)) {
2071 for (UIPress* press in presses) {
2072 [
self handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press event:event]
2074 [weakSelf superPressesCancelled:[NSSet setWithObject:press] withEvent:event];
2078 [
super pressesCancelled:presses withEvent:event];
2082#pragma mark - Orientation updates
2084- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {
2087 dispatch_async(dispatch_get_main_queue(), ^{
2088 NSDictionary* info = notification.userInfo;
2089 NSNumber* update = info[@(flutter::kOrientationUpdateNotificationKey)];
2090 if (update == nil) {
2093 [weakSelf performOrientationUpdate:update.unsignedIntegerValue];
2097- (void)requestGeometryUpdateForWindowScenes:(NSSet<UIScene*>*)windowScenes
2098 API_AVAILABLE(ios(16.0)) {
2099 for (UIScene* windowScene in windowScenes) {
2100 FML_DCHECK([windowScene isKindOfClass:[UIWindowScene class]]);
2101 UIWindowSceneGeometryPreferencesIOS* preference = [[UIWindowSceneGeometryPreferencesIOS alloc]
2102 initWithInterfaceOrientations:self.orientationPreferences];
2103 [(UIWindowScene*)windowScene
2104 requestGeometryUpdateWithPreferences:preference
2105 errorHandler:^(NSError* error) {
2106 os_log_error(OS_LOG_DEFAULT,
2107 "Failed to change device orientation: %@", error);
2109 [
self setNeedsUpdateOfSupportedInterfaceOrientations];
2113- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences {
2114 if (new_preferences !=
self.orientationPreferences) {
2115 self.orientationPreferences = new_preferences;
2117 if (@available(iOS 16.0, *)) {
2119 NSSet<UIScene*>* scenes = [NSSet set];
2120 if (flutterApplication) {
2121 scenes = [flutterApplication.connectedScenes
2122 filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
2123 id scene, NSDictionary* bindings) {
2124 return [scene isKindOfClass:[UIWindowScene class]];
2126 }
else if (
self.flutterWindowSceneIfViewLoaded) {
2127 scenes = [NSSet setWithObject:self.flutterWindowSceneIfViewLoaded];
2129 [
self requestGeometryUpdateForWindowScenes:scenes];
2131 UIInterfaceOrientationMask currentInterfaceOrientation = 0;
2132 UIWindowScene* windowScene =
self.flutterWindowSceneIfViewLoaded;
2136 @"Accessing the interface orientation when the window scene is unavailable."];
2139 currentInterfaceOrientation = 1 << windowScene.interfaceOrientation;
2140 if (!(
self.orientationPreferences & currentInterfaceOrientation)) {
2141 [UIViewController attemptRotationToDeviceOrientation];
2143 if (
self.orientationPreferences & UIInterfaceOrientationMaskPortrait) {
2147 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait)
2148 forKey:@"orientation"];
2149 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskPortraitUpsideDown) {
2150 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortraitUpsideDown)
2151 forKey:@"orientation"];
2152 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeLeft) {
2153 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft)
2154 forKey:@"orientation"];
2155 }
else if (
self.orientationPreferences & UIInterfaceOrientationMaskLandscapeRight) {
2156 [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeRight)
2157 forKey:@"orientation"];
2164- (void)onHideHomeIndicatorNotification:(NSNotification*)notification {
2165 self.isHomeIndicatorHidden = YES;
2168- (void)onShowHomeIndicatorNotification:(NSNotification*)notification {
2169 self.isHomeIndicatorHidden = NO;
2172- (void)setIsHomeIndicatorHidden:(
BOOL)hideHomeIndicator {
2173 if (hideHomeIndicator != _isHomeIndicatorHidden) {
2174 _isHomeIndicatorHidden = hideHomeIndicator;
2175 [
self setNeedsUpdateOfHomeIndicatorAutoHidden];
2179- (
BOOL)prefersHomeIndicatorAutoHidden {
2180 return self.isHomeIndicatorHidden;
2183- (
BOOL)shouldAutorotate {
2187- (NSUInteger)supportedInterfaceOrientations {
2188 return self.orientationPreferences;
2191#pragma mark - Accessibility
2193- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
2198 int32_t flags = [
self.accessibilityFeatures flags];
2199#if TARGET_OS_SIMULATOR
2205 _isVoiceOverRunning = [
self.accessibilityFeatures isVoiceOverRunning];
2206 enabled = _isVoiceOverRunning || [
self.accessibilityFeatures isSwitchControlRunning] ||
2207 [
self.accessibilityFeatures isSpeakScreenEnabled];
2209 [
self.engine enableSemantics:enabled withFlags:flags];
2212- (
BOOL)accessibilityPerformEscape {
2214 if (navigationChannel) {
2221#pragma mark - Set user settings
2223- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
2224 [
super traitCollectionDidChange:previousTraitCollection];
2225 [
self onUserSettingsChanged:nil];
2229 if (
self.isAutoResizable) {
2230 [
self.flutterView resetIntrinsicContentSize];
2234- (void)onUserSettingsChanged:(NSNotification*)notification {
2235 [
self.engine.settingsChannel sendMessage:@{
2236 @"textScaleFactor" : @(
self.textScaleFactor),
2238 @"platformBrightness" :
self.brightnessMode,
2239 @"platformContrast" : self.contrastMode,
2240 @"nativeSpellCheckServiceDefined" : @YES,
2241 @"supportsShowingSystemContextMenu" : @(self.supportsShowingSystemContextMenu)
2245- (CGFloat)textScaleFactor {
2247 if (flutterApplication == nil) {
2248 [FlutterLogger logWarning:@"Dynamic content size update is not supported in app extension."];
2252 UIContentSizeCategory category = flutterApplication.preferredContentSizeCategory;
2258 const CGFloat xs = 14;
2259 const CGFloat s = 15;
2260 const CGFloat m = 16;
2261 const CGFloat l = 17;
2262 const CGFloat xl = 19;
2263 const CGFloat xxl = 21;
2264 const CGFloat xxxl = 23;
2267 const CGFloat ax1 = 28;
2268 const CGFloat ax2 = 33;
2269 const CGFloat ax3 = 40;
2270 const CGFloat ax4 = 47;
2271 const CGFloat ax5 = 53;
2275 if ([category isEqualToString:UIContentSizeCategoryExtraSmall]) {
2277 }
else if ([category isEqualToString:UIContentSizeCategorySmall]) {
2279 }
else if ([category isEqualToString:UIContentSizeCategoryMedium]) {
2281 }
else if ([category isEqualToString:UIContentSizeCategoryLarge]) {
2283 }
else if ([category isEqualToString:UIContentSizeCategoryExtraLarge]) {
2285 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
2287 }
else if ([category isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
2289 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
2291 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
2293 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
2295 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
2297 }
else if ([category isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
2304- (
BOOL)supportsShowingSystemContextMenu {
2305 if (@available(iOS 16.0, *)) {
2315- (NSString*)brightnessMode {
2316 UIUserInterfaceStyle style =
self.traitCollection.userInterfaceStyle;
2318 if (style == UIUserInterfaceStyleDark) {
2328- (NSString*)contrastMode {
2329 UIAccessibilityContrast contrast =
self.traitCollection.accessibilityContrast;
2331 if (contrast == UIAccessibilityContrastHigh) {
2338#pragma mark - Status bar style
2340- (UIStatusBarStyle)preferredStatusBarStyle {
2341 return self.statusBarStyle;
2344- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification {
2347 dispatch_async(dispatch_get_main_queue(), ^{
2353 NSDictionary* info = notification.userInfo;
2354 NSNumber* update = info[@(flutter::kOverlayStyleUpdateNotificationKey)];
2355 if (update == nil) {
2359 UIStatusBarStyle style =
static_cast<UIStatusBarStyle
>(update.integerValue);
2360 if (style != strongSelf.statusBarStyle) {
2361 strongSelf.statusBarStyle = style;
2362 [strongSelf setNeedsStatusBarAppearanceUpdate];
2367- (void)setPrefersStatusBarHidden:(
BOOL)hidden {
2368 if (hidden !=
self.flutterPrefersStatusBarHidden) {
2369 self.flutterPrefersStatusBarHidden = hidden;
2370 [
self setNeedsStatusBarAppearanceUpdate];
2374- (
BOOL)prefersStatusBarHidden {
2375 return self.flutterPrefersStatusBarHidden;
2378#pragma mark - Platform views
2381 return self.engine.platformViewsController;
2384- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
2385 return self.engine.binaryMessenger;
2388#pragma mark - FlutterBinaryMessenger
2390- (void)sendOnChannel:(NSString*)channel message:(NSData*)message {
2391 [
self.engine.binaryMessenger sendOnChannel:channel message:message];
2394- (void)sendOnChannel:(NSString*)channel
2395 message:(NSData*)message
2397 NSAssert(
channel,
@"The channel must not be null");
2398 [
self.engine.binaryMessenger sendOnChannel:channel message:message binaryReply:callback];
2401- (NSObject<FlutterTaskQueue>*)makeBackgroundTaskQueue {
2402 return [
self.engine.binaryMessenger makeBackgroundTaskQueue];
2406 binaryMessageHandler:
2408 return [
self setMessageHandlerOnChannel:channel binaryMessageHandler:handler taskQueue:nil];
2412 setMessageHandlerOnChannel:(NSString*)channel
2414 taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
2415 NSAssert(
channel,
@"The channel must not be null");
2416 return [
self.engine.binaryMessenger setMessageHandlerOnChannel:channel
2417 binaryMessageHandler:handler
2418 taskQueue:taskQueue];
2422 [
self.engine.binaryMessenger cleanUpConnection:connection];
2425#pragma mark - FlutterTextureRegistry
2428 return [
self.engine.textureRegistry registerTexture:texture];
2431- (void)unregisterTexture:(int64_t)textureId {
2432 [
self.engine.textureRegistry unregisterTexture:textureId];
2435- (void)textureFrameAvailable:(int64_t)textureId {
2436 [
self.engine.textureRegistry textureFrameAvailable:textureId];
2439- (NSString*)lookupKeyForAsset:(NSString*)asset {
2443- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
2447- (
id<FlutterPluginRegistry>)pluginRegistry {
2451+ (
BOOL)isUIAccessibilityIsVoiceOverRunning {
2452 return UIAccessibilityIsVoiceOverRunning();