Skip to content

Commit a4526bc

Browse files
rnikefacebook-github-bot
authored andcommitted
fix: incorrect ScrollView offset on update (#30647)
Summary: This pull request is to fix #30258. After an investigation, I found out the scroll offset of the list seems to be calculated incorrectly due to a workaround in the source code. Instead of fixing the `calculateOffsetForContentSize`, I chose to remove it, the reason why I do so is because this workaround is for fixing the offset when `contentSize` is set manually, but according to the source code, there is no interface for a react-native user to set the `contentSize` of ScrollView, it is just set to `GCSizeZero` and will never be changed ([ref](https://github.com/facebook/react-native/pull/30647/files#diff-cf6f991f585ebf4cfdd555fe474e1f9ce40c2e4f823fc3f42b549414639c8c30L304)). Also I changed the function name from `updateContentOffsetIfNeeded` to `updateContentSizeIfNeeded` according what the function is actually doing. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Incorrect ScrollView offset on update Pull Request resolved: #30647 Test Plan: 1. Create a fresh new project named `testapp` using npx create-react-native-app 2. Apply example code to `testapp` from https://snack.expo.io/lokomass/flatlist 3. Run `testapp` on iOS emulator and reproduce the bug 4. Make changes to files in `testapp/node_modules/react-native/` 5. Rebuild `testapp` and run on iOS emulator again, the bug is no more exist 6. Apply changes from step 4 to react-native, make a pull request. #### Screenshots Before: The scroll offset is incorrect after children of FlatList has changed https://user-images.githubusercontent.com/48589760/103155130-b54a0580-47d7-11eb-97af-bdfd3e728714.mov After: No more incorrect scroll offset if children of FlatList has changed https://user-images.githubusercontent.com/48589760/103155091-6ef4a680-47d7-11eb-89fa-6f708bfef1c9.mov Reviewed By: sammy-SC Differential Revision: D25732958 Pulled By: shergin fbshipit-source-id: dac6eff15ac3bbfec502452ac14b3d49fee76c29
1 parent 0db56f1 commit a4526bc

File tree

3 files changed

+4
-68
lines changed

3 files changed

+4
-68
lines changed

React/Views/ScrollView/RCTScrollContentView.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ - (void)reactSetFrame:(CGRect)frame
2626

2727
RCTAssert([scrollView isKindOfClass:[RCTScrollView class]], @"Unexpected view hierarchy of RCTScrollView component.");
2828

29-
[scrollView updateContentOffsetIfNeeded];
29+
[scrollView updateContentSizeIfNeeded];
3030
}
3131

3232
@end

React/Views/ScrollView/RCTScrollView.h

+1-7
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@
2828
*/
2929
@property (nonatomic, readonly) UIView *contentView;
3030

31-
/**
32-
* If the `contentSize` is not specified (or is specified as {0, 0}, then the
33-
* `contentSize` will automatically be determined by the size of the subview.
34-
*/
35-
@property (nonatomic, assign) CGSize contentSize;
36-
3731
/**
3832
* The underlying scrollView (TODO: can we remove this?)
3933
*/
@@ -68,7 +62,7 @@
6862

6963
@interface RCTScrollView (Internal)
7064

71-
- (void)updateContentOffsetIfNeeded;
65+
- (void)updateContentSizeIfNeeded;
7266

7367
@end
7468

React/Views/ScrollView/RCTScrollView.m

+2-60
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,6 @@ - (instancetype)initWithEventDispatcher:(id<RCTEventDispatcherProtocol>)eventDis
301301

302302
_automaticallyAdjustContentInsets = YES;
303303
_contentInset = UIEdgeInsetsZero;
304-
_contentSize = CGSizeZero;
305304
_lastClippedToRect = CGRectNull;
306305

307306
_scrollEventThrottle = 0.0;
@@ -381,7 +380,7 @@ - (void)didUpdateReactSubviews
381380
- (void)didSetProps:(NSArray<NSString *> *)changedProps
382381
{
383382
if ([changedProps containsObject:@"contentSize"]) {
384-
[self updateContentOffsetIfNeeded];
383+
[self updateContentSizeIfNeeded];
385384
}
386385
}
387386

