@onekeyfe/react-native-pager-view 3.0.173 → 3.0.174
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +737 -25
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +12 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +16 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +2 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerPendingRestoreTest.kt +11 -0
- package/docs/SPEC.md +56 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +492 -29
- package/ios/RNCNativeScrollerComponentView.h +1 -0
- package/ios/RNCNativeScrollerComponentView.mm +48 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +293 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +14 -5
- package/lib/module/CollapsiblePagerView.js +6 -0
- package/lib/module/CollapsiblePagerView.web.js +81 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -0
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +11 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +1 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +18 -0
- package/src/CollapsiblePagerView.web.tsx +170 -36
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
|
@@ -1437,6 +1437,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1437
1437
|
@end
|
|
1438
1438
|
|
|
1439
1439
|
@implementation RNCCollapsiblePagerViewComponentView {
|
|
1440
|
+
#if DEBUG
|
|
1441
|
+
NSUInteger _offsetDiagSequence;
|
|
1442
|
+
#endif
|
|
1440
1443
|
UIView *_containerView;
|
|
1441
1444
|
RNCCollapsiblePagerSharedHeaderView *_sharedHeaderHostView;
|
|
1442
1445
|
UIPageViewController *_pageViewController;
|
|
@@ -1478,6 +1481,19 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1478
1481
|
NSArray<NSString *> *_pageKeys;
|
|
1479
1482
|
NSString *_retainedPages;
|
|
1480
1483
|
NSMutableDictionary<NSString *, NSNumber *> *_pageOffsets;
|
|
1484
|
+
NSMutableSet<NSString *> *_commandBodyKeys;
|
|
1485
|
+
__weak UIScrollView *_pagerTopScrollView;
|
|
1486
|
+
NSString *_pagerTopPageKey;
|
|
1487
|
+
NSUInteger _pagerTopGeneration;
|
|
1488
|
+
NSInteger _pagerTopPageIndex;
|
|
1489
|
+
CGFloat _pagerTopLogicalTarget;
|
|
1490
|
+
BOOL _dispatchingPagerTop;
|
|
1491
|
+
NSUInteger _pagerTopToken;
|
|
1492
|
+
NSMutableSet<NSString *> *_alignedBodyKeys;
|
|
1493
|
+
NSMutableSet<NSString *> *_suspendedBodyKeys;
|
|
1494
|
+
CGFloat _transitionHeaderOffset;
|
|
1495
|
+
CGFloat _transitionSourceDisplayBody;
|
|
1496
|
+
NSMutableDictionary<NSString *, NSNumber *> *_displayBodyOverrides;
|
|
1481
1497
|
NSMapTable<UIScrollView *, NSValue *> *_originalInsets;
|
|
1482
1498
|
NSMapTable<UIScrollView *, UIRefreshControl *> *_managedRefreshControls;
|
|
1483
1499
|
NSHashTable<UIScrollView *> *_refreshControlObservedScrollViews;
|
|
@@ -1491,6 +1507,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1491
1507
|
BOOL _needsScrollObserverReattachAfterMount;
|
|
1492
1508
|
NSHashTable<UIScrollView *> *_mountingRemovedScrollViews;
|
|
1493
1509
|
__weak UIScrollView *_observedScrollView;
|
|
1510
|
+
NSString *_observedPageKey;
|
|
1511
|
+
NSInteger _observedPageIndex;
|
|
1512
|
+
NSUInteger _observedPageGeneration;
|
|
1513
|
+
NSDictionary<NSString *, NSNumber *> *_transitionPageOffsets;
|
|
1514
|
+
NSMutableIndexSet *_transitionCompensatedPages;
|
|
1515
|
+
#if DEBUG
|
|
1516
|
+
NSInteger _transitionDiagBucket;
|
|
1517
|
+
#endif
|
|
1518
|
+
NSInteger _transitionSourceIndex;
|
|
1519
|
+
BOOL _syntheticPageRestore;
|
|
1520
|
+
BOOL _finishingPageStateTransition;
|
|
1521
|
+
NSMutableSet<NSString *> *_pendingPageRestoreKeys;
|
|
1494
1522
|
__weak UIScrollView *_sharedHeaderScrollView;
|
|
1495
1523
|
__weak UIScrollView *_pendingFallbackScrollView;
|
|
1496
1524
|
__weak UIView *_sharedHeaderPressCancellationGestureHost;
|
|
@@ -1525,6 +1553,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1525
1553
|
_logicalChildren = [NSMutableArray new];
|
|
1526
1554
|
_pageControllers = [NSMutableArray new];
|
|
1527
1555
|
_pageOffsets = [NSMutableDictionary new];
|
|
1556
|
+
_commandBodyKeys = [NSMutableSet new];
|
|
1557
|
+
_alignedBodyKeys = [NSMutableSet new];
|
|
1558
|
+
_suspendedBodyKeys = [NSMutableSet new];
|
|
1559
|
+
_displayBodyOverrides = [NSMutableDictionary new];
|
|
1560
|
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nativeListScrollCommandAccepted:)
|
|
1561
|
+
name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
|
|
1562
|
+
_pendingPageRestoreKeys = [NSMutableSet new];
|
|
1528
1563
|
_releasedPageScrollStates = [NSMutableDictionary new];
|
|
1529
1564
|
_mountingRemovedScrollViews = [NSHashTable weakObjectsHashTable];
|
|
1530
1565
|
_originalInsets = [NSMapTable weakToStrongObjectsMapTable];
|
|
@@ -1668,6 +1703,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1668
1703
|
|
|
1669
1704
|
- (void)dealloc
|
|
1670
1705
|
{
|
|
1706
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
|
|
1671
1707
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
1672
1708
|
[self updateNativeScrollerViewport:scrollView stickyHeight:0];
|
|
1673
1709
|
}
|
|
@@ -1899,18 +1935,124 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1899
1935
|
[self applyHeaderOffset];
|
|
1900
1936
|
}
|
|
1901
1937
|
|
|
1938
|
+
- (void)offsetDiag:(NSString *)reason page:(NSInteger)index scroll:(UIScrollView *)scroll
|
|
1939
|
+
{
|
|
1940
|
+
#if DEBUG
|
|
1941
|
+
NSString *key = index >= 0 && index < _pageControllers.count ? [self pageKeyForIndex:index] : @"none";
|
|
1942
|
+
if (![@[@"portfolio", @"perps", @"defi", @"nft", @"history"] containsObject:key]) key = @"other";
|
|
1943
|
+
NSNumber *saved = index >= 0 && index < _pageControllers.count ? _pageOffsets[[self pageKeyForIndex:index]] : nil;
|
|
1944
|
+
UIView *component = scroll == nil ? nil : RNCScrollComponentView(scroll);
|
|
1945
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1946
|
+
@"[HOME-OFFSET-DIAG] pager phase=%@ seq=%lu wallMs=%.0f mono=%.6f host=%p generation=%lu transitionId=%lu page=%ld key=%@ current=%ld destination=%ld transition=%d dragging=%d scroll=%p tag=%ld component=%p componentTag=%ld observed=%p ownerIndex=%ld ownerGeneration=%lu synthetic=%d cacheExists=%d cache=%.4f header=%.4f headerHeight=%ld physicalY=%.4f insetTop=%.4f insetBottom=%.4f logicalY=%.4f contentHeight=%.4f boundsHeight=%.4f",
|
|
1947
|
+
reason, (unsigned long)++_offsetDiagSequence, NSDate.date.timeIntervalSince1970 * 1000, CACurrentMediaTime(),
|
|
1948
|
+
(__bridge void *)self, (unsigned long)_generation, (unsigned long)_transitionId, (long)index, key,
|
|
1949
|
+
(long)_currentIndex, (long)_destinationIndex, _transitioning, _isPagerDragging,
|
|
1950
|
+
(__bridge void *)scroll, (long)scroll.tag, (__bridge void *)component, (long)component.tag,
|
|
1951
|
+
(__bridge void *)_observedScrollView, (long)_observedPageIndex, (unsigned long)_observedPageGeneration, _syntheticPageRestore, saved != nil, saved.doubleValue, _headerOffset, (long)_headerHeight,
|
|
1952
|
+
scroll.contentOffset.y, scroll.contentInset.top, scroll.contentInset.bottom,
|
|
1953
|
+
scroll.contentOffset.y + scroll.contentInset.top, scroll.contentSize.height, scroll.bounds.size.height]);
|
|
1954
|
+
#endif
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1902
1957
|
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
|
|
1903
1958
|
{
|
|
1904
|
-
|
|
1959
|
+
[self offsetDiag:@"prepare-check" page:pageIndex scroll:nil];
|
|
1960
|
+
if (!_nativeSmoothHeaderScrollEnabled ||
|
|
1905
1961
|
pageIndex < 0 || pageIndex >= _pageControllers.count) return;
|
|
1906
|
-
|
|
1907
|
-
_pageOffsets[[self pageKeyForIndex:pageIndex]] = @(_headerOffset);
|
|
1908
1962
|
UIScrollView *scrollView = [self verticalScrollViewForPageAtIndex:pageIndex];
|
|
1909
1963
|
if (scrollView != nil && scrollView != _observedScrollView) {
|
|
1910
1964
|
[self applyInsetsToScrollView:scrollView pageIndex:pageIndex restore:YES];
|
|
1911
1965
|
}
|
|
1912
1966
|
}
|
|
1913
1967
|
|
|
1968
|
+
- (void)beginPageStateTransition
|
|
1969
|
+
{
|
|
1970
|
+
[self cancelPagerTop];
|
|
1971
|
+
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0 || _transitionPageOffsets != nil) return;
|
|
1972
|
+
_transitionSourceDisplayBody = MAX(_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top - _headerHeight, 0);
|
|
1973
|
+
[self detachScrollObserver];
|
|
1974
|
+
_transitionSourceIndex = _currentIndex;
|
|
1975
|
+
_transitionHeaderOffset = _headerOffset;
|
|
1976
|
+
_transitionPageOffsets = [_pageOffsets copy];
|
|
1977
|
+
_transitionCompensatedPages = [NSMutableIndexSet new];
|
|
1978
|
+
#if DEBUG
|
|
1979
|
+
_transitionDiagBucket = -1;
|
|
1980
|
+
[self offsetDiag:[NSString stringWithFormat:@"screen-begin-source=%ld-savedQ=%.4f", (long)_transitionSourceIndex,
|
|
1981
|
+
[_transitionPageOffsets[self.currentPageKey] doubleValue]] page:_currentIndex scroll:nil];
|
|
1982
|
+
#endif
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
- (void)updatePageStateTransitionProgress:(CGFloat)progress
|
|
1986
|
+
{
|
|
1987
|
+
if (_transitionPageOffsets == nil || _finishingPageStateTransition) return;
|
|
1988
|
+
NSInteger first = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
|
|
1989
|
+
NSInteger second = MIN(first + 1, (NSInteger)_pageControllers.count - 1);
|
|
1990
|
+
CGFloat fraction = RNCClamp(progress - first, 0, 1);
|
|
1991
|
+
if (!_isPagerDragging && _destinationIndex != _transitionSourceIndex) {
|
|
1992
|
+
first = _transitionSourceIndex;
|
|
1993
|
+
second = _destinationIndex;
|
|
1994
|
+
fraction = RNCClamp((progress - first) / (second - first), 0, 1);
|
|
1995
|
+
}
|
|
1996
|
+
CGFloat firstQ = [_transitionPageOffsets[[self pageKeyForIndex:first]] doubleValue];
|
|
1997
|
+
CGFloat secondQ = [_transitionPageOffsets[[self pageKeyForIndex:second]] doubleValue];
|
|
1998
|
+
_headerOffset = _transitionHeaderOffset;
|
|
1999
|
+
#if DEBUG
|
|
2000
|
+
NSInteger bucket = (NSInteger)floor(fraction * 4);
|
|
2001
|
+
if (_transitionDiagBucket != bucket) {
|
|
2002
|
+
_transitionDiagBucket = bucket;
|
|
2003
|
+
[self offsetDiag:[NSString stringWithFormat:
|
|
2004
|
+
@"screen-progress-kind=%@-source=%ld-target=%ld-sourceQ=%.4f-targetQ=%.4f-position=%.4f-fraction=%.4f",
|
|
2005
|
+
_isPagerDragging ? @"interactive" : @"programmatic-endpoint", (long)first, (long)second,
|
|
2006
|
+
firstQ, secondQ, progress, fraction] page:_currentIndex scroll:nil];
|
|
2007
|
+
}
|
|
2008
|
+
#endif
|
|
2009
|
+
_syntheticPageRestore = YES;
|
|
2010
|
+
NSInteger visibleFirst = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
|
|
2011
|
+
NSInteger visibleLast = MIN(visibleFirst + 1, (NSInteger)_pageControllers.count - 1);
|
|
2012
|
+
NSMutableIndexSet *displayPages = [NSMutableIndexSet indexSetWithIndexesInRange:
|
|
2013
|
+
NSMakeRange(visibleFirst, visibleLast - visibleFirst + 1)];
|
|
2014
|
+
if (!_isPagerDragging) {
|
|
2015
|
+
// UIView tap animation reports its endpoint once while the source exits.
|
|
2016
|
+
[displayPages addIndex:_transitionSourceIndex];
|
|
2017
|
+
[displayPages addIndex:_destinationIndex];
|
|
2018
|
+
}
|
|
2019
|
+
for (NSUInteger index = displayPages.firstIndex; index != NSNotFound;
|
|
2020
|
+
index = [displayPages indexGreaterThanIndex:index]) {
|
|
2021
|
+
UIScrollView *scroll = [self verticalScrollViewForPageAtIndex:index];
|
|
2022
|
+
if (scroll != nil) [self applyInsetsToScrollView:scroll pageIndex:index restore:YES];
|
|
2023
|
+
}
|
|
2024
|
+
_syntheticPageRestore = NO;
|
|
2025
|
+
[self layoutSharedHeaders];
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
- (void)finishPageStateTransition
|
|
2029
|
+
{
|
|
2030
|
+
if (_transitionPageOffsets == nil) return;
|
|
2031
|
+
_finishingPageStateTransition = YES;
|
|
2032
|
+
_headerOffset = _transitionHeaderOffset;
|
|
2033
|
+
if (_currentIndex == _transitionSourceIndex) _displayBodyOverrides[self.currentPageKey] = @(_transitionSourceDisplayBody);
|
|
2034
|
+
else [_displayBodyOverrides removeObjectForKey:self.currentPageKey];
|
|
2035
|
+
if (_headerOffset >= _headerHeight) [_suspendedBodyKeys removeObject:self.currentPageKey];
|
|
2036
|
+
_syntheticPageRestore = YES;
|
|
2037
|
+
// Both completion and cancellation undo every temporary viewport offset.
|
|
2038
|
+
for (NSUInteger index = _transitionCompensatedPages.firstIndex; index != NSNotFound;
|
|
2039
|
+
index = [_transitionCompensatedPages indexGreaterThanIndex:index]) {
|
|
2040
|
+
UIScrollView *scroll = [self verticalScrollViewForPageAtIndex:index];
|
|
2041
|
+
if (scroll == nil) continue;
|
|
2042
|
+
[self applyInsetsToScrollView:scroll pageIndex:index restore:YES];
|
|
2043
|
+
|
|
2044
|
+
}
|
|
2045
|
+
_syntheticPageRestore = NO;
|
|
2046
|
+
_finishingPageStateTransition = NO;
|
|
2047
|
+
_transitionPageOffsets = nil;
|
|
2048
|
+
_transitionCompensatedPages = nil;
|
|
2049
|
+
[self layoutSharedHeaders];
|
|
2050
|
+
#if DEBUG
|
|
2051
|
+
[self offsetDiag:[NSString stringWithFormat:@"screen-finish-source=%ld-cancelled=%d", (long)_transitionSourceIndex,
|
|
2052
|
+
_currentIndex == _transitionSourceIndex] page:_currentIndex scroll:nil];
|
|
2053
|
+
#endif
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1914
2056
|
#pragma mark - React mounting
|
|
1915
2057
|
|
|
1916
2058
|
// OneKey patch: Fabric can recycle a removed list as a descendant of our header
|
|
@@ -1958,8 +2100,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1958
2100
|
_releasedPageScrollStates[pageKey] = state;
|
|
1959
2101
|
// Retained pages may be visually aligned to the expanded header while
|
|
1960
2102
|
// their saved offsets still hold the user's independent list position.
|
|
1961
|
-
|
|
2103
|
+
BOOL ownsObservedPage = scrollView == _observedScrollView &&
|
|
2104
|
+
(!_nativeSmoothHeaderScrollEnabled ||
|
|
2105
|
+
(_observedPageGeneration == _generation && _observedPageIndex == index &&
|
|
2106
|
+
[_observedPageKey isEqualToString:pageKey]));
|
|
2107
|
+
if (!_nativeSmoothHeaderScrollEnabled && _transitionPageOffsets == nil && !_syntheticPageRestore &&
|
|
2108
|
+
![_pendingPageRestoreKeys containsObject:pageKey] &&
|
|
2109
|
+
(ownsObservedPage || _pageOffsets[pageKey] == nil)) {
|
|
2110
|
+
[self offsetDiag:@"mount-release-save-before" page:index scroll:scrollView];
|
|
1962
2111
|
_pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2112
|
+
[self offsetDiag:@"mount-release-save-after" page:index scroll:scrollView];
|
|
1963
2113
|
}
|
|
1964
2114
|
break;
|
|
1965
2115
|
}
|
|
@@ -2243,7 +2393,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2243
2393
|
if (appliedTop != nil && scrollView.refreshControl != nil) {
|
|
2244
2394
|
original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
|
|
2245
2395
|
}
|
|
2246
|
-
|
|
2396
|
+
[self offsetDiag:@"release-original-inset-before" page:_currentIndex scroll:scrollView];
|
|
2397
|
+
scrollView.contentInset = original;
|
|
2398
|
+
[self offsetDiag:@"release-original-inset-after" page:_currentIndex scroll:scrollView];
|
|
2247
2399
|
}
|
|
2248
2400
|
NSValue *indicatorValue = [_originalIndicatorInsets objectForKey:scrollView];
|
|
2249
2401
|
if (indicatorValue != nil) {
|
|
@@ -2295,6 +2447,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2295
2447
|
_pagerScrollView = nil;
|
|
2296
2448
|
[super prepareForRecycle];
|
|
2297
2449
|
[_pageOffsets removeAllObjects];
|
|
2450
|
+
[_commandBodyKeys removeAllObjects];
|
|
2451
|
+
[_alignedBodyKeys removeAllObjects];
|
|
2452
|
+
[_suspendedBodyKeys removeAllObjects];
|
|
2453
|
+
[_displayBodyOverrides removeAllObjects];
|
|
2454
|
+
[_pendingPageRestoreKeys removeAllObjects];
|
|
2455
|
+
_transitionPageOffsets = nil;
|
|
2456
|
+
_transitionCompensatedPages = nil;
|
|
2457
|
+
_syntheticPageRestore = NO;
|
|
2458
|
+
_finishingPageStateTransition = NO;
|
|
2298
2459
|
_pageKeys = @[];
|
|
2299
2460
|
_retainedPages = @"[]";
|
|
2300
2461
|
_currentIndex = 0;
|
|
@@ -2627,6 +2788,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2627
2788
|
- (void)attachScrollObserverForCurrentPageAfterTransition:(BOOL)selectionSettled
|
|
2628
2789
|
{
|
|
2629
2790
|
if (_isBeingRecycled) return;
|
|
2791
|
+
if (_nativeSmoothHeaderScrollEnabled &&
|
|
2792
|
+
(_transitioning || _isPagerDragging || _transitionPageOffsets != nil) && !selectionSettled) return;
|
|
2793
|
+
if (selectionSettled) [self finishPageStateTransition];
|
|
2630
2794
|
if (_needsScrollObserverReattachAfterMount) {
|
|
2631
2795
|
if (self.window == nil || ((_transitioning || _isPagerDragging) && !selectionSettled)) return;
|
|
2632
2796
|
[self prepareAdjacentPageInsets];
|
|
@@ -2685,24 +2849,38 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2685
2849
|
if (_pagerScrollView != nil && !_nativeSmoothHeaderScrollEnabled && !_needsPropsReapply) {
|
|
2686
2850
|
[candidate.panGestureRecognizer requireGestureRecognizerToFail:_pagerScrollView.panGestureRecognizer];
|
|
2687
2851
|
}
|
|
2688
|
-
if (candidate == _observedScrollView
|
|
2852
|
+
if (candidate == _observedScrollView &&
|
|
2853
|
+
(!_nativeSmoothHeaderScrollEnabled ||
|
|
2854
|
+
([_observedPageKey isEqualToString:self.currentPageKey] &&
|
|
2855
|
+
_observedPageIndex == _currentIndex && _observedPageGeneration == _generation))) {
|
|
2689
2856
|
_needsScrollObserverReattachAfterMount = NO;
|
|
2857
|
+
if ([_pendingPageRestoreKeys containsObject:self.currentPageKey]) {
|
|
2858
|
+
[self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
|
|
2859
|
+
}
|
|
2690
2860
|
[self attachSharedHeadersToScrollView:candidate];
|
|
2691
2861
|
[self updateSharedHeaderPressCancellationGesture];
|
|
2692
2862
|
return;
|
|
2693
2863
|
}
|
|
2694
2864
|
|
|
2695
|
-
|
|
2865
|
+
[self offsetDiag:@"observer-candidate" page:_currentIndex scroll:candidate];
|
|
2866
|
+
BOOL replacingCurrentScrollView = _observingContentOffset &&
|
|
2867
|
+
(!_nativeSmoothHeaderScrollEnabled || [_observedPageKey isEqualToString:self.currentPageKey]);
|
|
2696
2868
|
[self detachScrollObserver];
|
|
2697
2869
|
if (replacingCurrentScrollView) {
|
|
2698
|
-
//
|
|
2699
|
-
|
|
2870
|
+
// Legacy replacement discards body state; smooth replacement waits for layout.
|
|
2871
|
+
[self offsetDiag:@"observer-replacement-cache-before" page:_currentIndex scroll:candidate];
|
|
2872
|
+
if (!_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
2873
|
+
[self offsetDiag:@"observer-replacement-cache-after" page:_currentIndex scroll:candidate];
|
|
2700
2874
|
}
|
|
2701
2875
|
_observedScrollView = candidate;
|
|
2876
|
+
_observedPageKey = [self.currentPageKey copy];
|
|
2877
|
+
_observedPageIndex = _currentIndex;
|
|
2878
|
+
_observedPageGeneration = _generation;
|
|
2879
|
+
[self offsetDiag:@"observer-attached-before-restore" page:_currentIndex scroll:candidate];
|
|
2702
2880
|
[self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
|
|
2703
2881
|
[candidate addObserver:self
|
|
2704
2882
|
forKeyPath:@"contentOffset"
|
|
2705
|
-
options:NSKeyValueObservingOptionNew
|
|
2883
|
+
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
|
2706
2884
|
context:RNCCollapsiblePagerContentOffsetContext];
|
|
2707
2885
|
[candidate addObserver:self
|
|
2708
2886
|
forKeyPath:@"contentInset"
|
|
@@ -2722,10 +2900,19 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2722
2900
|
|
|
2723
2901
|
- (void)detachScrollObserver
|
|
2724
2902
|
{
|
|
2903
|
+
[self cancelPagerTop];
|
|
2725
2904
|
UIScrollView *scrollView = _observedScrollView;
|
|
2726
2905
|
if (scrollView != nil) {
|
|
2727
2906
|
[scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
|
|
2728
|
-
|
|
2907
|
+
[self offsetDiag:@"detach-save-before" page:_currentIndex scroll:scrollView];
|
|
2908
|
+
NSString *ownerKey = _nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey;
|
|
2909
|
+
BOOL ownerKeyExists = ownerKey != nil && ([_pageKeys containsObject:ownerKey] ||
|
|
2910
|
+
(_observedPageIndex >= 0 && _observedPageIndex < _pageControllers.count &&
|
|
2911
|
+
[[self pageKeyForIndex:_observedPageIndex] isEqualToString:ownerKey]));
|
|
2912
|
+
if (ownerKey != nil && !_nativeSmoothHeaderScrollEnabled) {
|
|
2913
|
+
_pageOffsets[ownerKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2914
|
+
}
|
|
2915
|
+
[self offsetDiag:@"detach-save-after" page:_currentIndex scroll:scrollView];
|
|
2729
2916
|
if (_observingContentOffset) {
|
|
2730
2917
|
[scrollView removeObserver:self
|
|
2731
2918
|
forKeyPath:@"contentOffset"
|
|
@@ -2738,8 +2925,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2738
2925
|
context:RNCCollapsiblePagerContentOffsetContext];
|
|
2739
2926
|
}
|
|
2740
2927
|
}
|
|
2928
|
+
if (!_nativeSmoothHeaderScrollEnabled && _observedPageKey != nil) {
|
|
2929
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
2930
|
+
}
|
|
2741
2931
|
_observingContentOffset = NO;
|
|
2742
2932
|
_observedScrollView = nil;
|
|
2933
|
+
_observedPageKey = nil;
|
|
2934
|
+
_observedPageIndex = NSNotFound;
|
|
2743
2935
|
if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
|
|
2744
2936
|
[(RNCNativeScrollerComponentView *)scrollView.superview stopScrolling];
|
|
2745
2937
|
}
|
|
@@ -3010,7 +3202,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3010
3202
|
[self releaseRefreshControlForScrollView:scrollView];
|
|
3011
3203
|
if (scrollView == _observedScrollView) {
|
|
3012
3204
|
[self detachScrollObserver];
|
|
3013
|
-
|
|
3205
|
+
[self offsetDiag:resetSavedOffset ? @"release-reset-before" : @"release-retain-before" page:_currentIndex scroll:scrollView];
|
|
3206
|
+
if (resetSavedOffset && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
3207
|
+
[self offsetDiag:@"release-reset-after" page:_currentIndex scroll:scrollView];
|
|
3014
3208
|
}
|
|
3015
3209
|
if (scrollView == _sharedHeaderScrollView) {
|
|
3016
3210
|
[self restoreSharedHeadersToContainer];
|
|
@@ -3030,8 +3224,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3030
3224
|
[_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
|
|
3031
3225
|
scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
|
|
3032
3226
|
[_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
|
|
3227
|
+
[self offsetDiag:@"release-original-offset-before" page:_currentIndex scroll:scrollView];
|
|
3033
3228
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
|
|
3034
3229
|
animated:NO];
|
|
3230
|
+
[self offsetDiag:@"release-original-offset-after" page:_currentIndex scroll:scrollView];
|
|
3035
3231
|
[_originalInsets removeObjectForKey:scrollView];
|
|
3036
3232
|
[_appliedTopInsets removeObjectForKey:scrollView];
|
|
3037
3233
|
[_originalIndicatorInsets removeObjectForKey:scrollView];
|
|
@@ -3060,8 +3256,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3060
3256
|
pageIndex:(NSInteger)pageIndex
|
|
3061
3257
|
restore:(BOOL)restore
|
|
3062
3258
|
{
|
|
3063
|
-
//
|
|
3064
|
-
//
|
|
3259
|
+
// Smooth pages retain their complete offset while replacement identity and
|
|
3260
|
+
// layout settle. Legacy pages retain the historical header-only policy.
|
|
3261
|
+
[self offsetDiag:restore ? @"restore-enter" : @"insets-enter" page:pageIndex scroll:scrollView];
|
|
3065
3262
|
NSString *pageKey = [self pageKeyForIndex:pageIndex];
|
|
3066
3263
|
RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
|
|
3067
3264
|
BOOL deferredReleasedIdentity = NO;
|
|
@@ -3107,8 +3304,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3107
3304
|
deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
|
|
3108
3305
|
RNCCollapsiblePagerFallbackDebounceInterval;
|
|
3109
3306
|
}
|
|
3307
|
+
#if DEBUG
|
|
3308
|
+
[self offsetDiag:[NSString stringWithFormat:@"released-identity-match=%d-deferred=%d-oldTag=%ld-oldScroll=%p", sameList, deferredReleasedIdentity, (long)released.componentTag, (__bridge void *)released.scrollView] page:pageIndex scroll:scrollView];
|
|
3309
|
+
#endif
|
|
3110
3310
|
if (!deferredReleasedIdentity) {
|
|
3111
|
-
|
|
3311
|
+
[self offsetDiag:@"released-identity-cache-before" page:pageIndex scroll:scrollView];
|
|
3312
|
+
if (!sameList && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[pageKey] = @(_headerOffset);
|
|
3313
|
+
[self offsetDiag:@"released-identity-cache-after" page:pageIndex scroll:scrollView];
|
|
3112
3314
|
[_releasedPageScrollStates removeObjectForKey:pageKey];
|
|
3113
3315
|
}
|
|
3114
3316
|
}
|
|
@@ -3156,29 +3358,60 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3156
3358
|
if (pageIndex == _currentIndex) [self updateHeaderForScrollView:scrollView];
|
|
3157
3359
|
return;
|
|
3158
3360
|
}
|
|
3361
|
+
BOOL previousSyntheticRestore = _syntheticPageRestore;
|
|
3362
|
+
if (_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:pageKey]) _syntheticPageRestore = YES;
|
|
3363
|
+
[self offsetDiag:@"inset-assignment-before" page:pageIndex scroll:scrollView];
|
|
3159
3364
|
scrollView.contentInset = next;
|
|
3365
|
+
[self offsetDiag:@"inset-assignment-after" page:pageIndex scroll:scrollView];
|
|
3160
3366
|
UIEdgeInsets indicatorInsets = scrollView.verticalScrollIndicatorInsets;
|
|
3161
3367
|
indicatorInsets.top = pagerTopInset;
|
|
3162
3368
|
scrollView.verticalScrollIndicatorInsets = indicatorInsets;
|
|
3163
3369
|
scrollView.alwaysBounceVertical = YES;
|
|
3164
3370
|
|
|
3165
3371
|
CGFloat targetOffset = previousLogicalOffset - next.top;
|
|
3372
|
+
CGFloat intendedLogicalOffset = previousLogicalOffset;
|
|
3166
3373
|
if (restore) {
|
|
3167
|
-
NSNumber *saved =
|
|
3374
|
+
NSNumber *saved = _transitionPageOffsets != nil
|
|
3375
|
+
? _transitionPageOffsets[pageKey] : _pageOffsets[pageKey];
|
|
3168
3376
|
// The sticky slot can change height between pages. Cache offsets relative
|
|
3169
3377
|
// to content start so a round trip does not add that height difference.
|
|
3170
|
-
CGFloat restoredLogicalOffset
|
|
3171
|
-
|
|
3172
|
-
:
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3378
|
+
CGFloat restoredLogicalOffset;
|
|
3379
|
+
if (_nativeSmoothHeaderScrollEnabled && _headerHeight > 0) {
|
|
3380
|
+
[_alignedBodyKeys addObject:pageKey];
|
|
3381
|
+
if (_headerOffset < _headerHeight && saved.doubleValue > 0) [_suspendedBodyKeys addObject:pageKey];
|
|
3382
|
+
CGFloat body = saved.doubleValue;
|
|
3383
|
+
if (_transitionPageOffsets != nil && pageIndex == _transitionSourceIndex) body = _transitionSourceDisplayBody;
|
|
3384
|
+
else if (_transitionPageOffsets != nil) [_displayBodyOverrides removeObjectForKey:pageKey];
|
|
3385
|
+
else if (_displayBodyOverrides[pageKey] != nil) body = _displayBodyOverrides[pageKey].doubleValue;
|
|
3386
|
+
restoredLogicalOffset = _headerOffset + (_headerOffset >= _headerHeight ? body : 0);
|
|
3387
|
+
if (_transitionPageOffsets != nil) [_transitionCompensatedPages addIndex:pageIndex];
|
|
3388
|
+
if (deferredReleasedIdentity || scrollView.bounds.size.height <= 0 || scrollView.contentSize.height <= 0) {
|
|
3389
|
+
[_pendingPageRestoreKeys addObject:pageKey];
|
|
3390
|
+
_syntheticPageRestore = previousSyntheticRestore;
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
[_pendingPageRestoreKeys removeObject:pageKey];
|
|
3394
|
+
} else {
|
|
3395
|
+
restoredLogicalOffset = deferredReleasedIdentity || saved == nil ? _headerOffset : saved.doubleValue;
|
|
3396
|
+
}
|
|
3397
|
+
#if DEBUG
|
|
3398
|
+
[self offsetDiag:[NSString stringWithFormat:@"restore-candidate-saved=%.4f-headerAlignedLogical=%.4f", saved.doubleValue, restoredLogicalOffset] page:pageIndex scroll:scrollView];
|
|
3399
|
+
#endif
|
|
3400
|
+
intendedLogicalOffset = restoredLogicalOffset;
|
|
3401
|
+
// Cancellation restores the visible source, while retaining its unreachable reading intent.
|
|
3402
|
+
if (_nativeSmoothHeaderScrollEnabled && _headerOffset >= _headerHeight &&
|
|
3403
|
+
_displayBodyOverrides[pageKey] != nil) {
|
|
3404
|
+
intendedLogicalOffset = MAX(intendedLogicalOffset, _headerOffset + _pageOffsets[pageKey].doubleValue);
|
|
3177
3405
|
}
|
|
3178
3406
|
targetOffset = restoredLogicalOffset - next.top;
|
|
3179
|
-
|
|
3407
|
+
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0) {
|
|
3408
|
+
targetOffset = MAX(targetOffset, -next.top + _headerOffset);
|
|
3409
|
+
}
|
|
3180
3410
|
// An inactive list may shrink after its offset observer was detached.
|
|
3181
3411
|
// Do not overwrite its corrected offset with a now-unreachable cached one.
|
|
3412
|
+
#if DEBUG
|
|
3413
|
+
[self offsetDiag:[NSString stringWithFormat:@"restore-clamp-before-candidate=%.4f-max=%.4f", targetOffset, MAX(-next.top, scrollView.contentSize.height - scrollView.bounds.size.height + next.bottom)] page:pageIndex scroll:scrollView];
|
|
3414
|
+
#endif
|
|
3182
3415
|
targetOffset = RNCLimitRestoredScrollOffset(
|
|
3183
3416
|
targetOffset,
|
|
3184
3417
|
scrollView.contentSize.height,
|
|
@@ -3187,11 +3420,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3187
3420
|
next.bottom
|
|
3188
3421
|
);
|
|
3189
3422
|
}
|
|
3423
|
+
#if DEBUG
|
|
3424
|
+
[self offsetDiag:[NSString stringWithFormat:@"restore-final-target=%.4f-restore=%d", targetOffset, restore] page:pageIndex scroll:scrollView];
|
|
3425
|
+
#endif
|
|
3190
3426
|
// A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
|
|
3191
3427
|
if (restore || previousTopInset != next.top) {
|
|
3192
3428
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, targetOffset) animated:NO];
|
|
3193
3429
|
}
|
|
3194
|
-
if (
|
|
3430
|
+
if (_nativeSmoothHeaderScrollEnabled && restore && targetOffset + next.top + 0.5 < intendedLogicalOffset) {
|
|
3431
|
+
[_pendingPageRestoreKeys addObject:pageKey];
|
|
3432
|
+
}
|
|
3433
|
+
_syntheticPageRestore = previousSyntheticRestore;
|
|
3434
|
+
if (pageIndex == _currentIndex) {
|
|
3435
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3436
|
+
if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:scrollView];
|
|
3437
|
+
else [self applyHeaderOffset];
|
|
3438
|
+
}
|
|
3439
|
+
[self offsetDiag:@"restore-post" page:pageIndex scroll:scrollView];
|
|
3195
3440
|
}
|
|
3196
3441
|
|
|
3197
3442
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
@@ -3201,6 +3446,71 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3201
3446
|
}
|
|
3202
3447
|
}
|
|
3203
3448
|
|
|
3449
|
+
- (void)nativeListScrollCommandAccepted:(NSNotification *)notification
|
|
3450
|
+
{
|
|
3451
|
+
UIScrollView *scrollView = notification.object;
|
|
3452
|
+
if (scrollView != _observedScrollView ||
|
|
3453
|
+
_observedPageKey == nil || ![_observedPageKey isEqualToString:self.currentPageKey] ||
|
|
3454
|
+
_observedPageIndex != _currentIndex || _observedPageGeneration != _generation ||
|
|
3455
|
+
_transitioning || _isPagerDragging || _transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
3456
|
+
NSInteger phase = [notification.userInfo[@"phase"] integerValue];
|
|
3457
|
+
if ([self matchesPagerTop:scrollView]) {
|
|
3458
|
+
if (phase == 0) [self cancelPagerTop];
|
|
3459
|
+
else return;
|
|
3460
|
+
}
|
|
3461
|
+
if (phase == 0) [_alignedBodyKeys removeObject:_observedPageKey];
|
|
3462
|
+
if (!_nativeSmoothHeaderScrollEnabled) return;
|
|
3463
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3464
|
+
if (phase == 1) { [_commandBodyKeys addObject:_observedPageKey]; return; }
|
|
3465
|
+
if (phase == 2) {
|
|
3466
|
+
if (![_commandBodyKeys containsObject:_observedPageKey]) return;
|
|
3467
|
+
_pageOffsets[_observedPageKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3468
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3469
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3470
|
+
return;
|
|
3471
|
+
}
|
|
3472
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3473
|
+
[_pendingPageRestoreKeys removeObject:_observedPageKey];
|
|
3474
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3475
|
+
[_suspendedBodyKeys removeObject:_observedPageKey];
|
|
3476
|
+
[_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3477
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3478
|
+
BOOL explicitTop = [notification.userInfo[@"explicitTop"] boolValue];
|
|
3479
|
+
_pageOffsets[_observedPageKey] = @(explicitTop ? 0 : MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3480
|
+
if (explicitTop) {
|
|
3481
|
+
_headerOffset = 0;
|
|
3482
|
+
[self applyHeaderOffset];
|
|
3483
|
+
}
|
|
3484
|
+
[self offsetDiag:@"list-command-body-takeover" page:_currentIndex scroll:scrollView];
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
- (void)nativeScrollerWillReceiveScrollCommand:(RNCNativeScrollerComponentView *)scroller
|
|
3488
|
+
{
|
|
3489
|
+
UIScrollView *scrollView = scroller.scrollView;
|
|
3490
|
+
NSString *ownerKey = _observedPageKey;
|
|
3491
|
+
if (scrollView != _observedScrollView ||
|
|
3492
|
+
ownerKey == nil || ![ownerKey isEqualToString:self.currentPageKey] ||
|
|
3493
|
+
_observedPageIndex != _currentIndex || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
|
|
3494
|
+
_observedPageGeneration != _generation || _transitioning || _isPagerDragging ||
|
|
3495
|
+
_transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
3496
|
+
if (!_dispatchingPagerTop) {
|
|
3497
|
+
[self cancelPagerTop];
|
|
3498
|
+
[_alignedBodyKeys removeObject:ownerKey];
|
|
3499
|
+
}
|
|
3500
|
+
if (!_nativeSmoothHeaderScrollEnabled) return;
|
|
3501
|
+
[self offsetDiag:@"command-pending-cancel-before" page:_observedPageIndex scroll:scrollView];
|
|
3502
|
+
[_pendingPageRestoreKeys removeObject:ownerKey];
|
|
3503
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3504
|
+
[_commandBodyKeys removeObject:ownerKey];
|
|
3505
|
+
[_alignedBodyKeys removeObject:ownerKey];
|
|
3506
|
+
[_suspendedBodyKeys removeObject:ownerKey];
|
|
3507
|
+
[_displayBodyOverrides removeObjectForKey:ownerKey];
|
|
3508
|
+
_pageOffsets[ownerKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3509
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3510
|
+
[self offsetDiag:@"command-pending-cancel-after" page:_observedPageIndex scroll:scrollView];
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
|
|
3204
3514
|
- (void)nativeScroller:(RNCNativeScrollerComponentView *)scroller
|
|
3205
3515
|
callerInsetChangedFrom:(UIEdgeInsets)previous to:(UIEdgeInsets)next
|
|
3206
3516
|
{
|
|
@@ -3252,25 +3562,86 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3252
3562
|
if (context == RNCScrollRefreshControlContext) {
|
|
3253
3563
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
3254
3564
|
[self updateRefreshControlForScrollView:scrollView];
|
|
3255
|
-
if (scrollView == _observedScrollView
|
|
3565
|
+
if (scrollView == _observedScrollView && !_syntheticPageRestore &&
|
|
3566
|
+
(!_nativeSmoothHeaderScrollEnabled || (!_transitioning && _transitionPageOffsets == nil))) {
|
|
3567
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3568
|
+
}
|
|
3256
3569
|
return;
|
|
3257
3570
|
}
|
|
3258
3571
|
if (context == RNCCollapsiblePagerContentOffsetContext && object == _observedScrollView) {
|
|
3572
|
+
if ((_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:_observedPageKey]) &&
|
|
3573
|
+
(_observedPageGeneration != _generation || _observedPageKey == nil ||
|
|
3574
|
+
_observedPageIndex != _currentIndex || ![_observedPageKey isEqualToString:self.currentPageKey] ||
|
|
3575
|
+
_transitioning || _isPagerDragging ||
|
|
3576
|
+
_transitionPageOffsets != nil || _syntheticPageRestore)) return;
|
|
3577
|
+
UIScrollView *ownedScroll = (UIScrollView *)object;
|
|
3578
|
+
NSValue *oldPoint = change[NSKeyValueChangeOldKey];
|
|
3579
|
+
NSValue *newPoint = change[NSKeyValueChangeNewKey];
|
|
3580
|
+
if ([keyPath isEqualToString:@"contentOffset"] && [self matchesPagerTop:ownedScroll] && ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3581
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3582
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3583
|
+
CGPoint interruptedOffset = ownedScroll.contentOffset;
|
|
3584
|
+
[self cancelPagerTop];
|
|
3585
|
+
[ownedScroll setContentOffset:interruptedOffset animated:NO];
|
|
3586
|
+
}
|
|
3587
|
+
if ([keyPath isEqualToString:@"contentOffset"] &&
|
|
3588
|
+
ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3589
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3590
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3591
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3592
|
+
}
|
|
3593
|
+
if (_nativeSmoothHeaderScrollEnabled && [keyPath isEqualToString:@"contentOffset"] &&
|
|
3594
|
+
ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3595
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3596
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3597
|
+
[self cancelPagerTop];
|
|
3598
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3599
|
+
[_pendingPageRestoreKeys removeObject:_observedPageKey];
|
|
3600
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3601
|
+
[_suspendedBodyKeys removeObject:_observedPageKey];
|
|
3602
|
+
[_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3603
|
+
}
|
|
3604
|
+
if (_nativeSmoothHeaderScrollEnabled && [_pendingPageRestoreKeys containsObject:_observedPageKey]) {
|
|
3605
|
+
if ([keyPath isEqualToString:@"contentSize"] &&
|
|
3606
|
+
![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
|
|
3607
|
+
if (_headerOffset >= _headerHeight) [_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3608
|
+
[self applyInsetsToScrollView:_observedScrollView pageIndex:_observedPageIndex restore:YES];
|
|
3609
|
+
}
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3259
3612
|
if ([keyPath isEqualToString:@"contentInset"]) {
|
|
3260
|
-
[self updateHeaderForScrollView:(UIScrollView *)object];
|
|
3613
|
+
if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:(UIScrollView *)object];
|
|
3261
3614
|
return;
|
|
3262
3615
|
}
|
|
3263
3616
|
if ([keyPath isEqualToString:@"contentSize"]) {
|
|
3264
3617
|
NSValue *oldSize = change[NSKeyValueChangeOldKey];
|
|
3265
3618
|
NSValue *newSize = change[NSKeyValueChangeNewKey];
|
|
3266
3619
|
if (![oldSize isEqual:newSize]) {
|
|
3267
|
-
|
|
3620
|
+
BOOL restoreAlignedBody =
|
|
3621
|
+
_observedPageGeneration == _generation && _observedPageIndex == _currentIndex &&
|
|
3622
|
+
_observedPageKey != nil && [_observedPageKey isEqualToString:self.currentPageKey] &&
|
|
3623
|
+
[_alignedBodyKeys containsObject:_observedPageKey] &&
|
|
3624
|
+
![_commandBodyKeys containsObject:_observedPageKey] &&
|
|
3625
|
+
_observedScrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
|
|
3626
|
+
!_observedScrollView.isDragging && !_observedScrollView.isDecelerating;
|
|
3627
|
+
[self applyInsetsToScrollView:_observedScrollView pageIndex:_currentIndex restore:restoreAlignedBody];
|
|
3268
3628
|
}
|
|
3269
3629
|
return;
|
|
3270
3630
|
}
|
|
3271
3631
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
3272
3632
|
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3273
|
-
|
|
3633
|
+
#if DEBUG
|
|
3634
|
+
BOOL offsetDiagChanged = _pageOffsets[self.currentPageKey] == nil || _pageOffsets[self.currentPageKey].doubleValue != (_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3635
|
+
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-before" page:_currentIndex scroll:scrollView];
|
|
3636
|
+
#endif
|
|
3637
|
+
if ([_alignedBodyKeys containsObject:_observedPageKey] ||
|
|
3638
|
+
(_nativeSmoothHeaderScrollEnabled && scrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
|
|
3639
|
+
!scrollView.isDecelerating && ![_commandBodyKeys containsObject:_observedPageKey])) return;
|
|
3640
|
+
_pageOffsets[_nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey] =
|
|
3641
|
+
@(_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3642
|
+
#if DEBUG
|
|
3643
|
+
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-after" page:_currentIndex scroll:scrollView];
|
|
3644
|
+
#endif
|
|
3274
3645
|
[self updateHeaderForScrollView:scrollView];
|
|
3275
3646
|
return;
|
|
3276
3647
|
}
|
|
@@ -3279,6 +3650,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3279
3650
|
|
|
3280
3651
|
- (void)updateHeaderForScrollView:(UIScrollView *)scrollView
|
|
3281
3652
|
{
|
|
3653
|
+
if ([self matchesPagerTop:scrollView]) {
|
|
3654
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3655
|
+
_headerOffset = _pagerTopLogicalTarget;
|
|
3656
|
+
[self applyHeaderOffset];
|
|
3657
|
+
[self finishPagerTopIfReached];
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
if ([_alignedBodyKeys containsObject:self.currentPageKey] ||
|
|
3661
|
+
(_nativeSmoothHeaderScrollEnabled && (_syntheticPageRestore || _transitionPageOffsets != nil ||
|
|
3662
|
+
[_pendingPageRestoreKeys containsObject:self.currentPageKey]))) return;
|
|
3282
3663
|
CGFloat logicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3283
3664
|
_currentLogicalOffset = logicalOffset;
|
|
3284
3665
|
_headerOffset = MIN(MAX(logicalOffset, 0), _headerHeight);
|
|
@@ -3322,6 +3703,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3322
3703
|
|
|
3323
3704
|
- (void)goTo:(NSInteger)index animated:(BOOL)animated
|
|
3324
3705
|
{
|
|
3706
|
+
[self offsetDiag:@"go-to" page:index scroll:_observedScrollView];
|
|
3325
3707
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
3326
3708
|
_hasReceivedPageCommand = YES;
|
|
3327
3709
|
_hasAppliedInitialPage = YES;
|
|
@@ -3347,6 +3729,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3347
3729
|
return;
|
|
3348
3730
|
}
|
|
3349
3731
|
|
|
3732
|
+
[self beginPageStateTransition];
|
|
3350
3733
|
[self preparePageForHorizontalTransitionAtIndex:index];
|
|
3351
3734
|
_destinationIndex = index;
|
|
3352
3735
|
[self liftSharedHeadersForPagerTransition:@"programmatic"];
|
|
@@ -3366,6 +3749,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3366
3749
|
void (^animations)(void) = ^{
|
|
3367
3750
|
[self->_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0)];
|
|
3368
3751
|
[self->_nativeTabBarView setProgress:index];
|
|
3752
|
+
[self updatePageStateTransitionProgress:index];
|
|
3369
3753
|
};
|
|
3370
3754
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
|
3371
3755
|
__strong __typeof__(weakSelf) self = weakSelf;
|
|
@@ -3373,6 +3757,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3373
3757
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
3374
3758
|
self->_pagerScrollView.scrollEnabled = self->_scrollEnabled;
|
|
3375
3759
|
if (finished) {
|
|
3760
|
+
[self detachScrollObserver];
|
|
3376
3761
|
self->_currentIndex = index;
|
|
3377
3762
|
self->_destinationIndex = index;
|
|
3378
3763
|
[self->_nativeTabBarView setProgress:index];
|
|
@@ -3410,6 +3795,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3410
3795
|
if (self == nil || self->_generation != capturedGeneration ||
|
|
3411
3796
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
3412
3797
|
if (finished) {
|
|
3798
|
+
[self detachScrollObserver];
|
|
3413
3799
|
self->_currentIndex = index;
|
|
3414
3800
|
self->_destinationIndex = index;
|
|
3415
3801
|
[self->_nativeTabBarView setProgress:index];
|
|
@@ -3434,6 +3820,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3434
3820
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
3435
3821
|
if (self->_generation != generation || self->_transitionId != transition ||
|
|
3436
3822
|
self->_isBeingRecycled) return;
|
|
3823
|
+
[self finishPageStateTransition];
|
|
3437
3824
|
self->_transitioning = NO;
|
|
3438
3825
|
if (self->_hasPendingDirectNativePagerChange) {
|
|
3439
3826
|
BOOL enabled = self->_pendingDirectNativePagerEnabled;
|
|
@@ -3497,9 +3884,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3497
3884
|
|
|
3498
3885
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
3499
3886
|
{
|
|
3887
|
+
[self offsetDiag:@"pager-drag-begin" page:_currentIndex scroll:_observedScrollView];
|
|
3500
3888
|
if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
|
|
3501
3889
|
[_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
|
|
3502
3890
|
}
|
|
3891
|
+
[self beginPageStateTransition];
|
|
3503
3892
|
if (_directNativePagerEnabled) {
|
|
3504
3893
|
[self preparePageForHorizontalTransitionAtIndex:_currentIndex - 1];
|
|
3505
3894
|
[self preparePageForHorizontalTransitionAtIndex:_currentIndex + 1];
|
|
@@ -3651,6 +4040,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3651
4040
|
NSInteger position = (NSInteger)floor(progress);
|
|
3652
4041
|
CGFloat offset = progress - position;
|
|
3653
4042
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
4043
|
+
[self updatePageStateTransitionProgress:progress];
|
|
3654
4044
|
[_nativeTabBarView setProgress:progress];
|
|
3655
4045
|
const auto emitter = [self eventEmitter];
|
|
3656
4046
|
if (emitter) {
|
|
@@ -3668,6 +4058,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3668
4058
|
CGFloat transitionProgress = RNCClamp(fabs(rawOffset), 0, 1);
|
|
3669
4059
|
tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
|
|
3670
4060
|
}
|
|
4061
|
+
[self updatePageStateTransitionProgress:tabProgress];
|
|
3671
4062
|
[_nativeTabBarView setProgress:tabProgress];
|
|
3672
4063
|
const auto emitter = [self eventEmitter];
|
|
3673
4064
|
if (emitter) {
|
|
@@ -3685,6 +4076,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3685
4076
|
|
|
3686
4077
|
- (void)emitPageSelected:(NSInteger)position
|
|
3687
4078
|
{
|
|
4079
|
+
[self offsetDiag:@"selection-complete" page:position scroll:_observedScrollView];
|
|
3688
4080
|
const auto emitter = [self eventEmitter];
|
|
3689
4081
|
if (emitter) emitter->onPageSelected({.position = (double)position});
|
|
3690
4082
|
}
|
|
@@ -3713,6 +4105,77 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3713
4105
|
RCTRNCCollapsiblePagerViewHandleCommand(self, commandName, args);
|
|
3714
4106
|
}
|
|
3715
4107
|
|
|
4108
|
+
- (BOOL)matchesPagerTop:(UIScrollView *)scrollView
|
|
4109
|
+
{
|
|
4110
|
+
return scrollView != nil && scrollView == _pagerTopScrollView && scrollView == _observedScrollView &&
|
|
4111
|
+
_pagerTopPageKey != nil && [_pagerTopPageKey isEqualToString:self.currentPageKey] &&
|
|
4112
|
+
[_observedPageKey isEqualToString:_pagerTopPageKey] && _pagerTopPageIndex == _currentIndex &&
|
|
4113
|
+
_observedPageIndex == _currentIndex && _pagerTopGeneration == _generation &&
|
|
4114
|
+
_observedPageGeneration == _generation && scrollView.window != nil;
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
- (void)cancelPagerTop
|
|
4118
|
+
{
|
|
4119
|
+
if (_pagerTopPageKey != nil) [_commandBodyKeys removeObject:_pagerTopPageKey];
|
|
4120
|
+
_pagerTopScrollView = nil;
|
|
4121
|
+
_pagerTopPageKey = nil;
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
- (void)finishPagerTopIfReached
|
|
4125
|
+
{
|
|
4126
|
+
UIScrollView *scrollView = _pagerTopScrollView;
|
|
4127
|
+
if (_dispatchingPagerTop || ![self matchesPagerTop:scrollView]) return;
|
|
4128
|
+
CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
4129
|
+
if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
|
|
4130
|
+
_pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
|
|
4131
|
+
[_alignedBodyKeys addObject:_pagerTopPageKey];
|
|
4132
|
+
_currentLogicalOffset = logical;
|
|
4133
|
+
_headerOffset = _pagerTopLogicalTarget;
|
|
4134
|
+
[self applyHeaderOffset];
|
|
4135
|
+
[self offsetDiag:[NSString stringWithFormat:@"pager-top-complete-token=%lu-target=%.4f", (unsigned long)_pagerTopToken, _pagerTopLogicalTarget] page:_currentIndex scroll:scrollView];
|
|
4136
|
+
[self cancelPagerTop];
|
|
4137
|
+
}
|
|
4138
|
+
|
|
4139
|
+
- (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
|
|
4140
|
+
{
|
|
4141
|
+
UIScrollView *scrollView = _observedScrollView;
|
|
4142
|
+
NSString *key = _observedPageKey;
|
|
4143
|
+
if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
4144
|
+
_transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
|
|
4145
|
+
key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
|
|
4146
|
+
_observedPageGeneration != _generation || self.window == nil) return;
|
|
4147
|
+
[self cancelPagerTop];
|
|
4148
|
+
// Old List phase2 notifications must run before the new target owns this page.
|
|
4149
|
+
[NSNotificationCenter.defaultCenter postNotificationName:@"OneKeyNativeListCancelScrollCommand" object:scrollView];
|
|
4150
|
+
if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
|
|
4151
|
+
[(RNCNativeScrollerComponentView *)scrollView.superview stopScrolling];
|
|
4152
|
+
} else {
|
|
4153
|
+
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
|
|
4154
|
+
}
|
|
4155
|
+
[_pendingPageRestoreKeys removeObject:key];
|
|
4156
|
+
[_alignedBodyKeys removeObject:key];
|
|
4157
|
+
[_suspendedBodyKeys removeObject:key];
|
|
4158
|
+
[_displayBodyOverrides removeObjectForKey:key];
|
|
4159
|
+
CGFloat logicalTarget = target == 1 ? _headerHeight : 0;
|
|
4160
|
+
_pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
|
|
4161
|
+
_pagerTopScrollView = scrollView;
|
|
4162
|
+
_pagerTopPageKey = [key copy];
|
|
4163
|
+
_pagerTopPageIndex = _currentIndex;
|
|
4164
|
+
_pagerTopGeneration = _generation;
|
|
4165
|
+
_pagerTopLogicalTarget = logicalTarget;
|
|
4166
|
+
_pagerTopToken++;
|
|
4167
|
+
[_commandBodyKeys addObject:key];
|
|
4168
|
+
_headerOffset = logicalTarget;
|
|
4169
|
+
[self applyHeaderOffset];
|
|
4170
|
+
CGFloat physicalTarget = RNCLimitRestoredScrollOffset(logicalTarget - scrollView.contentInset.top,
|
|
4171
|
+
scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
|
|
4172
|
+
[self offsetDiag:[NSString stringWithFormat:@"pager-top-begin-token=%lu-target=%ld-animated=%d", (unsigned long)_pagerTopToken, (long)target, animated] page:_currentIndex scroll:scrollView];
|
|
4173
|
+
_dispatchingPagerTop = YES;
|
|
4174
|
+
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:animated];
|
|
4175
|
+
_dispatchingPagerTop = NO;
|
|
4176
|
+
[self finishPagerTopIfReached];
|
|
4177
|
+
}
|
|
4178
|
+
|
|
3716
4179
|
- (void)setPage:(NSInteger)index
|
|
3717
4180
|
{
|
|
3718
4181
|
[self goTo:index animated:YES];
|