Revert "[ios] Dynamic Content Resizing" (#178958)

Reverts flutter/flutter#177410

The change is causing the task `wide_gamut_ios` to hang every time.
Local testing shows that reverting this commit fixes the problem.
This commit is contained in:
Jim Graham 2025-11-22 07:42:27 -08:00 committed by GitHub
parent 12b5c1cfdf
commit c8cfb2b1a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 6 additions and 512 deletions

View File

@ -54,9 +54,6 @@ Future<void> main() async {
final File marquee = File(path.join(flutterModuleLibSource.path, 'marquee'));
marquee.copySync(path.join(flutterModuleLibDestination.path, 'marquee.dart'));
final File resize = File(path.join(flutterModuleLibSource.path, 'resize'));
resize.copySync(path.join(flutterModuleLibDestination.path, 'resize.dart'));
section('Create package with native assets');
const String ffiPackageName = 'ffi_package';

View File

@ -126,30 +126,6 @@ static const CGFloat kStandardTimeOut = 60.0;
XCTAssertTrue([app.navigationBars[@"Flutter iOS Demos Home"] waitForExistenceWithTimeout:kStandardTimeOut]);
}
- (void)testResize {
XCUIApplication *app = self.app;
[self waitForAndTapElement:app.buttons[@"Dynamic Content Resizing"]];
XCUIElement *flutterView = app.otherElements[@"flutter_view"];
CGRect flutterViewFrame = flutterView.frame;
CGSize flutterViewSize = flutterViewFrame.size;
XCTAssertTrue(flutterViewSize.height == 100);
[self waitForAndTapElement: app.otherElements[@"flutter_view"]];
XCUIElement *flutterViewPostClick = app.otherElements[@"flutter_view"];
CGRect flutterViewFramePostClick = flutterViewPostClick.frame;
CGSize flutterViewSizePostClick = flutterViewFramePostClick.size;
XCTAssertTrue(flutterViewSizePostClick.height == 200);
// Back navigation.
[app.navigationBars[@"Dynamic Content Resizing"].buttons[@"Flutter iOS Demos Home"] tap];
XCTAssertTrue([app.navigationBars[@"Flutter iOS Demos Home"] waitForExistenceWithTimeout:kStandardTimeOut]);
}
- (void)waitForAndTapElement:(XCUIElement *)element {
NSPredicate *hittable = [NSPredicate predicateWithFormat:@"exists == YES AND hittable == YES"];
[self expectationForPredicate:hittable evaluatedWithObject:element handler:nil];

View File

@ -13,7 +13,6 @@
74F97871215AB9E9005A0F04 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 74F9786F215AB9E9005A0F04 /* LaunchScreen.storyboard */; };
74F97874215AB9E9005A0F04 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 74F97873215AB9E9005A0F04 /* main.m */; };
7E06ECD9FAEFCA4A0ED04D6E /* libPods-FlutterUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 327E8CC22A494E7D7155FB58 /* libPods-FlutterUITests.a */; };
F2D997DE2EA8BCB0002FC7EC /* DynamicResizingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F2D997DD2EA8BCB0002FC7EC /* DynamicResizingViewController.m */; };
F7C2661424AC18D00085742D /* DualFlutterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F7C2660E24AC18D00085742D /* DualFlutterViewController.m */; };
F7C2661524AC18D00085742D /* FullScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F7C2660F24AC18D00085742D /* FullScreenViewController.m */; };
F7C2661624AC18D00085742D /* HybridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F7C2661124AC18D00085742D /* HybridViewController.m */; };
@ -46,8 +45,6 @@
8E6751F293967EE3DFF4178A /* Pods-FlutterUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlutterUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FlutterUITests/Pods-FlutterUITests.release.xcconfig"; sourceTree = "<group>"; };
91B7D4263BFD246A27391225 /* Pods-Host.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Host.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Host/Pods-Host.debug.xcconfig"; sourceTree = "<group>"; };
E20960BD8B505D605FE82853 /* libPods-Host.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Host.a"; sourceTree = BUILT_PRODUCTS_DIR; };
F2D997DC2EA8BCB0002FC7EC /* DynamicResizingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DynamicResizingViewController.h; sourceTree = "<group>"; };
F2D997DD2EA8BCB0002FC7EC /* DynamicResizingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DynamicResizingViewController.m; sourceTree = "<group>"; };
F7C2660E24AC18D00085742D /* DualFlutterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DualFlutterViewController.m; sourceTree = "<group>"; };
F7C2660F24AC18D00085742D /* FullScreenViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FullScreenViewController.m; sourceTree = "<group>"; };
F7C2661024AC18D00085742D /* HybridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HybridViewController.h; sourceTree = "<group>"; };
@ -106,8 +103,6 @@
74F97863215AB9E8005A0F04 /* Host */ = {
isa = PBXGroup;
children = (
F2D997DC2EA8BCB0002FC7EC /* DynamicResizingViewController.h */,
F2D997DD2EA8BCB0002FC7EC /* DynamicResizingViewController.m */,
F7C2661724AC18DD0085742D /* MainViewController.h */,
F7C2661A24AC18DD0085742D /* MainViewController.m */,
F7C2661924AC18DD0085742D /* NativeViewController.h */,
@ -289,14 +284,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FlutterUITests/Pods-FlutterUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FlutterUITests/Pods-FlutterUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FlutterUITests/Pods-FlutterUITests-frameworks.sh\"\n";
@ -314,8 +305,6 @@
"${SRCROOT}/../hello/.ios/Flutter/flutter_export_environment.sh",
);
name = "[CP-User] Run Flutter Build hello Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../hello/.ios/Flutter/flutter_export_environment.sh\"\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build";
@ -328,14 +317,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Host/Pods-Host-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Host/Pods-Host-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Host/Pods-Host-frameworks.sh\"\n";
@ -353,8 +338,6 @@
"${SRCROOT}/../hello/.ios/Flutter/flutter_export_environment.sh",
);
name = "[CP-User] Run Flutter Build hello Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../hello/.ios/Flutter/flutter_export_environment.sh\"\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build";
@ -389,7 +372,6 @@
buildActionMask = 2147483647;
files = (
F7C2661524AC18D00085742D /* FullScreenViewController.m in Sources */,
F2D997DE2EA8BCB0002FC7EC /* DynamicResizingViewController.m in Sources */,
F7C2661424AC18D00085742D /* DualFlutterViewController.m in Sources */,
F7C2661C24AC18DD0085742D /* MainViewController.m in Sources */,
74F97874215AB9E9005A0F04 /* main.m in Sources */,

View File

@ -1,16 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface DynamicResizingViewController : UIViewController
@property (readonly, strong, nonatomic) FlutterViewController* flutterViewController;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,72 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Flutter/Flutter.h>
#import "DynamicResizingViewController.h"
@interface DynamicResizingViewController ()
@end
@implementation DynamicResizingViewController {
FlutterBasicMessageChannel *_messageChannel;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemBackgroundColor];
self.title = @"Dynamic Content Resizing";
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:scrollView];
UIStackView *stackView = [[UIStackView alloc] init];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.axis = UILayoutConstraintAxisVertical;
stackView.spacing = 10;
[scrollView addSubview:stackView];
[NSLayoutConstraint activateConstraints:@[
[scrollView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[scrollView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]
]];
[NSLayoutConstraint activateConstraints:@[
[stackView.topAnchor constraintEqualToAnchor:scrollView.contentLayoutGuide.topAnchor],
[stackView.leadingAnchor constraintEqualToAnchor:scrollView.contentLayoutGuide.leadingAnchor],
[stackView.trailingAnchor constraintEqualToAnchor:scrollView.contentLayoutGuide.trailingAnchor],
[stackView.bottomAnchor constraintEqualToAnchor:scrollView.contentLayoutGuide.bottomAnchor],
[stackView.widthAnchor constraintEqualToAnchor:scrollView.frameLayoutGuide.widthAnchor]
]];
for (int index = 1; index <= 50; index++) {
if (index == 10) {
_flutterViewController = [[FlutterViewController alloc] init];
[_flutterViewController setInitialRoute:@"resize"];
_flutterViewController.autoResizable = true;
[self addChildViewController:_flutterViewController];
[stackView addArrangedSubview:_flutterViewController.view];
_flutterViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
_flutterViewController.view.accessibilityIdentifier = @"flutter_view";
[_flutterViewController didMoveToParentViewController:self];
} else {
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:@" Hello from iOS %d ", index];
label.backgroundColor = (index % 2 == 0) ? [UIColor systemGray5Color] : [UIColor systemGray3Color];
label.translatesAutoresizingMaskIntoConstraints = NO;
[label.heightAnchor constraintEqualToConstant:44].active = YES;
[stackView addArrangedSubview:label];
}
}
}
@end