@@ -799,8 +798,6 @@ - (UIView *)viewForZoomingInScrollView:(__unused UIScrollView *)scrollView
799798
return _contentView;
800799
}
801800

802-
#pragma mark - Setters
803-
804801
- (CGSize)_calculateViewportSize
805802
{
806803
CGSize viewportSize = self.bounds.size;
@@ -813,71 +810,16 @@ - (CGSize)_calculateViewportSize
813810
return viewportSize;
814811
}
815812

816-
- (CGPoint)calculateOffsetForContentSize:(CGSize)newContentSize
817-
{
818-
CGPoint oldOffset = _scrollView.contentOffset;
819-
CGPoint newOffset = oldOffset;
820-
821-
CGSize oldContentSize = _scrollView.contentSize;
822-
CGSize viewportSize = [self _calculateViewportSize];
823-
824-
BOOL fitsinViewportY = oldContentSize.height <= viewportSize.height && newContentSize.height <= viewportSize.height;
825-
if (newContentSize.height < oldContentSize.height && !fitsinViewportY) {
826-
CGFloat offsetHeight = oldOffset.y + viewportSize.height;
827-
if (oldOffset.y < 0) {
828-
// overscrolled on top, leave offset alone
829-
} else if (offsetHeight > oldContentSize.height) {
830-
// overscrolled on the bottom, preserve overscroll amount
831-
newOffset.y = MAX(0, oldOffset.y - (oldContentSize.height - newContentSize.height));
832-
} else if (offsetHeight > newContentSize.height) {
833-
// offset falls outside of bounds, scroll back to end of list
834-
newOffset.y = MAX(0, newContentSize.height - viewportSize.height);
835-
}
836-
}
837-
838-
BOOL fitsinViewportX = oldContentSize.width <= viewportSize.width && newContentSize.width <= viewportSize.width;
839-
if (newContentSize.width < oldContentSize.width && !fitsinViewportX) {
840-
CGFloat offsetHeight = oldOffset.x + viewportSize.width;
841-
if (oldOffset.x < 0) {
842-
// overscrolled at the beginning, leave offset alone
843-
} else if (offsetHeight > oldContentSize.width && newContentSize.width > viewportSize.width) {
844-
// overscrolled at the end, preserve overscroll amount as much as possible
845-
newOffset.x = MAX(0, oldOffset.x - (oldContentSize.width - newContentSize.width));
846-
} else if (offsetHeight > newContentSize.width) {
847-
// offset falls outside of bounds, scroll back to end
848-
newOffset.x = MAX(0, newContentSize.width - viewportSize.width);
849-
}
850-
}
851-
852-
// all other cases, offset doesn't change
853-
return newOffset;
854-
}
855-
856-
/**
857-
* Once you set the `contentSize`, to a nonzero value, it is assumed to be
858-
* managed by you, and we'll never automatically compute the size for you,
859-
* unless you manually reset it back to {0, 0}
860-
*/
861813
- (CGSize)contentSize
862814
{
863-
if (!CGSizeEqualToSize(_contentSize, CGSizeZero)) {
864-
return _contentSize;
865-
}
866-
867815
return _contentView.frame.size;
868816
}
869817

870-
- (void)updateContentOffsetIfNeeded
818+
- (void)updateContentSizeIfNeeded
871819
{
872820
CGSize contentSize = self.contentSize;
873821
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
874-
// When contentSize is set manually, ScrollView internals will reset
875-
// contentOffset to {0, 0}. Since we potentially set contentSize whenever
876-
// anything in the ScrollView updates, we workaround this issue by manually
877-
// adjusting contentOffset whenever this happens
878-
CGPoint newOffset = [self calculateOffsetForContentSize:contentSize];
879822
_scrollView.contentSize = contentSize;
880-
_scrollView.contentOffset = newOffset;
881823
}
882824
}
883825

0 commit comments

Comments
 (0)