This commit is contained in:
louisehsu 2025-08-26 16:53:00 -07:00
parent 757986dd39
commit 130bc5203a
6 changed files with 181 additions and 5 deletions

View File

@ -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

View File

@ -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<fml::CountDownLatch>(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<fml::CountDownLatch>(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<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams

View File

@ -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

View File

@ -14,8 +14,12 @@ FLUTTER_ASSERT_ARC
@property(nonatomic, weak) id<FlutterViewEngineDelegate> 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;

View File

@ -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 {

View File

@ -1,4 +0,0 @@
{
"name": "fake-vs-code-install-for-tests",
"version": "1.2.3"
}