View File

@ -9,7 +9,6 @@
#import "FullScreenViewController.h"
#import "HybridViewController.h"
#import "NativeViewController.h"
#import "DynamicResizingViewController.h"
@interface MainViewController ()
@ -45,7 +44,6 @@
self.flutterViewWarmButton = [self addButton:@"Flutter View (Warm)" action:@selector(showFlutterViewWarm)];
[self addButton:@"Hybrid View (Warm)" action:@selector(showHybridView)];
[self addButton:@"Dual Flutter View (Cold)" action:@selector(showDualView)];
[self addButton:@"Dynamic Content Resizing" action:@selector(showContentResizingView)];
}
- (FlutterEngine *)engine {
@ -76,13 +74,6 @@
animated:YES];
}
- (void)showContentResizingView {
DynamicResizingViewController *resizingViewController =
[[DynamicResizingViewController alloc] init];
[self.navigationController pushViewController:resizingViewController
animated:NO];
}
- (void)showFullScreenCold {
FullScreenViewController *flutterViewController =
[[FullScreenViewController alloc] init];

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:ffi_package/ffi_package.dart';
import 'marquee.dart';
import 'resize.dart';
/// Route names. (See [main] for more details.)
///
@ -19,7 +18,6 @@ const String greenMarqueeRouteName = 'marquee_green';
const String purpleMarqueeRouteName = 'marquee_purple';
const String fullscreenRouteName = 'full';
const String hybridRouteName = 'hybrid';
const String resizeRouteName = 'resize';
/// Channel used to let the Flutter app know to reset the app to a specific
/// route. See the [run] method.
@ -57,9 +55,6 @@ Future<String> run(String? name) async {
case purpleMarqueeRouteName:
runApp(Marquee(color: Colors.purple[400]));
break;
case resizeRouteName:
runApp(ResizeApp());
break;
case fullscreenRouteName:
case hybridRouteName:
default:

View File

@ -1,42 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
class ResizeApp extends StatefulWidget {
/// Creates the [ResizeApp].
const ResizeApp({super.key});
@override
State<ResizeApp> createState() => _ResizeAppState();
}
class _ResizeAppState extends State<ResizeApp> {
int _listSize = 1;
void _addToList() {
setState(() {
_listSize++;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _addToList, // The tap anywhere logic
child: Center(
heightFactor: 1,
child: Directionality(
textDirection: TextDirection.ltr,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
for (int i = 0; i < _listSize; i++)
Container(color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(), height: 100),
],
),
),
),
);
}
}

View File

@ -13,7 +13,6 @@
#include "flutter/fml/message_loop.h"
#include "flutter/fml/message_loop_impl.h"
#include "flutter/fml/message_loop_task_queues.h"
#include "flutter/fml/synchronization/waitable_event.h"
namespace fml {

View File

@ -254,19 +254,6 @@ FLUTTER_DARWIN_EXPORT
*/
@property(nonatomic, readonly) BOOL engineAllowHeadlessExecution;
/**
* Controls whether the created view can be sized based on its content.
* When set to `YES`, the FlutterView will be the same size as the outermost widget.
* Cannot be used with unbounded height widgets, such as Scaffold.
* This property is intended to be used with Add-to-App scenarios.
*
* Once auto resizing is enabled, the FlutterView will rely on custom constraints from then on.
* Avoid disabling it after enabling, as behaviour will then be undefined.
*
* Default is `NO`.
*/
@property(nonatomic, getter=isAutoResizable) BOOL autoResizable;
@end
NS_ASSUME_NONNULL_END

View File

@ -404,14 +404,6 @@ extern CFTimeInterval display_link_target;
}
- (void)presentOnMainThread:(FlutterTexture*)texture {
if (texture.texture.width != _drawableSize.width ||
texture.texture.height != _drawableSize.height) {
// This texture was created with an old size, but the view has since been
// resized. Do not present this stale frame to avoid distortion. The texture
// will be correctly recycled on the next frame.
return;
}
// This is needed otherwise frame gets skipped on touch begin / end. Go figure.
// Might also be placebo
[self setNeedsDisplay];

View File

@ -665,19 +665,8 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext {
TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame");
// No platform views to render.
// No platform views to render; we're done.
if (self.flutterView == nil || (self.compositionOrder.empty() && !self.hadPlatformViews)) {
// No platform views to render but the FlutterView may need to be resized.
if (self.flutterView != nil) {
if (self.platformTaskRunner->RunsTasksOnCurrentThread()) {
[self performResize:self.frameSize];
} else {
PostTaskSync(self.platformTaskRunner, [self, frameSize = self.frameSize]() mutable {
[self performResize:frameSize];
});
}
}
self.hadPlatformViews = NO;
return background_frame->Submit();
}
@ -763,13 +752,15 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
std::vector<std::shared_ptr<flutter::OverlayLayer>> unusedLayers =
self.layerPool->RemoveUnusedLayers();
self.layerPool->RecycleLayers();
auto task = [self, //
platformViewLayers = std::move(platformViewLayers), //
currentCompositionParams = self.currentCompositionParams, //
viewsToRecomposite = self.viewsToRecomposite, //
compositionOrder = self.compositionOrder, //
unusedLayers = std::move(unusedLayers), //
surfaceFrames = std::move(surfaceFrames)]() mutable {
surfaceFrames = std::move(surfaceFrames) //
]() mutable {
[self performSubmit:platformViewLayers
currentCompositionParams:currentCompositionParams
viewsToRecomposite:viewsToRecomposite
@ -779,6 +770,7 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
};
fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, fml::MakeCopyable(std::move(task)));
return didEncode;
}
@ -807,16 +799,6 @@ 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) {
[(FlutterView*)self.flutterView
setIntrinsicContentSize:CGSizeMake(frameSize.width, frameSize.height)];
}
}
- (void)performSubmit:(const LayersMap&)platformViewLayers
currentCompositionParams:
(std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams
@ -1019,18 +1001,4 @@ static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
return _previousCompositionOrder;
}
namespace {
void PostTaskSync(const fml::RefPtr<fml::TaskRunner>& task_runner, fml::closure task) {
FML_DCHECK(!task_runner->RunsTasksOnCurrentThread());
fml::AutoResetWaitableEvent latch;
task_runner->PostTask([&latch, task = std::move(task)]() {
if (task) {
task();
}
latch.Signal();
});
latch.Wait();
}
} // namespace
@end

