@@ -63,6 +63,11 @@ @interface FlutterViewController () <FlutterBinaryMessenger,
63
63
@property (nonatomic , assign ) BOOL isHomeIndicatorHidden;
64
64
@property (nonatomic , assign ) BOOL isPresentingViewControllerAnimating;
65
65
66
+ /* *
67
+ * Whether we should ignore viewport metrics updates during rotation transition.
68
+ */
69
+ @property (nonatomic , assign ) BOOL shouldIgnoreViewportMetricsUpdatesDuringRotation;
70
+
66
71
/* *
67
72
* Keyboard animation properties
68
73
*/
@@ -843,6 +848,35 @@ - (void)viewDidDisappear:(BOOL)animated {
843
848
[super viewDidDisappear: animated];
844
849
}
845
850
851
+ - (void )viewWillTransitionToSize : (CGSize )size
852
+ withTransitionCoordinator : (id <UIViewControllerTransitionCoordinator>)coordinator {
853
+ [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
854
+
855
+ // We delay the viewport metrics update for half of rotation transition duration, to address
856
+ // a bug with distorted aspect ratio.
857
+ // See: https://github.com/flutter/flutter/issues/16322
858
+ //
859
+ // This approach does not fully resolve all distortion problem. But instead, it reduces the
860
+ // rotation distortion roughly from 4x to 2x. The most distorted frames occur in the middle
861
+ // of the transition when it is rotating the fastest, making it hard to notice.
862
+
863
+ NSTimeInterval transitionDuration = coordinator.transitionDuration ;
864
+ // Do not delay viewport metrics update if zero transition duration.
865
+ if (transitionDuration == 0 ) {
866
+ return ;
867
+ }
868
+
869
+ _shouldIgnoreViewportMetricsUpdatesDuringRotation = YES ;
870
+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW,
871
+ static_cast <int64_t >(transitionDuration / 2.0 * NSEC_PER_SEC)),
872
+ dispatch_get_main_queue (), ^{
873
+ // `viewWillTransitionToSize` is only called after the previous rotation is
874
+ // complete. So there won't be race condition for this flag.
875
+ _shouldIgnoreViewportMetricsUpdatesDuringRotation = NO ;
876
+ [self updateViewportMetricsIfNeeded ];
877
+ });
878
+ }
879
+
846
880
- (void )flushOngoingTouches {
847
881
if (_engine && _ongoingTouches.get ().count > 0 ) {
848
882
auto packet = std::make_unique<flutter::PointerDataPacket>(_ongoingTouches.get ().count );
@@ -1278,7 +1312,10 @@ - (void)pencilInteractionDidTap:(UIPencilInteraction*)interaction API_AVAILABLE(
1278
1312
1279
1313
#pragma mark - Handle view resizing
1280
1314
1281
- - (void )updateViewportMetrics {
1315
+ - (void )updateViewportMetricsIfNeeded {
1316
+ if (_shouldIgnoreViewportMetricsUpdatesDuringRotation) {
1317
+ return ;
1318
+ }
1282
1319
if ([_engine.get () viewController ] == self) {
1283
1320
[_engine.get () updateViewportMetrics: _viewportMetrics];
1284
1321
}
@@ -1295,11 +1332,9 @@ - (void)viewDidLayoutSubviews {
1295
1332
// First time since creation that the dimensions of its view is known.
1296
1333
bool firstViewBoundsUpdate = !_viewportMetrics.physical_width ;
1297
1334
_viewportMetrics.device_pixel_ratio = scale;
1298
- _viewportMetrics.physical_width = viewBounds.size .width * scale;
1299
- _viewportMetrics.physical_height = viewBounds.size .height * scale;
1300
-
1301
- [self updateViewportPadding ];
1302
- [self updateViewportMetrics ];
1335
+ [self setViewportMetricsSize ];
1336
+ [self setViewportMetricsPaddings ];
1337
+ [self updateViewportMetricsIfNeeded ];
1303
1338
1304
1339
// There is no guarantee that UIKit will layout subviews when the application is active. Creating
1305
1340
// the surface when inactive will cause GPU accesses from the background. Only wait for the first
@@ -1328,15 +1363,27 @@ - (void)viewDidLayoutSubviews {
1328
1363
}
1329
1364
1330
1365
- (void )viewSafeAreaInsetsDidChange {
1331
- [self updateViewportPadding ];
1332
- [self updateViewportMetrics ];
1366
+ [self setViewportMetricsPaddings ];
1367
+ [self updateViewportMetricsIfNeeded ];
1333
1368
[super viewSafeAreaInsetsDidChange ];
1334
1369
}
1335
1370
1336
- // Updates _viewportMetrics physical padding.
1371
+ // Set _viewportMetrics physical size.
1372
+ - (void )setViewportMetricsSize {
1373
+ // TODO(hellohuanlin): Use [self mainScreenIfViewLoaded] instead of [UIScreen mainScreen].
1374
+ // This requires adding the view to window during unit tests, which calls multiple engine calls
1375
+ // that is hard to mock since they take/return structs. An alternative approach is to partial mock
1376
+ // the FlutterViewController to make view controller life cycle methods no-op, and insert
1377
+ // this mock into the responder chain.
1378
+ CGFloat scale = [UIScreen mainScreen ].scale ;
1379
+ _viewportMetrics.physical_width = self.view .bounds .size .width * scale;
1380
+ _viewportMetrics.physical_height = self.view .bounds .size .height * scale;
1381
+ }
1382
+
1383
+ // Set _viewportMetrics physical paddings.
1337
1384
//
1338
- // Viewport padding represents the iOS safe area insets.
1339
- - (void )updateViewportPadding {
1385
+ // Viewport paddings represent the iOS safe area insets.
1386
+ - (void )setViewportMetricsPaddings {
1340
1387
CGFloat scale = [UIScreen mainScreen ].scale ;
1341
1388
_viewportMetrics.physical_padding_top = self.view .safeAreaInsets .top * scale;
1342
1389
_viewportMetrics.physical_padding_left = self.view .safeAreaInsets .left * scale;
@@ -1661,15 +1708,15 @@ - (void)setupKeyboardAnimationVsyncClient {
1661
1708
flutterViewController.get ()->_viewportMetrics .physical_view_inset_bottom =
1662
1709
flutterViewController.get ()
1663
1710
.keyboardAnimationView .layer .presentationLayer .frame .origin .y ;
1664
- [flutterViewController updateViewportMetrics ];
1711
+ [flutterViewController updateViewportMetricsIfNeeded ];
1665
1712
}
1666
1713
} else {
1667
1714
fml::TimeDelta timeElapsed = recorder.get ()->GetVsyncTargetTime () -
1668
1715
flutterViewController.get ().keyboardAnimationStartTime ;
1669
1716
1670
1717
flutterViewController.get ()->_viewportMetrics .physical_view_inset_bottom =
1671
1718
[[flutterViewController keyboardSpringAnimation ] curveFunction: timeElapsed.ToSecondsF ()];
1672
- [flutterViewController updateViewportMetrics ];
1719
+ [flutterViewController updateViewportMetricsIfNeeded ];
1673
1720
}
1674
1721
};
1675
1722
flutter::Shell& shell = [_engine.get () shell ];
@@ -1698,7 +1745,7 @@ - (void)ensureViewportMetricsIsCorrect {
1698
1745
if (_viewportMetrics.physical_view_inset_bottom != self.targetViewInsetBottom ) {
1699
1746
// Make sure the `physical_view_inset_bottom` is the target value.
1700
1747
_viewportMetrics.physical_view_inset_bottom = self.targetViewInsetBottom ;
1701
- [self updateViewportMetrics ];
1748
+ [self updateViewportMetricsIfNeeded ];
1702
1749
}
1703
1750
}
1704
1751
0 commit comments