From 130bc5203af5e7c0eb3aa14aa0ea2df2def1daf7 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 26 Aug 2025 16:53:00 -0700 Subject: [PATCH] test --- .../framework/Headers/FlutterViewController.h | 7 ++ .../Source/FlutterPlatformViewsController.mm | 35 +++++++++- .../darwin/ios/framework/Source/FlutterView.h | 6 ++ .../ios/framework/Source/FlutterView.mm | 69 +++++++++++++++++++ .../framework/Source/FlutterViewController.mm | 65 +++++++++++++++++ .../application/Resources/app/package.json | 4 -- 6 files changed, 181 insertions(+), 5 deletions(-) delete mode 100644 packages/flutter_tools/test/data/vscode/application/Resources/app/package.json diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index e41565c3b1f..3589c05a634 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -254,6 +254,13 @@ FLUTTER_DARWIN_EXPORT */ @property(nonatomic, readonly) BOOL engineAllowHeadlessExecution; +/** + * Controls whether the created view can be sized based on its content. + * + * Default is `NO`. + */ +@property(nonatomic, getter=isAutoResizable) BOOL autoResizable; + @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm index 8fdaf2fb76c..33019b3f842 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsController.mm @@ -667,6 +667,21 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { // No platform views to render; we're done. if (self.flutterView == nil || (self.compositionOrder.empty() && !self.hadPlatformViews)) { + // No platform views to render but there is a Flutter view and therefore may have a resize + if (self.flutterView != nil) { + // If the raster thread isn't merged, resize the view on the platform thread and block until + // complete. + auto latch = std::make_shared(1u); + fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, + [self, frameSize = self.frameSize, latch]() mutable { + [self performResize:frameSize]; + latch->CountDown(); + }); + if (![[NSThread currentThread] isMainThread]) { + latch->Wait(); + } + } + // No platform views to render; we're done. self.hadPlatformViews = NO; return background_frame->Submit(); } @@ -753,13 +768,16 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { self.layerPool->RemoveUnusedLayers(); self.layerPool->RecycleLayers(); + // If the raster thread isn't merged, wait for the platform thread to finish submitting the frame. + auto latch = std::make_shared(1u); auto task = [self, // platformViewLayers = std::move(platformViewLayers), // currentCompositionParams = self.currentCompositionParams, // viewsToRecomposite = self.viewsToRecomposite, // compositionOrder = self.compositionOrder, // unusedLayers = std::move(unusedLayers), // - surfaceFrames = std::move(surfaceFrames) // + surfaceFrames = std::move(surfaceFrames), // + latch // ]() mutable { [self performSubmit:platformViewLayers currentCompositionParams:currentCompositionParams @@ -767,10 +785,14 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { compositionOrder:compositionOrder unusedLayers:unusedLayers surfaceFrames:surfaceFrames]; + latch->CountDown(); }; fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, fml::MakeCopyable(std::move(task))); + if (![[NSThread currentThread] isMainThread]) { + latch->Wait(); + } return didEncode; } @@ -799,6 +821,17 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) { } } +- (void)performResize:(const flutter::DlISize&)frameSize { + TRACE_EVENT0("flutter", "PlatformViewsController::PerformResize"); + FML_DCHECK([[NSThread currentThread] isMainThread]); + if (self.flutterView != nil) { + FML_LOG(ERROR) << "RESIZE RENDER STEP 9: \n setIntrinsicContentSize " << frameSize.width << "x" + << frameSize.height; + [(FlutterView*)self.flutterView + setIntrinsicContentSize:CGSizeMake(frameSize.width, frameSize.height)]; + } +} + - (void)performSubmit:(const LayersMap&)platformViewLayers currentCompositionParams: (std::unordered_map&)currentCompositionParams diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h index 7f3a32ed63f..498ebd98143 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h @@ -29,6 +29,9 @@ - (void)flutterViewAccessibilityDidCall; @end +@interface FlutterAutoResizeLayoutConstraint : NSLayoutConstraint +@end + @interface FlutterView : UIView - (instancetype)init NS_UNAVAILABLE; @@ -42,6 +45,9 @@ - (UIScreen*)screen; - (MTLPixelFormat)pixelFormat; +- (void)setIntrinsicContentSize:(CGSize)size; +- (void)resetIntrinsicContentSize; +@property(nonatomic, assign, readwrite) BOOL autoResizable; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 61d950b30e7..3a4f8ecdfce 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -14,8 +14,12 @@ FLUTTER_ASSERT_ARC @property(nonatomic, weak) id delegate; @end +@implementation FlutterAutoResizeLayoutConstraint +@end + @implementation FlutterView { BOOL _isWideGamutEnabled; + CGSize _intrinsicSize; } - (instancetype)init { @@ -37,6 +41,69 @@ FLUTTER_ASSERT_ARC return self.window.windowScene.screen; } + +// iOS has a concept of "intrinsicContentSize", which indicates the size a view would like to be +// based on its content. When an intrinsicContentSize is set, iOS will automatically add Auto Layout +// constraints for the width and/or height. However, the constraints use a private API. There are +// situations where we may want to filter these constraints. To avoid using a private API, Flutter +// creates a custom constraint called FlutterAutoResizeLayoutConstraint to add a width/height +// constraint that reflects the intrinsicContentSize. +- (void)setIntrinsicContentSize:(CGSize)size { + if (!self.autoResizable) { + return; + } + + CGFloat scale = self.window.windowScene.screen.scale; + CGSize scaledSize = CGSizeMake(size.width / scale, size.height / scale); + + // If the size has not changed, don't update constraints. + if (CGSizeEqualToSize(_intrinsicSize, scaledSize)) { + return; + } + _intrinsicSize = scaledSize; + + self.translatesAutoresizingMaskIntoConstraints = false; + + // Remove any existing FlutterAutoResizeLayoutConstraint + [self removeAutoResizeLayoutConstraints]; + + FlutterAutoResizeLayoutConstraint* widthConstraint = + [FlutterAutoResizeLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:scaledSize.width]; + + FlutterAutoResizeLayoutConstraint* heightConstraint = + [FlutterAutoResizeLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:scaledSize.height]; + + // widthConstraint.priority = UILayoutPriorityDefaultLow; + // heightConstraint.priority = UILayoutPriorityDefaultLow; + [NSLayoutConstraint activateConstraints:@[ widthConstraint, heightConstraint ]]; + [self setNeedsLayout]; +} + +- (void)resetIntrinsicContentSize { + _intrinsicSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); + [self removeAutoResizeLayoutConstraints]; +} + +- (void)removeAutoResizeLayoutConstraints { + for (NSLayoutConstraint* constraint in self.constraints) { + if ([constraint isKindOfClass:[FlutterAutoResizeLayoutConstraint class]]) { + constraint.active = NO; + } + } +} + - (MTLPixelFormat)pixelFormat { if ([self.layer isKindOfClass:[CAMetalLayer class]]) { // It is a known Apple bug that CAMetalLayer incorrectly reports its supported @@ -77,6 +144,8 @@ FLUTTER_ASSERT_ARC _delegate = delegate; _isWideGamutEnabled = isWideGamutEnabled; self.layer.opaque = opaque; + _autoResizable = NO; + _intrinsicSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric); } return self; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f7fd6cd518d..841730d2d09 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -129,6 +129,8 @@ typedef struct MouseState { /// the same with frame rate of rendering. @property(nonatomic, strong) VSyncClient* touchRateCorrectionVSyncClient; +@property(nonatomic, assign) CGSize sizeBeforeAutoResized; + /* * Mouse and trackpad gesture recognizers */ @@ -168,6 +170,8 @@ typedef struct MouseState { @synthesize viewOpaque = _viewOpaque; @synthesize displayingFlutterUI = _displayingFlutterUI; +@synthesize autoResizable = _autoResizable; + // TODO(dkwingsmt): https://github.com/flutter/flutter/issues/138168 // No backing ivar is currently required; when multiple views are supported, we'll need to // synthesize the ivar and store the view identifier. @@ -1435,6 +1439,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) _viewportMetrics.device_pixel_ratio = scale; [self setViewportMetricsSize]; [self setViewportMetricsPaddings]; + [self updateAutoResizeConstraints]; [self updateViewportMetricsIfNeeded]; // There is no guarantee that UIKit will layout subviews when the application/scene is active. @@ -1460,6 +1465,61 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) } }]; } + } +} + +- (BOOL)isAutoResizable { + return _autoResizable; +} + +- (void)setAutoResizable:(BOOL)value { + _autoResizable = value; + self.flutterView.autoResizable = value; +} + +- (void)updateAutoResizeConstraints { + if (!self.isAutoResizable) { + return; + } + + // When viewDidLayoutSubviews is called (which is where this method is called), + // the view has finished laying out its subviews and has applied any auto layout constraints. + // Therefore, we're able to use the frame to determine what size is allowed by layout constraints. + // However, we're only able to use this value if Flutter hasn't already applied any + // FlutterAutoResizeLayoutConstraint constraints. Once Flutter applies constraints, that will + // determine the frame. This imposes a limitation on content resizing that layout constraints + // updated after an auto-resize has been applied may not work properly. + BOOL hasBeenAutoResized = NO; + for (NSLayoutConstraint* constraint in self.view.constraints) { + if ([constraint isKindOfClass:[FlutterAutoResizeLayoutConstraint class]]) { + hasBeenAutoResized = YES; + break; + } + } + if (!hasBeenAutoResized) { + self.sizeBeforeAutoResized = self.view.frame.size; + } + + CGFloat maxWidth = self.sizeBeforeAutoResized.width; + CGFloat maxHeight = self.sizeBeforeAutoResized.height; + CGFloat minWidth = self.sizeBeforeAutoResized.width; + CGFloat minHeight = self.sizeBeforeAutoResized.height; + + // maxWidth or maxHeight may be 0 when the width/height are ambiguous. + if (maxWidth == 0) { + maxWidth = DBL_MAX; + } + if (maxHeight == 0) { + maxHeight = DBL_MAX; + } + _viewportMetrics.physical_min_width_constraint = minWidth * _viewportMetrics.device_pixel_ratio; + _viewportMetrics.physical_max_width_constraint = maxWidth * _viewportMetrics.device_pixel_ratio; + _viewportMetrics.physical_min_height_constraint = minHeight * _viewportMetrics.device_pixel_ratio; + _viewportMetrics.physical_max_height_constraint = maxHeight * _viewportMetrics.device_pixel_ratio; + + // NSLog(@"Updated auto-resize constraints: maxWidth = %f, maxHeight = %f, minWidth = %f, " + // @"minHeight = %f", + // maxWidth, maxHeight, minWidth, minHeight); } - (void)viewSafeAreaInsetsDidChange { @@ -2201,6 +2261,11 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; [self onUserSettingsChanged:nil]; + + // Size changes when certain traits change, such as orientation or scale. + if (self.isAutoResizable) { + [self.flutterView resetIntrinsicContentSize]; + } } - (void)onUserSettingsChanged:(NSNotification*)notification { diff --git a/packages/flutter_tools/test/data/vscode/application/Resources/app/package.json b/packages/flutter_tools/test/data/vscode/application/Resources/app/package.json deleted file mode 100644 index e4e72b6e0ba..00000000000 --- a/packages/flutter_tools/test/data/vscode/application/Resources/app/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "fake-vs-code-install-for-tests", - "version": "1.2.3" -}