View File

@ -29,23 +29,6 @@
- (void)flutterViewAccessibilityDidCall;
@end
/**
* A custom NSLayoutConstraint subclass for autoresizing the FlutterView.
* This class is a special NSLayoutConstraint used internally to
* manage the dynamic resizing of a FlutterView based on its content.
*
* In native, `intrinsicContentSize` is a public property that determines the preferred
* sized of an UIView, based on it's internal content. Given a position and layout constraints,
* this allows the UIView to size itself.
* However, the mechanism in which this sizing occurs based on`intrinsicContentSize`
* and the layout constraints is private.
*
* This custom NSLayoutConstraint allows us to replicate this mechanizm without needing to rely
* on private APIs.
*/
@interface FlutterAutoResizeLayoutConstraint : NSLayoutConstraint
@end
@interface FlutterView : UIView
- (instancetype)init NS_UNAVAILABLE;
@ -60,19 +43,6 @@
- (UIScreen*)screen;
- (MTLPixelFormat)pixelFormat;
/**
* A method that sets the instrinsic content size
* This is used when autoResizable is enabled.
*/
- (void)setIntrinsicContentSize:(CGSize)size;
/**
* A method that resets and recalculates the instrinsic content size
* Currently called when the device orientation changes.
*/
- (void)resetIntrinsicContentSize;
@property(nonatomic, assign, readwrite) BOOL autoResizable;
@end
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEW_H_

