Skip to content

Commit

Permalink
[AppBar] Fix AppBar from jumping when contentOffset updates when Voic…
Browse files Browse the repository at this point in the history
…eOver is on.

The problem occurs because the transform of the flexible header view's Y positioning based on the contentOffset is not done in coordination with the scroll view animation.

Similarly to the jumping effect in #9872 , we coordinate the transform when VoiceOver is on with the scroll view content offset change.

This was tested with the Expandable Cells example and the glitch is fixed.

PiperOrigin-RevId: 304352119
  • Loading branch information
yarneo authored and material-automation committed Apr 2, 2020
1 parent df52284 commit 13b6382
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 24 deletions.
9 changes: 5 additions & 4 deletions components/AppBar/examples/AppBarWithExpandableCells.swift
Expand Up @@ -19,10 +19,11 @@ import MaterialComponents.MaterialAppBar_Theming
import MaterialComponents.MaterialContainerScheme

// This example shows a bug when using an FlexibleHeader in a UITableViewController with expandable
// cells.
// 1. Expand a cell by tapping it.
// 2. Scroll that expanded cell above the FlexibleHeader.
// 3. Tap another cell to expand it and collapse the previously selected cell.
// cells. This occurs both when VoiceOver is on and off.
// 1. (Optional) Turn VoiceOver on
// 2. Expand a cell by tapping it.
// 3. Scroll that expanded cell above the FlexibleHeader.
// 4. Tap another cell to expand it and collapse the previously selected cell.
//
// With the buggy behavior, the FlexibleHeader will shift off-screen unexpectedly.

Expand Down
51 changes: 31 additions & 20 deletions components/FlexibleHeader/src/MDCFlexibleHeaderView.m
Expand Up @@ -1301,6 +1301,24 @@ - (void)fhv_contentOffsetDidChange {
self.transform = CGAffineTransformMakeTranslation(0, self.trackingScrollView.contentOffset.y);
};

CAAnimation *boundsAnimation = [self.trackingScrollView.layer animationForKey:@"bounds.origin"];

void (^updateTransformWithInFlightAnimation)(void) = ^{
// Check if there is an in-flight bounds animation and piggy-back its duration in order to
// avoid a jumping effect resulting from the transform otherwise updating instantly.
// This can happen if one of the cells positioned above the app bar shrinks in height, which
// causes the scroll view to animate its bounds origin in order to keep the scroll view's
// content from moving.
if (boundsAnimation) {
[UIView animateWithDuration:boundsAnimation.duration
animations:^{
updateTransform();
}];
} else {
updateTransform();
}
};

if (UIAccessibilityIsVoiceOverRunning()) {
// Clamp the offset to at least the max of -self.maximumHeight and the topContentInset.
// Accessibility may attempt to scroll to a lesser offset than this to pull the flexible
Expand All @@ -1313,29 +1331,22 @@ - (void)fhv_contentOffsetDidChange {
offset.y = MAX(offset.y, -(MAX(self.minMaxHeight.maximumHeightWithTopSafeArea,
scrollViewAdjustedContentInsetTop)));
[self fhv_setContentOffset:offset forTrackingScrollView:self.trackingScrollView];
// Setting the transform on the same run loop as the accessibility scroll can cause additional
// incorrect scrolling as the scrollview attempts to resolve to a position that will place
// the header in the center of the scroll. Punting to the next loop prevents this.
dispatch_async(dispatch_get_main_queue(), ^{
updateTransform();
[self fhv_updateLayout];
});
} else {
// Check if there is an in-flight bounds animation and piggy-back its duration in order to
// avoid a jumping effect resulting from the transform otherwise updating instantly.
// This can happen if one of the cells positioned above the app bar shrinks in height, which
// causes the scroll view to animate its bounds origin in order to keep the scroll view's
// content from moving.
CAAnimation *boundsAnimation =
[self.trackingScrollView.layer animationForKey:@"bounds.origin"];
if (boundsAnimation) {
[UIView animateWithDuration:boundsAnimation.duration
animations:^{
updateTransform();
}];
// The transform will piggy-back with the in-flight bounds
// animation in order to avoid a jumping effect.
updateTransformWithInFlightAnimation();
} else {
updateTransform();
// Setting the transform on the same run loop as the accessibility scroll can cause
// additional incorrect scrolling as the scrollview attempts to resolve to a position that
// will place the header in the center of the scroll. Punting to the next loop prevents
// this.
dispatch_async(dispatch_get_main_queue(), ^{
updateTransform();
[self fhv_updateLayout];
});
}
} else {
updateTransformWithInFlightAnimation();
}
}

Expand Down

0 comments on commit 13b6382

Please sign in to comment.