Skip to content

Commit

Permalink
fix(ios): optimize smoothness of nested scroll in special scenarios 3
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwcg committed Feb 28, 2025
1 parent ad13ad4 commit 74a626c
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,11 @@ static inline BOOL isScrollInSpringbackState(const UIScrollView *scrollview,
}

static inline bool isIntersect(const UIScrollView *outerScrollView, const UIScrollView *innerScrollView) {
CGRect frameInOuter = [outerScrollView convertRect:innerScrollView.frame fromView:(UIView *)innerScrollView];
return CGRectIntersectsRect(outerScrollView.bounds, frameInOuter);
CALayer *outerPresentation = outerScrollView.layer.presentationLayer;
CALayer *innerPresentation = innerScrollView.layer.presentationLayer;
CGRect actualOuter = [outerPresentation convertRect:outerPresentation.bounds toLayer:nil];
CGRect actualInner = [innerPresentation convertRect:innerPresentation.bounds toLayer:nil];
return CGRectIntersectsRect(actualOuter, actualInner);
}

static inline void lockScrollView(const UIScrollView<HippyNestedScrollProtocol> *scrollView) {
Expand All @@ -164,10 +167,19 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
BOOL isInner = (sv == innerScrollView);

// 0. Exclude irrelevant scroll events using `activeInnerScrollView`
if (outerScrollView.activeInnerScrollView &&
outerScrollView.activeInnerScrollView != innerScrollView) {
if (outerScrollView.activeInnerScrollView && outerScrollView.activeInnerScrollView != innerScrollView) {
HippyNSLogTrace(@"Not active inner return.");
return;
} else if (isOuter && !outerScrollView.activeInnerScrollView) {
if (outerScrollView.shouldHaveActiveInner) {
// 0.1 If outer should have an active innder but not, ignore.
HippyNSLogTrace(@"Not active inner return 2.");
return;
} else if (!isIntersect(outerScrollView, innerScrollView)) {
// 0.2 If the two ScrollViews do not intersect at all, ignore.
HippyNSLogTrace(@"Not Intersect return. %p", sv);
return;
}
}

// 1. Determine direction of scrolling
Expand All @@ -186,14 +198,6 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
return;
}

// 1.1 If the two ScrollViews do not intersect at all, ignore.
if (isOuter && !outerScrollView.activeInnerScrollView && !isIntersect(outerScrollView, innerScrollView)) {
HippyNSLogTrace(@"No Intersect return. %p", sv);
HippyNSLogTrace(@"outer bounds: %@, innerFrame in outer: %@", NSStringFromCGRect(outerScrollView.bounds),
NSStringFromCGRect([sv convertRect:innerScrollView.frame fromView:(UIView *)innerScrollView]));
return;
}

HippyNSLogTrace(@"start handle (%p) did scroll: %@", sv, NSStringFromCGPoint(sv.contentOffset));

// 2. Lock inner scrollview if necessary
Expand Down Expand Up @@ -274,7 +278,7 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (hasScrollToTheDirectionEdge(innerScrollView, direction)) {
self.shouldUnlockOuterScrollView = YES;
HippyNSLogTrace(@"set unlock outer ~");
} else {
} else if (outerScrollView.activeInnerScrollView) {
self.shouldUnlockOuterScrollView = NO;
HippyNSLogTrace(@"set lock outer !");
}
Expand Down Expand Up @@ -325,6 +329,13 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView {


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (!self.outerScrollView.shouldHaveActiveInner) {
// Clear any recorded activeInner or activeOuter if shouldHaveActiveInner is NO,
// this code is executed only if the scrollView is an outer.
self.outerScrollView.activeInnerScrollView = nil;
self.innerScrollView.activeOuterScrollView = nil;
}

if (scrollView == self.outerScrollView) {
self.shouldUnlockOuterScrollView = NO;
HippyNSLogTrace2(YES, @"reset outer scroll lock");
Expand All @@ -348,19 +359,13 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
self.dragType = HippyNestedScrollDragTypeUndefined;
if (!decelerate) {
// reset active scroll
self.outerScrollView.activeInnerScrollView = nil;
self.innerScrollView.activeOuterScrollView = nil;

// Reset shouldHaveActiveInner flag when user end dragging.
if (self.outerScrollView.shouldHaveActiveInner) {
self.outerScrollView.shouldHaveActiveInner = NO;
}
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// reset active scroll
self.outerScrollView.activeInnerScrollView = nil;
self.innerScrollView.activeOuterScrollView = nil;
}


#pragma mark - HippyNestedScrollGestureDelegate

Expand All @@ -380,6 +385,7 @@ - (BOOL)shouldRecognizeScrollGestureSimultaneouslyWithView:(UIView *)view {
self.nestedScrollBottomPriority > HippyNestedScrollPriorityNone ||
self.nestedScrollLeftPriority > HippyNestedScrollPriorityNone ||
self.nestedScrollRightPriority > HippyNestedScrollPriorityNone) {
self.outerScrollView.shouldHaveActiveInner = YES;
return YES;
}
} else if (self.outerScrollView.nestedGestureDelegate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#define HIPPY_NESTEDSCROLL_PROTOCOL_PROPERTY_IMP \
@synthesize lastContentOffset; \
@synthesize shouldHaveActiveInner; \
@synthesize activeInnerScrollView; \
@synthesize activeOuterScrollView; \
@synthesize nestedGestureDelegate; \
Expand All @@ -52,6 +53,11 @@
/// Record the last content offset for scroll lock.
@property (nonatomic, assign) CGPoint lastContentOffset;

/// A flag indicates that outer should have activeInner,
/// which is set during shouldRecognizeSimultaneously and reset during EndDragging.
/// Use it for unrelated rolling event filtering
@property (nonatomic, assign) BOOL shouldHaveActiveInner;

/// Record the current active inner scrollable view.
/// Used to judge the responder when outer has more than one inner scrollview.
@property (nonatomic, weak) UIScrollView<HippyNestedScrollProtocol> *activeInnerScrollView;
Expand Down

0 comments on commit 74a626c

Please sign in to comment.