View File

@ -16,12 +16,8 @@ FLUTTER_ASSERT_ARC
@property(nonatomic, weak) UIWindowScene* previousScene;
@end
@implementation FlutterAutoResizeLayoutConstraint
@end
@implementation FlutterView {
BOOL _isWideGamutEnabled;
CGSize _intrinsicSize;
}
- (instancetype)init {
@ -43,70 +39,6 @@ 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);
CGSize roundedScaleSize = CGSizeMake(roundf(scaledSize.width), roundf(scaledSize.height));
CGSize roundedIntrinsicSize =
CGSizeMake(roundf(_intrinsicSize.width), roundf(_intrinsicSize.height));
// If the size has not changed, don't update constraints.
if (CGSizeEqualToSize(roundedIntrinsicSize, roundedScaleSize)) {
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];
[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
@ -147,8 +79,6 @@ FLUTTER_ASSERT_ARC
_delegate = delegate;
_isWideGamutEnabled = isWideGamutEnabled;
self.layer.opaque = opaque;
_autoResizable = NO;
_intrinsicSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
}
return self;

View File

@ -101,6 +101,7 @@ typedef struct MouseState {
* Whether we should ignore viewport metrics updates during rotation transition.
*/
@property(nonatomic, assign) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
/**
* Keyboard animation properties
*/
@ -130,10 +131,6 @@ typedef struct MouseState {
/// the same with frame rate of rendering.
@property(nonatomic, strong) VSyncClient* touchRateCorrectionVSyncClient;
/// The size of the FlutterView's frame, as determined by auto-layout,
/// before Flutter's custom auto-resizing constraints are applied.
@property(nonatomic, assign) CGSize sizeBeforeAutoResized;
/*
* Mouse and trackpad gesture recognizers
*/
@ -1461,7 +1458,6 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
_viewportMetrics.device_pixel_ratio = scale;
[self setViewportMetricsSize];
[self checkAndUpdateAutoResizeConstraints];
[self setViewportMetricsPaddings];
[self updateViewportMetricsIfNeeded];
@ -1490,89 +1486,6 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
}
}
- (BOOL)isAutoResizable {
return self.flutterView.autoResizable;
}
- (void)setAutoResizable:(BOOL)value {
self.flutterView.autoResizable = value;
self.flutterView.contentMode = UIViewContentModeCenter;
}
- (void)checkAndUpdateAutoResizeConstraints {
if (!self.isAutoResizable) {
return;
}
[self updateAutoResizeConstraints];
}
/**
* Updates the FlutterAutoResizeLayoutConstraints based on the view's
* current frame.
*
* This method is invoked during viewDidLayoutSubviews, at which point the
* view has completed its subview layout and applied any existing Auto Layout
* constraints.
*
* Initially, the view's frame is used to determine the maximum size allowed
* by the native layout system. This size is then used to establish the viewport
* constraints for the Flutter engine.
*
* A critical consideration is that this initial frame-based sizing is only
* applicable if FlutterAutoResizeLayoutConstraints have not yet been applied
* by Flutter. Once Flutter applies its own FlutterAutoResizeLayoutConstraints,
* these constraints will subsequently dictate the view's frame.
*
* This interaction imposes a limitation: native layout constraints that are
* updated after Flutter has applied its auto-resize constraints may not
* function as expected or properly influence the FlutterView's size.
*/
- (void)updateAutoResizeConstraints {
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, eg. for
// unsized widgets
if (maxWidth == 0) {
maxWidth = CGFLOAT_MAX;
[FlutterLogger
logWarning:
@"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
@"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
@"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
@"rendering failure. Please ensure your top-level Flutter widget has explicit "
@"constraints (e.g., using SizedBox or Container)."];
}
if (maxHeight == 0) {
maxHeight = CGFLOAT_MAX;
[FlutterLogger
logWarning:
@"Warning: The outermost widget in the autoresizable Flutter view is unsized or has "
@"ambiguous dimensions, causing the host native view's width to be 0. The autoresizing "
@"logic is setting the viewport constraint to unbounded DBL_MAX to prevent "
@"rendering failure. Please ensure your top-level Flutter widget has explicit "
@"constraints (e.g., using SizedBox or Container)."];
}
_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;
}
- (void)viewSafeAreaInsetsDidChange {
[self setViewportMetricsPaddings];
[self updateViewportMetricsIfNeeded];
@ -2282,12 +2195,6 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
[self onUserSettingsChanged:nil];
// Since this method can get triggered by changes in device orientation, reset and recalculate the
// instrinsic size.
if (self.isAutoResizable) {
[self.flutterView resetIntrinsicContentSize];
}
}
- (void)onUserSettingsChanged:(NSNotification*)notification {

View File

@ -142,8 +142,6 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
nextAction:(void (^)())next API_AVAILABLE(ios(13.4));
- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
- (void)updateViewportMetricsIfNeeded;
- (void)updateAutoResizeConstraints;
- (void)checkAndUpdateAutoResizeConstraints;
- (void)onUserSettingsChanged:(NSNotification*)notification;
- (void)applicationWillTerminate:(NSNotification*)notification;
- (void)goToApplicationLifecycle:(nonnull NSString*)state;
@ -999,44 +997,6 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
OCMVerifyAll(mockEngine);
}
- (void)testUpdatedViewportMetricsDoesResizeFlutterViewWhenAutoResizable {
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:mockEngine
nibName:nil
bundle:nil];
id mockVC = OCMPartialMock(realVC);
mockEngine.viewController = mockVC;
OCMExpect([mockVC updateAutoResizeConstraints]);
[mockVC setAutoResizable:YES];
[mockVC viewDidLayoutSubviews];
OCMVerifyAll(mockVC);
}
- (void)testUpdatedViewportMetricsDoesNotResizeFlutterViewWhenNotAutoResizable {
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:mockEngine
nibName:nil
bundle:nil];
id mockVC = OCMPartialMock(realVC);
mockEngine.viewController = mockVC;
OCMReject([mockVC updateAutoResizeConstraints]);
[mockVC setAutoResizable:NO];
[mockVC viewDidLayoutSubviews];
OCMVerifyAll(mockVC);
}
- (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
@ -1319,36 +1279,6 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
return mockTraitCollection;
}
- (void)testTraitCollectionDidChangeCallsResetIntrinsicContentSizeWhenAutoResizable {
// Setup test.
id mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:mockEngine
nibName:nil
bundle:nil];
id partialMockVC = OCMPartialMock(realVC);
id mockFlutterView = OCMClassMock([FlutterView class]);
OCMStub([partialMockVC flutterView]).andReturn(mockFlutterView);
// Ensure isAutoResizable is YES
OCMStub([partialMockVC isAutoResizable]).andReturn(YES);
// Expect resetIntrinsicContentSize to be called on mockFlutterView
OCMExpect([mockFlutterView resetIntrinsicContentSize]);
// Exercise behavior under test.
[partialMockVC traitCollectionDidChange:nil];
// Verify behavior.
OCMVerifyAll(mockFlutterView);
// Clean up mocks
[partialMockVC stopMocking];
[mockFlutterView stopMocking];
}
#pragma mark - Platform Contrast
- (void)testItReportsNormalPlatformContrastByDefault {