@onekeyfe/react-native-pager-view 3.0.173 → 3.0.175
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 +860 -25
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +17 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +16 -0
- package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt +4 -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 +65 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +616 -30
- package/ios/RNCNativeScrollerComponentView.h +1 -0
- package/ios/RNCNativeScrollerComponentView.mm +48 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +332 -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 +8 -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 +3 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +18 -0
- package/src/CollapsiblePagerView.web.tsx +170 -36
- package/src/CollapsiblePagerViewNativeComponent.ts +8 -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,28 @@ 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 _headerSettledAtTop;
|
|
1521
|
+
__weak UIScrollView *_headerSettledOwner;
|
|
1522
|
+
NSString *_headerSettledKey;
|
|
1523
|
+
NSUInteger _headerSettledGeneration;
|
|
1524
|
+
NSString *_headerSettledGeometry;
|
|
1525
|
+
CFTimeInterval _headerSettledSince;
|
|
1526
|
+
BOOL _headerSettledWasBusy;
|
|
1527
|
+
NSTimer *_headerSettledTimer;
|
|
1528
|
+
__weak UIScrollView *_headerCommandOwner;
|
|
1529
|
+
|
|
1530
|
+
BOOL _finishingPageStateTransition;
|
|
1531
|
+
NSMutableSet<NSString *> *_pendingPageRestoreKeys;
|
|
1494
1532
|
__weak UIScrollView *_sharedHeaderScrollView;
|
|
1495
1533
|
__weak UIScrollView *_pendingFallbackScrollView;
|
|
1496
1534
|
__weak UIView *_sharedHeaderPressCancellationGestureHost;
|
|
@@ -1525,6 +1563,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1525
1563
|
_logicalChildren = [NSMutableArray new];
|
|
1526
1564
|
_pageControllers = [NSMutableArray new];
|
|
1527
1565
|
_pageOffsets = [NSMutableDictionary new];
|
|
1566
|
+
_commandBodyKeys = [NSMutableSet new];
|
|
1567
|
+
_alignedBodyKeys = [NSMutableSet new];
|
|
1568
|
+
_suspendedBodyKeys = [NSMutableSet new];
|
|
1569
|
+
_displayBodyOverrides = [NSMutableDictionary new];
|
|
1570
|
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nativeListScrollCommandAccepted:)
|
|
1571
|
+
name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
|
|
1572
|
+
_pendingPageRestoreKeys = [NSMutableSet new];
|
|
1528
1573
|
_releasedPageScrollStates = [NSMutableDictionary new];
|
|
1529
1574
|
_mountingRemovedScrollViews = [NSHashTable weakObjectsHashTable];
|
|
1530
1575
|
_originalInsets = [NSMapTable weakToStrongObjectsMapTable];
|
|
@@ -1642,6 +1687,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1642
1687
|
{
|
|
1643
1688
|
[super didMoveToWindow];
|
|
1644
1689
|
[self updateSharedHeaderPressCancellationGesture];
|
|
1690
|
+
[self updateHeaderSettledStateForceBusy:NO];
|
|
1645
1691
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
1646
1692
|
[self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight];
|
|
1647
1693
|
}
|
|
@@ -1668,6 +1714,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1668
1714
|
|
|
1669
1715
|
- (void)dealloc
|
|
1670
1716
|
{
|
|
1717
|
+
[self cancelHeaderSettledCheck];
|
|
1718
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
|
|
1671
1719
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
1672
1720
|
[self updateNativeScrollerViewport:scrollView stickyHeight:0];
|
|
1673
1721
|
}
|
|
@@ -1899,18 +1947,124 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1899
1947
|
[self applyHeaderOffset];
|
|
1900
1948
|
}
|
|
1901
1949
|
|
|
1950
|
+
- (void)offsetDiag:(NSString *)reason page:(NSInteger)index scroll:(UIScrollView *)scroll
|
|
1951
|
+
{
|
|
1952
|
+
#if DEBUG
|
|
1953
|
+
NSString *key = index >= 0 && index < _pageControllers.count ? [self pageKeyForIndex:index] : @"none";
|
|
1954
|
+
if (![@[@"portfolio", @"perps", @"defi", @"nft", @"history"] containsObject:key]) key = @"other";
|
|
1955
|
+
NSNumber *saved = index >= 0 && index < _pageControllers.count ? _pageOffsets[[self pageKeyForIndex:index]] : nil;
|
|
1956
|
+
UIView *component = scroll == nil ? nil : RNCScrollComponentView(scroll);
|
|
1957
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1958
|
+
@"[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",
|
|
1959
|
+
reason, (unsigned long)++_offsetDiagSequence, NSDate.date.timeIntervalSince1970 * 1000, CACurrentMediaTime(),
|
|
1960
|
+
(__bridge void *)self, (unsigned long)_generation, (unsigned long)_transitionId, (long)index, key,
|
|
1961
|
+
(long)_currentIndex, (long)_destinationIndex, _transitioning, _isPagerDragging,
|
|
1962
|
+
(__bridge void *)scroll, (long)scroll.tag, (__bridge void *)component, (long)component.tag,
|
|
1963
|
+
(__bridge void *)_observedScrollView, (long)_observedPageIndex, (unsigned long)_observedPageGeneration, _syntheticPageRestore, saved != nil, saved.doubleValue, _headerOffset, (long)_headerHeight,
|
|
1964
|
+
scroll.contentOffset.y, scroll.contentInset.top, scroll.contentInset.bottom,
|
|
1965
|
+
scroll.contentOffset.y + scroll.contentInset.top, scroll.contentSize.height, scroll.bounds.size.height]);
|
|
1966
|
+
#endif
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1902
1969
|
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
|
|
1903
1970
|
{
|
|
1904
|
-
|
|
1971
|
+
[self offsetDiag:@"prepare-check" page:pageIndex scroll:nil];
|
|
1972
|
+
if (!_nativeSmoothHeaderScrollEnabled ||
|
|
1905
1973
|
pageIndex < 0 || pageIndex >= _pageControllers.count) return;
|
|
1906
|
-
|
|
1907
|
-
_pageOffsets[[self pageKeyForIndex:pageIndex]] = @(_headerOffset);
|
|
1908
1974
|
UIScrollView *scrollView = [self verticalScrollViewForPageAtIndex:pageIndex];
|
|
1909
1975
|
if (scrollView != nil && scrollView != _observedScrollView) {
|
|
1910
1976
|
[self applyInsetsToScrollView:scrollView pageIndex:pageIndex restore:YES];
|
|
1911
1977
|
}
|
|
1912
1978
|
}
|
|
1913
1979
|
|
|
1980
|
+
- (void)beginPageStateTransition
|
|
1981
|
+
{
|
|
1982
|
+
[self cancelPagerTop];
|
|
1983
|
+
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0 || _transitionPageOffsets != nil) return;
|
|
1984
|
+
_transitionSourceDisplayBody = MAX(_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top - _headerHeight, 0);
|
|
1985
|
+
[self detachScrollObserver];
|
|
1986
|
+
_transitionSourceIndex = _currentIndex;
|
|
1987
|
+
_transitionHeaderOffset = _headerOffset;
|
|
1988
|
+
_transitionPageOffsets = [_pageOffsets copy];
|
|
1989
|
+
_transitionCompensatedPages = [NSMutableIndexSet new];
|
|
1990
|
+
#if DEBUG
|
|
1991
|
+
_transitionDiagBucket = -1;
|
|
1992
|
+
[self offsetDiag:[NSString stringWithFormat:@"screen-begin-source=%ld-savedQ=%.4f", (long)_transitionSourceIndex,
|
|
1993
|
+
[_transitionPageOffsets[self.currentPageKey] doubleValue]] page:_currentIndex scroll:nil];
|
|
1994
|
+
#endif
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
- (void)updatePageStateTransitionProgress:(CGFloat)progress
|
|
1998
|
+
{
|
|
1999
|
+
if (_transitionPageOffsets == nil || _finishingPageStateTransition) return;
|
|
2000
|
+
NSInteger first = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
|
|
2001
|
+
NSInteger second = MIN(first + 1, (NSInteger)_pageControllers.count - 1);
|
|
2002
|
+
CGFloat fraction = RNCClamp(progress - first, 0, 1);
|
|
2003
|
+
if (!_isPagerDragging && _destinationIndex != _transitionSourceIndex) {
|
|
2004
|
+
first = _transitionSourceIndex;
|
|
2005
|
+
second = _destinationIndex;
|
|
2006
|
+
fraction = RNCClamp((progress - first) / (second - first), 0, 1);
|
|
2007
|
+
}
|
|
2008
|
+
CGFloat firstQ = [_transitionPageOffsets[[self pageKeyForIndex:first]] doubleValue];
|
|
2009
|
+
CGFloat secondQ = [_transitionPageOffsets[[self pageKeyForIndex:second]] doubleValue];
|
|
2010
|
+
_headerOffset = _transitionHeaderOffset;
|
|
2011
|
+
#if DEBUG
|
|
2012
|
+
NSInteger bucket = (NSInteger)floor(fraction * 4);
|
|
2013
|
+
if (_transitionDiagBucket != bucket) {
|
|
2014
|
+
_transitionDiagBucket = bucket;
|
|
2015
|
+
[self offsetDiag:[NSString stringWithFormat:
|
|
2016
|
+
@"screen-progress-kind=%@-source=%ld-target=%ld-sourceQ=%.4f-targetQ=%.4f-position=%.4f-fraction=%.4f",
|
|
2017
|
+
_isPagerDragging ? @"interactive" : @"programmatic-endpoint", (long)first, (long)second,
|
|
2018
|
+
firstQ, secondQ, progress, fraction] page:_currentIndex scroll:nil];
|
|
2019
|
+
}
|
|
2020
|
+
#endif
|
|
2021
|
+
_syntheticPageRestore = YES;
|
|
2022
|
+
NSInteger visibleFirst = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
|
|
2023
|
+
NSInteger visibleLast = MIN(visibleFirst + 1, (NSInteger)_pageControllers.count - 1);
|
|
2024
|
+
NSMutableIndexSet *displayPages = [NSMutableIndexSet indexSetWithIndexesInRange:
|
|
2025
|
+
NSMakeRange(visibleFirst, visibleLast - visibleFirst + 1)];
|
|
2026
|
+
if (!_isPagerDragging) {
|
|
2027
|
+
// UIView tap animation reports its endpoint once while the source exits.
|
|
2028
|
+
[displayPages addIndex:_transitionSourceIndex];
|
|
2029
|
+
[displayPages addIndex:_destinationIndex];
|
|
2030
|
+
}
|
|
2031
|
+
for (NSUInteger index = displayPages.firstIndex; index != NSNotFound;
|
|
2032
|
+
index = [displayPages indexGreaterThanIndex:index]) {
|
|
2033
|
+
UIScrollView *scroll = [self verticalScrollViewForPageAtIndex:index];
|
|
2034
|
+
if (scroll != nil) [self applyInsetsToScrollView:scroll pageIndex:index restore:YES];
|
|
2035
|
+
}
|
|
2036
|
+
_syntheticPageRestore = NO;
|
|
2037
|
+
[self layoutSharedHeaders];
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
- (void)finishPageStateTransition
|
|
2041
|
+
{
|
|
2042
|
+
if (_transitionPageOffsets == nil) return;
|
|
2043
|
+
_finishingPageStateTransition = YES;
|
|
2044
|
+
_headerOffset = _transitionHeaderOffset;
|
|
2045
|
+
if (_currentIndex == _transitionSourceIndex) _displayBodyOverrides[self.currentPageKey] = @(_transitionSourceDisplayBody);
|
|
2046
|
+
else [_displayBodyOverrides removeObjectForKey:self.currentPageKey];
|
|
2047
|
+
if (_headerOffset >= _headerHeight) [_suspendedBodyKeys removeObject:self.currentPageKey];
|
|
2048
|
+
_syntheticPageRestore = YES;
|
|
2049
|
+
// Both completion and cancellation undo every temporary viewport offset.
|
|
2050
|
+
for (NSUInteger index = _transitionCompensatedPages.firstIndex; index != NSNotFound;
|
|
2051
|
+
index = [_transitionCompensatedPages indexGreaterThanIndex:index]) {
|
|
2052
|
+
UIScrollView *scroll = [self verticalScrollViewForPageAtIndex:index];
|
|
2053
|
+
if (scroll == nil) continue;
|
|
2054
|
+
[self applyInsetsToScrollView:scroll pageIndex:index restore:YES];
|
|
2055
|
+
|
|
2056
|
+
}
|
|
2057
|
+
_syntheticPageRestore = NO;
|
|
2058
|
+
_finishingPageStateTransition = NO;
|
|
2059
|
+
_transitionPageOffsets = nil;
|
|
2060
|
+
_transitionCompensatedPages = nil;
|
|
2061
|
+
[self layoutSharedHeaders];
|
|
2062
|
+
#if DEBUG
|
|
2063
|
+
[self offsetDiag:[NSString stringWithFormat:@"screen-finish-source=%ld-cancelled=%d", (long)_transitionSourceIndex,
|
|
2064
|
+
_currentIndex == _transitionSourceIndex] page:_currentIndex scroll:nil];
|
|
2065
|
+
#endif
|
|
2066
|
+
}
|
|
2067
|
+
|
|
1914
2068
|
#pragma mark - React mounting
|
|
1915
2069
|
|
|
1916
2070
|
// OneKey patch: Fabric can recycle a removed list as a descendant of our header
|
|
@@ -1958,8 +2112,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1958
2112
|
_releasedPageScrollStates[pageKey] = state;
|
|
1959
2113
|
// Retained pages may be visually aligned to the expanded header while
|
|
1960
2114
|
// their saved offsets still hold the user's independent list position.
|
|
1961
|
-
|
|
2115
|
+
BOOL ownsObservedPage = scrollView == _observedScrollView &&
|
|
2116
|
+
(!_nativeSmoothHeaderScrollEnabled ||
|
|
2117
|
+
(_observedPageGeneration == _generation && _observedPageIndex == index &&
|
|
2118
|
+
[_observedPageKey isEqualToString:pageKey]));
|
|
2119
|
+
if (!_nativeSmoothHeaderScrollEnabled && _transitionPageOffsets == nil && !_syntheticPageRestore &&
|
|
2120
|
+
![_pendingPageRestoreKeys containsObject:pageKey] &&
|
|
2121
|
+
(ownsObservedPage || _pageOffsets[pageKey] == nil)) {
|
|
2122
|
+
[self offsetDiag:@"mount-release-save-before" page:index scroll:scrollView];
|
|
1962
2123
|
_pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2124
|
+
[self offsetDiag:@"mount-release-save-after" page:index scroll:scrollView];
|
|
1963
2125
|
}
|
|
1964
2126
|
break;
|
|
1965
2127
|
}
|
|
@@ -2213,6 +2375,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2213
2375
|
}
|
|
2214
2376
|
}
|
|
2215
2377
|
[self layoutSharedHeaders];
|
|
2378
|
+
[self updateHeaderSettledStateForceBusy:NO];
|
|
2216
2379
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2217
2380
|
if (!self->_isBeingRecycled) {
|
|
2218
2381
|
[self prepareAdjacentPageInsets];
|
|
@@ -2223,6 +2386,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2223
2386
|
|
|
2224
2387
|
- (void)prepareForRecycle
|
|
2225
2388
|
{
|
|
2389
|
+
[self cancelHeaderSettledCheck];
|
|
2390
|
+
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
2226
2391
|
_isBeingRecycled = YES;
|
|
2227
2392
|
_generation++;
|
|
2228
2393
|
[_mountingRemovedScrollViews removeAllObjects];
|
|
@@ -2243,7 +2408,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2243
2408
|
if (appliedTop != nil && scrollView.refreshControl != nil) {
|
|
2244
2409
|
original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
|
|
2245
2410
|
}
|
|
2246
|
-
|
|
2411
|
+
[self offsetDiag:@"release-original-inset-before" page:_currentIndex scroll:scrollView];
|
|
2412
|
+
scrollView.contentInset = original;
|
|
2413
|
+
[self offsetDiag:@"release-original-inset-after" page:_currentIndex scroll:scrollView];
|
|
2247
2414
|
}
|
|
2248
2415
|
NSValue *indicatorValue = [_originalIndicatorInsets objectForKey:scrollView];
|
|
2249
2416
|
if (indicatorValue != nil) {
|
|
@@ -2295,6 +2462,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2295
2462
|
_pagerScrollView = nil;
|
|
2296
2463
|
[super prepareForRecycle];
|
|
2297
2464
|
[_pageOffsets removeAllObjects];
|
|
2465
|
+
[_commandBodyKeys removeAllObjects];
|
|
2466
|
+
[_alignedBodyKeys removeAllObjects];
|
|
2467
|
+
[_suspendedBodyKeys removeAllObjects];
|
|
2468
|
+
[_displayBodyOverrides removeAllObjects];
|
|
2469
|
+
[_pendingPageRestoreKeys removeAllObjects];
|
|
2470
|
+
_transitionPageOffsets = nil;
|
|
2471
|
+
_transitionCompensatedPages = nil;
|
|
2472
|
+
_syntheticPageRestore = NO;
|
|
2473
|
+
_finishingPageStateTransition = NO;
|
|
2298
2474
|
_pageKeys = @[];
|
|
2299
2475
|
_retainedPages = @"[]";
|
|
2300
2476
|
_currentIndex = 0;
|
|
@@ -2627,6 +2803,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2627
2803
|
- (void)attachScrollObserverForCurrentPageAfterTransition:(BOOL)selectionSettled
|
|
2628
2804
|
{
|
|
2629
2805
|
if (_isBeingRecycled) return;
|
|
2806
|
+
if (_nativeSmoothHeaderScrollEnabled &&
|
|
2807
|
+
(_transitioning || _isPagerDragging || _transitionPageOffsets != nil) && !selectionSettled) return;
|
|
2808
|
+
if (selectionSettled) [self finishPageStateTransition];
|
|
2630
2809
|
if (_needsScrollObserverReattachAfterMount) {
|
|
2631
2810
|
if (self.window == nil || ((_transitioning || _isPagerDragging) && !selectionSettled)) return;
|
|
2632
2811
|
[self prepareAdjacentPageInsets];
|
|
@@ -2685,24 +2864,38 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2685
2864
|
if (_pagerScrollView != nil && !_nativeSmoothHeaderScrollEnabled && !_needsPropsReapply) {
|
|
2686
2865
|
[candidate.panGestureRecognizer requireGestureRecognizerToFail:_pagerScrollView.panGestureRecognizer];
|
|
2687
2866
|
}
|
|
2688
|
-
if (candidate == _observedScrollView
|
|
2867
|
+
if (candidate == _observedScrollView &&
|
|
2868
|
+
(!_nativeSmoothHeaderScrollEnabled ||
|
|
2869
|
+
([_observedPageKey isEqualToString:self.currentPageKey] &&
|
|
2870
|
+
_observedPageIndex == _currentIndex && _observedPageGeneration == _generation))) {
|
|
2689
2871
|
_needsScrollObserverReattachAfterMount = NO;
|
|
2872
|
+
if ([_pendingPageRestoreKeys containsObject:self.currentPageKey]) {
|
|
2873
|
+
[self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
|
|
2874
|
+
}
|
|
2690
2875
|
[self attachSharedHeadersToScrollView:candidate];
|
|
2691
2876
|
[self updateSharedHeaderPressCancellationGesture];
|
|
2692
2877
|
return;
|
|
2693
2878
|
}
|
|
2694
2879
|
|
|
2695
|
-
|
|
2880
|
+
[self offsetDiag:@"observer-candidate" page:_currentIndex scroll:candidate];
|
|
2881
|
+
BOOL replacingCurrentScrollView = _observingContentOffset &&
|
|
2882
|
+
(!_nativeSmoothHeaderScrollEnabled || [_observedPageKey isEqualToString:self.currentPageKey]);
|
|
2696
2883
|
[self detachScrollObserver];
|
|
2697
2884
|
if (replacingCurrentScrollView) {
|
|
2698
|
-
//
|
|
2699
|
-
|
|
2885
|
+
// Legacy replacement discards body state; smooth replacement waits for layout.
|
|
2886
|
+
[self offsetDiag:@"observer-replacement-cache-before" page:_currentIndex scroll:candidate];
|
|
2887
|
+
if (!_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
2888
|
+
[self offsetDiag:@"observer-replacement-cache-after" page:_currentIndex scroll:candidate];
|
|
2700
2889
|
}
|
|
2701
2890
|
_observedScrollView = candidate;
|
|
2891
|
+
_observedPageKey = [self.currentPageKey copy];
|
|
2892
|
+
_observedPageIndex = _currentIndex;
|
|
2893
|
+
_observedPageGeneration = _generation;
|
|
2894
|
+
[self offsetDiag:@"observer-attached-before-restore" page:_currentIndex scroll:candidate];
|
|
2702
2895
|
[self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
|
|
2703
2896
|
[candidate addObserver:self
|
|
2704
2897
|
forKeyPath:@"contentOffset"
|
|
2705
|
-
options:NSKeyValueObservingOptionNew
|
|
2898
|
+
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
|
2706
2899
|
context:RNCCollapsiblePagerContentOffsetContext];
|
|
2707
2900
|
[candidate addObserver:self
|
|
2708
2901
|
forKeyPath:@"contentInset"
|
|
@@ -2718,14 +2911,29 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2718
2911
|
_currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
|
|
2719
2912
|
[self attachSharedHeadersToScrollView:candidate];
|
|
2720
2913
|
[self updateSharedHeaderPressCancellationGesture];
|
|
2914
|
+
[self updateHeaderSettledStateForceBusy:NO];
|
|
2721
2915
|
}
|
|
2722
2916
|
|
|
2723
2917
|
- (void)detachScrollObserver
|
|
2724
2918
|
{
|
|
2919
|
+
[self cancelHeaderSettledCheck];
|
|
2920
|
+
_headerCommandOwner = nil;
|
|
2921
|
+
_headerSettledGeometry = nil;
|
|
2922
|
+
_headerSettledWasBusy = YES;
|
|
2923
|
+
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
2924
|
+
[self cancelPagerTop];
|
|
2725
2925
|
UIScrollView *scrollView = _observedScrollView;
|
|
2726
2926
|
if (scrollView != nil) {
|
|
2727
2927
|
[scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
|
|
2728
|
-
|
|
2928
|
+
[self offsetDiag:@"detach-save-before" page:_currentIndex scroll:scrollView];
|
|
2929
|
+
NSString *ownerKey = _nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey;
|
|
2930
|
+
BOOL ownerKeyExists = ownerKey != nil && ([_pageKeys containsObject:ownerKey] ||
|
|
2931
|
+
(_observedPageIndex >= 0 && _observedPageIndex < _pageControllers.count &&
|
|
2932
|
+
[[self pageKeyForIndex:_observedPageIndex] isEqualToString:ownerKey]));
|
|
2933
|
+
if (ownerKey != nil && !_nativeSmoothHeaderScrollEnabled) {
|
|
2934
|
+
_pageOffsets[ownerKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2935
|
+
}
|
|
2936
|
+
[self offsetDiag:@"detach-save-after" page:_currentIndex scroll:scrollView];
|
|
2729
2937
|
if (_observingContentOffset) {
|
|
2730
2938
|
[scrollView removeObserver:self
|
|
2731
2939
|
forKeyPath:@"contentOffset"
|
|
@@ -2738,8 +2946,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2738
2946
|
context:RNCCollapsiblePagerContentOffsetContext];
|
|
2739
2947
|
}
|
|
2740
2948
|
}
|
|
2949
|
+
if (!_nativeSmoothHeaderScrollEnabled && _observedPageKey != nil) {
|
|
2950
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
2951
|
+
}
|
|
2741
2952
|
_observingContentOffset = NO;
|
|
2742
2953
|
_observedScrollView = nil;
|
|
2954
|
+
_observedPageKey = nil;
|
|
2955
|
+
_observedPageIndex = NSNotFound;
|
|
2743
2956
|
if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
|
|
2744
2957
|
[(RNCNativeScrollerComponentView *)scrollView.superview stopScrolling];
|
|
2745
2958
|
}
|
|
@@ -2925,6 +3138,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2925
3138
|
|
|
2926
3139
|
- (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer
|
|
2927
3140
|
{
|
|
3141
|
+
if (recognizer == _observedScrollView.panGestureRecognizer) {
|
|
3142
|
+
[self updateHeaderSettledStateForceBusy:
|
|
3143
|
+
recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged];
|
|
3144
|
+
}
|
|
2928
3145
|
if (!_nativeSmoothHeaderScrollEnabled || recognizer != _observedScrollView.panGestureRecognizer) {
|
|
2929
3146
|
return;
|
|
2930
3147
|
}
|
|
@@ -3010,7 +3227,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3010
3227
|
[self releaseRefreshControlForScrollView:scrollView];
|
|
3011
3228
|
if (scrollView == _observedScrollView) {
|
|
3012
3229
|
[self detachScrollObserver];
|
|
3013
|
-
|
|
3230
|
+
[self offsetDiag:resetSavedOffset ? @"release-reset-before" : @"release-retain-before" page:_currentIndex scroll:scrollView];
|
|
3231
|
+
if (resetSavedOffset && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
3232
|
+
[self offsetDiag:@"release-reset-after" page:_currentIndex scroll:scrollView];
|
|
3014
3233
|
}
|
|
3015
3234
|
if (scrollView == _sharedHeaderScrollView) {
|
|
3016
3235
|
[self restoreSharedHeadersToContainer];
|
|
@@ -3030,8 +3249,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3030
3249
|
[_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
|
|
3031
3250
|
scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
|
|
3032
3251
|
[_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
|
|
3252
|
+
[self offsetDiag:@"release-original-offset-before" page:_currentIndex scroll:scrollView];
|
|
3033
3253
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
|
|
3034
3254
|
animated:NO];
|
|
3255
|
+
[self offsetDiag:@"release-original-offset-after" page:_currentIndex scroll:scrollView];
|
|
3035
3256
|
[_originalInsets removeObjectForKey:scrollView];
|
|
3036
3257
|
[_appliedTopInsets removeObjectForKey:scrollView];
|
|
3037
3258
|
[_originalIndicatorInsets removeObjectForKey:scrollView];
|
|
@@ -3060,8 +3281,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3060
3281
|
pageIndex:(NSInteger)pageIndex
|
|
3061
3282
|
restore:(BOOL)restore
|
|
3062
3283
|
{
|
|
3063
|
-
//
|
|
3064
|
-
//
|
|
3284
|
+
// Smooth pages retain their complete offset while replacement identity and
|
|
3285
|
+
// layout settle. Legacy pages retain the historical header-only policy.
|
|
3286
|
+
[self offsetDiag:restore ? @"restore-enter" : @"insets-enter" page:pageIndex scroll:scrollView];
|
|
3065
3287
|
NSString *pageKey = [self pageKeyForIndex:pageIndex];
|
|
3066
3288
|
RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
|
|
3067
3289
|
BOOL deferredReleasedIdentity = NO;
|
|
@@ -3107,8 +3329,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3107
3329
|
deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
|
|
3108
3330
|
RNCCollapsiblePagerFallbackDebounceInterval;
|
|
3109
3331
|
}
|
|
3332
|
+
#if DEBUG
|
|
3333
|
+
[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];
|
|
3334
|
+
#endif
|
|
3110
3335
|
if (!deferredReleasedIdentity) {
|
|
3111
|
-
|
|
3336
|
+
[self offsetDiag:@"released-identity-cache-before" page:pageIndex scroll:scrollView];
|
|
3337
|
+
if (!sameList && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[pageKey] = @(_headerOffset);
|
|
3338
|
+
[self offsetDiag:@"released-identity-cache-after" page:pageIndex scroll:scrollView];
|
|
3112
3339
|
[_releasedPageScrollStates removeObjectForKey:pageKey];
|
|
3113
3340
|
}
|
|
3114
3341
|
}
|
|
@@ -3156,29 +3383,60 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3156
3383
|
if (pageIndex == _currentIndex) [self updateHeaderForScrollView:scrollView];
|
|
3157
3384
|
return;
|
|
3158
3385
|
}
|
|
3386
|
+
BOOL previousSyntheticRestore = _syntheticPageRestore;
|
|
3387
|
+
if (_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:pageKey]) _syntheticPageRestore = YES;
|
|
3388
|
+
[self offsetDiag:@"inset-assignment-before" page:pageIndex scroll:scrollView];
|
|
3159
3389
|
scrollView.contentInset = next;
|
|
3390
|
+
[self offsetDiag:@"inset-assignment-after" page:pageIndex scroll:scrollView];
|
|
3160
3391
|
UIEdgeInsets indicatorInsets = scrollView.verticalScrollIndicatorInsets;
|
|
3161
3392
|
indicatorInsets.top = pagerTopInset;
|
|
3162
3393
|
scrollView.verticalScrollIndicatorInsets = indicatorInsets;
|
|
3163
3394
|
scrollView.alwaysBounceVertical = YES;
|
|
3164
3395
|
|
|
3165
3396
|
CGFloat targetOffset = previousLogicalOffset - next.top;
|
|
3397
|
+
CGFloat intendedLogicalOffset = previousLogicalOffset;
|
|
3166
3398
|
if (restore) {
|
|
3167
|
-
NSNumber *saved =
|
|
3399
|
+
NSNumber *saved = _transitionPageOffsets != nil
|
|
3400
|
+
? _transitionPageOffsets[pageKey] : _pageOffsets[pageKey];
|
|
3168
3401
|
// The sticky slot can change height between pages. Cache offsets relative
|
|
3169
3402
|
// to content start so a round trip does not add that height difference.
|
|
3170
|
-
CGFloat restoredLogicalOffset
|
|
3171
|
-
|
|
3172
|
-
:
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3403
|
+
CGFloat restoredLogicalOffset;
|
|
3404
|
+
if (_nativeSmoothHeaderScrollEnabled && _headerHeight > 0) {
|
|
3405
|
+
[_alignedBodyKeys addObject:pageKey];
|
|
3406
|
+
if (_headerOffset < _headerHeight && saved.doubleValue > 0) [_suspendedBodyKeys addObject:pageKey];
|
|
3407
|
+
CGFloat body = saved.doubleValue;
|
|
3408
|
+
if (_transitionPageOffsets != nil && pageIndex == _transitionSourceIndex) body = _transitionSourceDisplayBody;
|
|
3409
|
+
else if (_transitionPageOffsets != nil) [_displayBodyOverrides removeObjectForKey:pageKey];
|
|
3410
|
+
else if (_displayBodyOverrides[pageKey] != nil) body = _displayBodyOverrides[pageKey].doubleValue;
|
|
3411
|
+
restoredLogicalOffset = _headerOffset + (_headerOffset >= _headerHeight ? body : 0);
|
|
3412
|
+
if (_transitionPageOffsets != nil) [_transitionCompensatedPages addIndex:pageIndex];
|
|
3413
|
+
if (deferredReleasedIdentity || scrollView.bounds.size.height <= 0 || scrollView.contentSize.height <= 0) {
|
|
3414
|
+
[_pendingPageRestoreKeys addObject:pageKey];
|
|
3415
|
+
_syntheticPageRestore = previousSyntheticRestore;
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
[_pendingPageRestoreKeys removeObject:pageKey];
|
|
3419
|
+
} else {
|
|
3420
|
+
restoredLogicalOffset = deferredReleasedIdentity || saved == nil ? _headerOffset : saved.doubleValue;
|
|
3421
|
+
}
|
|
3422
|
+
#if DEBUG
|
|
3423
|
+
[self offsetDiag:[NSString stringWithFormat:@"restore-candidate-saved=%.4f-headerAlignedLogical=%.4f", saved.doubleValue, restoredLogicalOffset] page:pageIndex scroll:scrollView];
|
|
3424
|
+
#endif
|
|
3425
|
+
intendedLogicalOffset = restoredLogicalOffset;
|
|
3426
|
+
// Cancellation restores the visible source, while retaining its unreachable reading intent.
|
|
3427
|
+
if (_nativeSmoothHeaderScrollEnabled && _headerOffset >= _headerHeight &&
|
|
3428
|
+
_displayBodyOverrides[pageKey] != nil) {
|
|
3429
|
+
intendedLogicalOffset = MAX(intendedLogicalOffset, _headerOffset + _pageOffsets[pageKey].doubleValue);
|
|
3177
3430
|
}
|
|
3178
3431
|
targetOffset = restoredLogicalOffset - next.top;
|
|
3179
|
-
|
|
3432
|
+
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0) {
|
|
3433
|
+
targetOffset = MAX(targetOffset, -next.top + _headerOffset);
|
|
3434
|
+
}
|
|
3180
3435
|
// An inactive list may shrink after its offset observer was detached.
|
|
3181
3436
|
// Do not overwrite its corrected offset with a now-unreachable cached one.
|
|
3437
|
+
#if DEBUG
|
|
3438
|
+
[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];
|
|
3439
|
+
#endif
|
|
3182
3440
|
targetOffset = RNCLimitRestoredScrollOffset(
|
|
3183
3441
|
targetOffset,
|
|
3184
3442
|
scrollView.contentSize.height,
|
|
@@ -3187,11 +3445,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3187
3445
|
next.bottom
|
|
3188
3446
|
);
|
|
3189
3447
|
}
|
|
3448
|
+
#if DEBUG
|
|
3449
|
+
[self offsetDiag:[NSString stringWithFormat:@"restore-final-target=%.4f-restore=%d", targetOffset, restore] page:pageIndex scroll:scrollView];
|
|
3450
|
+
#endif
|
|
3190
3451
|
// A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
|
|
3191
3452
|
if (restore || previousTopInset != next.top) {
|
|
3192
3453
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, targetOffset) animated:NO];
|
|
3193
3454
|
}
|
|
3194
|
-
if (
|
|
3455
|
+
if (_nativeSmoothHeaderScrollEnabled && restore && targetOffset + next.top + 0.5 < intendedLogicalOffset) {
|
|
3456
|
+
[_pendingPageRestoreKeys addObject:pageKey];
|
|
3457
|
+
}
|
|
3458
|
+
_syntheticPageRestore = previousSyntheticRestore;
|
|
3459
|
+
if (pageIndex == _currentIndex) {
|
|
3460
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3461
|
+
if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:scrollView];
|
|
3462
|
+
else [self applyHeaderOffset];
|
|
3463
|
+
}
|
|
3464
|
+
[self offsetDiag:@"restore-post" page:pageIndex scroll:scrollView];
|
|
3195
3465
|
}
|
|
3196
3466
|
|
|
3197
3467
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
@@ -3201,6 +3471,75 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3201
3471
|
}
|
|
3202
3472
|
}
|
|
3203
3473
|
|
|
3474
|
+
- (void)nativeListScrollCommandAccepted:(NSNotification *)notification
|
|
3475
|
+
{
|
|
3476
|
+
UIScrollView *scrollView = notification.object;
|
|
3477
|
+
if (scrollView != _observedScrollView ||
|
|
3478
|
+
_observedPageKey == nil || ![_observedPageKey isEqualToString:self.currentPageKey] ||
|
|
3479
|
+
_observedPageIndex != _currentIndex || _observedPageGeneration != _generation ||
|
|
3480
|
+
_transitioning || _isPagerDragging || _transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
3481
|
+
NSInteger phase = [notification.userInfo[@"phase"] integerValue];
|
|
3482
|
+
[self updateHeaderSettledStateForceBusy:NO];
|
|
3483
|
+
if (phase == 1) _headerCommandOwner = scrollView;
|
|
3484
|
+
else if (phase == 0 || phase == 2) _headerCommandOwner = nil;
|
|
3485
|
+
[self updateHeaderSettledStateForceBusy:phase != 2];
|
|
3486
|
+
if ([self matchesPagerTop:scrollView]) {
|
|
3487
|
+
if (phase == 0) [self cancelPagerTop];
|
|
3488
|
+
else return;
|
|
3489
|
+
}
|
|
3490
|
+
if (phase == 0) [_alignedBodyKeys removeObject:_observedPageKey];
|
|
3491
|
+
if (!_nativeSmoothHeaderScrollEnabled) return;
|
|
3492
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3493
|
+
if (phase == 1) { [_commandBodyKeys addObject:_observedPageKey]; return; }
|
|
3494
|
+
if (phase == 2) {
|
|
3495
|
+
if (![_commandBodyKeys containsObject:_observedPageKey]) return;
|
|
3496
|
+
_pageOffsets[_observedPageKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3497
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3498
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3502
|
+
[_pendingPageRestoreKeys removeObject:_observedPageKey];
|
|
3503
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3504
|
+
[_suspendedBodyKeys removeObject:_observedPageKey];
|
|
3505
|
+
[_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3506
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3507
|
+
BOOL explicitTop = [notification.userInfo[@"explicitTop"] boolValue];
|
|
3508
|
+
_pageOffsets[_observedPageKey] = @(explicitTop ? 0 : MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3509
|
+
if (explicitTop) {
|
|
3510
|
+
_headerOffset = 0;
|
|
3511
|
+
[self applyHeaderOffset];
|
|
3512
|
+
}
|
|
3513
|
+
[self offsetDiag:@"list-command-body-takeover" page:_currentIndex scroll:scrollView];
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
- (void)nativeScrollerWillReceiveScrollCommand:(RNCNativeScrollerComponentView *)scroller
|
|
3517
|
+
{
|
|
3518
|
+
UIScrollView *scrollView = scroller.scrollView;
|
|
3519
|
+
NSString *ownerKey = _observedPageKey;
|
|
3520
|
+
if (scrollView != _observedScrollView ||
|
|
3521
|
+
ownerKey == nil || ![ownerKey isEqualToString:self.currentPageKey] ||
|
|
3522
|
+
_observedPageIndex != _currentIndex || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
|
|
3523
|
+
_observedPageGeneration != _generation || _transitioning || _isPagerDragging ||
|
|
3524
|
+
_transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
3525
|
+
if (!_dispatchingPagerTop) {
|
|
3526
|
+
[self cancelPagerTop];
|
|
3527
|
+
[_alignedBodyKeys removeObject:ownerKey];
|
|
3528
|
+
}
|
|
3529
|
+
if (!_nativeSmoothHeaderScrollEnabled) return;
|
|
3530
|
+
[self offsetDiag:@"command-pending-cancel-before" page:_observedPageIndex scroll:scrollView];
|
|
3531
|
+
[_pendingPageRestoreKeys removeObject:ownerKey];
|
|
3532
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3533
|
+
[_commandBodyKeys removeObject:ownerKey];
|
|
3534
|
+
[_alignedBodyKeys removeObject:ownerKey];
|
|
3535
|
+
[_suspendedBodyKeys removeObject:ownerKey];
|
|
3536
|
+
[_displayBodyOverrides removeObjectForKey:ownerKey];
|
|
3537
|
+
_pageOffsets[ownerKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3538
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3539
|
+
[self offsetDiag:@"command-pending-cancel-after" page:_observedPageIndex scroll:scrollView];
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
|
|
3204
3543
|
- (void)nativeScroller:(RNCNativeScrollerComponentView *)scroller
|
|
3205
3544
|
callerInsetChangedFrom:(UIEdgeInsets)previous to:(UIEdgeInsets)next
|
|
3206
3545
|
{
|
|
@@ -3252,25 +3591,89 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3252
3591
|
if (context == RNCScrollRefreshControlContext) {
|
|
3253
3592
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
3254
3593
|
[self updateRefreshControlForScrollView:scrollView];
|
|
3255
|
-
if (scrollView == _observedScrollView
|
|
3594
|
+
if (scrollView == _observedScrollView && !_syntheticPageRestore &&
|
|
3595
|
+
(!_nativeSmoothHeaderScrollEnabled || (!_transitioning && _transitionPageOffsets == nil))) {
|
|
3596
|
+
[self updateHeaderForScrollView:scrollView];
|
|
3597
|
+
}
|
|
3256
3598
|
return;
|
|
3257
3599
|
}
|
|
3258
3600
|
if (context == RNCCollapsiblePagerContentOffsetContext && object == _observedScrollView) {
|
|
3601
|
+
if (![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
|
|
3602
|
+
[self updateHeaderSettledStateForceBusy:YES];
|
|
3603
|
+
}
|
|
3604
|
+
if ((_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:_observedPageKey]) &&
|
|
3605
|
+
(_observedPageGeneration != _generation || _observedPageKey == nil ||
|
|
3606
|
+
_observedPageIndex != _currentIndex || ![_observedPageKey isEqualToString:self.currentPageKey] ||
|
|
3607
|
+
_transitioning || _isPagerDragging ||
|
|
3608
|
+
_transitionPageOffsets != nil || _syntheticPageRestore)) return;
|
|
3609
|
+
UIScrollView *ownedScroll = (UIScrollView *)object;
|
|
3610
|
+
NSValue *oldPoint = change[NSKeyValueChangeOldKey];
|
|
3611
|
+
NSValue *newPoint = change[NSKeyValueChangeNewKey];
|
|
3612
|
+
if ([keyPath isEqualToString:@"contentOffset"] && [self matchesPagerTop:ownedScroll] && ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3613
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3614
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3615
|
+
CGPoint interruptedOffset = ownedScroll.contentOffset;
|
|
3616
|
+
[self cancelPagerTop];
|
|
3617
|
+
[ownedScroll setContentOffset:interruptedOffset animated:NO];
|
|
3618
|
+
}
|
|
3619
|
+
if ([keyPath isEqualToString:@"contentOffset"] &&
|
|
3620
|
+
ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3621
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3622
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3623
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3624
|
+
}
|
|
3625
|
+
if (_nativeSmoothHeaderScrollEnabled && [keyPath isEqualToString:@"contentOffset"] &&
|
|
3626
|
+
ownedScroll.panGestureRecognizer.state == UIGestureRecognizerStateChanged &&
|
|
3627
|
+
[oldPoint isKindOfClass:NSValue.class] && [newPoint isKindOfClass:NSValue.class] &&
|
|
3628
|
+
oldPoint.CGPointValue.y != newPoint.CGPointValue.y) {
|
|
3629
|
+
[self cancelPagerTop];
|
|
3630
|
+
[_commandBodyKeys removeObject:_observedPageKey];
|
|
3631
|
+
[_pendingPageRestoreKeys removeObject:_observedPageKey];
|
|
3632
|
+
[_alignedBodyKeys removeObject:_observedPageKey];
|
|
3633
|
+
[_suspendedBodyKeys removeObject:_observedPageKey];
|
|
3634
|
+
[_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3635
|
+
}
|
|
3636
|
+
if (_nativeSmoothHeaderScrollEnabled && [_pendingPageRestoreKeys containsObject:_observedPageKey]) {
|
|
3637
|
+
if ([keyPath isEqualToString:@"contentSize"] &&
|
|
3638
|
+
![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
|
|
3639
|
+
if (_headerOffset >= _headerHeight) [_displayBodyOverrides removeObjectForKey:_observedPageKey];
|
|
3640
|
+
[self applyInsetsToScrollView:_observedScrollView pageIndex:_observedPageIndex restore:YES];
|
|
3641
|
+
}
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3259
3644
|
if ([keyPath isEqualToString:@"contentInset"]) {
|
|
3260
|
-
[self updateHeaderForScrollView:(UIScrollView *)object];
|
|
3645
|
+
if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:(UIScrollView *)object];
|
|
3261
3646
|
return;
|
|
3262
3647
|
}
|
|
3263
3648
|
if ([keyPath isEqualToString:@"contentSize"]) {
|
|
3264
3649
|
NSValue *oldSize = change[NSKeyValueChangeOldKey];
|
|
3265
3650
|
NSValue *newSize = change[NSKeyValueChangeNewKey];
|
|
3266
3651
|
if (![oldSize isEqual:newSize]) {
|
|
3267
|
-
|
|
3652
|
+
BOOL restoreAlignedBody =
|
|
3653
|
+
_observedPageGeneration == _generation && _observedPageIndex == _currentIndex &&
|
|
3654
|
+
_observedPageKey != nil && [_observedPageKey isEqualToString:self.currentPageKey] &&
|
|
3655
|
+
[_alignedBodyKeys containsObject:_observedPageKey] &&
|
|
3656
|
+
![_commandBodyKeys containsObject:_observedPageKey] &&
|
|
3657
|
+
_observedScrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
|
|
3658
|
+
!_observedScrollView.isDragging && !_observedScrollView.isDecelerating;
|
|
3659
|
+
[self applyInsetsToScrollView:_observedScrollView pageIndex:_currentIndex restore:restoreAlignedBody];
|
|
3268
3660
|
}
|
|
3269
3661
|
return;
|
|
3270
3662
|
}
|
|
3271
3663
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
3272
3664
|
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3273
|
-
|
|
3665
|
+
#if DEBUG
|
|
3666
|
+
BOOL offsetDiagChanged = _pageOffsets[self.currentPageKey] == nil || _pageOffsets[self.currentPageKey].doubleValue != (_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3667
|
+
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-before" page:_currentIndex scroll:scrollView];
|
|
3668
|
+
#endif
|
|
3669
|
+
if ([_alignedBodyKeys containsObject:_observedPageKey] ||
|
|
3670
|
+
(_nativeSmoothHeaderScrollEnabled && scrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
|
|
3671
|
+
!scrollView.isDecelerating && ![_commandBodyKeys containsObject:_observedPageKey])) return;
|
|
3672
|
+
_pageOffsets[_nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey] =
|
|
3673
|
+
@(_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3674
|
+
#if DEBUG
|
|
3675
|
+
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-after" page:_currentIndex scroll:scrollView];
|
|
3676
|
+
#endif
|
|
3274
3677
|
[self updateHeaderForScrollView:scrollView];
|
|
3275
3678
|
return;
|
|
3276
3679
|
}
|
|
@@ -3279,6 +3682,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3279
3682
|
|
|
3280
3683
|
- (void)updateHeaderForScrollView:(UIScrollView *)scrollView
|
|
3281
3684
|
{
|
|
3685
|
+
if ([self matchesPagerTop:scrollView]) {
|
|
3686
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3687
|
+
_headerOffset = _pagerTopLogicalTarget;
|
|
3688
|
+
[self applyHeaderOffset];
|
|
3689
|
+
[self finishPagerTopIfReached];
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
if ([_alignedBodyKeys containsObject:self.currentPageKey] ||
|
|
3693
|
+
(_nativeSmoothHeaderScrollEnabled && (_syntheticPageRestore || _transitionPageOffsets != nil ||
|
|
3694
|
+
[_pendingPageRestoreKeys containsObject:self.currentPageKey]))) return;
|
|
3282
3695
|
CGFloat logicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3283
3696
|
_currentLogicalOffset = logicalOffset;
|
|
3284
3697
|
_headerOffset = MIN(MAX(logicalOffset, 0), _headerHeight);
|
|
@@ -3287,6 +3700,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3287
3700
|
|
|
3288
3701
|
- (void)applyHeaderOffset
|
|
3289
3702
|
{
|
|
3703
|
+
[self updateHeaderSettledStateForceBusy:NO];
|
|
3290
3704
|
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
3291
3705
|
_sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
|
|
3292
3706
|
if (attachedToList) {
|
|
@@ -3322,6 +3736,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3322
3736
|
|
|
3323
3737
|
- (void)goTo:(NSInteger)index animated:(BOOL)animated
|
|
3324
3738
|
{
|
|
3739
|
+
[self offsetDiag:@"go-to" page:index scroll:_observedScrollView];
|
|
3325
3740
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
3326
3741
|
_hasReceivedPageCommand = YES;
|
|
3327
3742
|
_hasAppliedInitialPage = YES;
|
|
@@ -3347,6 +3762,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3347
3762
|
return;
|
|
3348
3763
|
}
|
|
3349
3764
|
|
|
3765
|
+
[self beginPageStateTransition];
|
|
3350
3766
|
[self preparePageForHorizontalTransitionAtIndex:index];
|
|
3351
3767
|
_destinationIndex = index;
|
|
3352
3768
|
[self liftSharedHeadersForPagerTransition:@"programmatic"];
|
|
@@ -3366,6 +3782,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3366
3782
|
void (^animations)(void) = ^{
|
|
3367
3783
|
[self->_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0)];
|
|
3368
3784
|
[self->_nativeTabBarView setProgress:index];
|
|
3785
|
+
[self updatePageStateTransitionProgress:index];
|
|
3369
3786
|
};
|
|
3370
3787
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
|
3371
3788
|
__strong __typeof__(weakSelf) self = weakSelf;
|
|
@@ -3373,6 +3790,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3373
3790
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
3374
3791
|
self->_pagerScrollView.scrollEnabled = self->_scrollEnabled;
|
|
3375
3792
|
if (finished) {
|
|
3793
|
+
[self detachScrollObserver];
|
|
3376
3794
|
self->_currentIndex = index;
|
|
3377
3795
|
self->_destinationIndex = index;
|
|
3378
3796
|
[self->_nativeTabBarView setProgress:index];
|
|
@@ -3410,6 +3828,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3410
3828
|
if (self == nil || self->_generation != capturedGeneration ||
|
|
3411
3829
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
3412
3830
|
if (finished) {
|
|
3831
|
+
[self detachScrollObserver];
|
|
3413
3832
|
self->_currentIndex = index;
|
|
3414
3833
|
self->_destinationIndex = index;
|
|
3415
3834
|
[self->_nativeTabBarView setProgress:index];
|
|
@@ -3434,6 +3853,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3434
3853
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
3435
3854
|
if (self->_generation != generation || self->_transitionId != transition ||
|
|
3436
3855
|
self->_isBeingRecycled) return;
|
|
3856
|
+
[self finishPageStateTransition];
|
|
3437
3857
|
self->_transitioning = NO;
|
|
3438
3858
|
if (self->_hasPendingDirectNativePagerChange) {
|
|
3439
3859
|
BOOL enabled = self->_pendingDirectNativePagerEnabled;
|
|
@@ -3497,9 +3917,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3497
3917
|
|
|
3498
3918
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
3499
3919
|
{
|
|
3920
|
+
[self updateHeaderSettledStateForceBusy:YES];
|
|
3921
|
+
[self offsetDiag:@"pager-drag-begin" page:_currentIndex scroll:_observedScrollView];
|
|
3500
3922
|
if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
|
|
3501
3923
|
[_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
|
|
3502
3924
|
}
|
|
3925
|
+
[self beginPageStateTransition];
|
|
3503
3926
|
if (_directNativePagerEnabled) {
|
|
3504
3927
|
[self preparePageForHorizontalTransitionAtIndex:_currentIndex - 1];
|
|
3505
3928
|
[self preparePageForHorizontalTransitionAtIndex:_currentIndex + 1];
|
|
@@ -3651,6 +4074,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3651
4074
|
NSInteger position = (NSInteger)floor(progress);
|
|
3652
4075
|
CGFloat offset = progress - position;
|
|
3653
4076
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
4077
|
+
[self updatePageStateTransitionProgress:progress];
|
|
3654
4078
|
[_nativeTabBarView setProgress:progress];
|
|
3655
4079
|
const auto emitter = [self eventEmitter];
|
|
3656
4080
|
if (emitter) {
|
|
@@ -3668,6 +4092,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3668
4092
|
CGFloat transitionProgress = RNCClamp(fabs(rawOffset), 0, 1);
|
|
3669
4093
|
tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
|
|
3670
4094
|
}
|
|
4095
|
+
[self updatePageStateTransitionProgress:tabProgress];
|
|
3671
4096
|
[_nativeTabBarView setProgress:tabProgress];
|
|
3672
4097
|
const auto emitter = [self eventEmitter];
|
|
3673
4098
|
if (emitter) {
|
|
@@ -3685,10 +4110,98 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3685
4110
|
|
|
3686
4111
|
- (void)emitPageSelected:(NSInteger)position
|
|
3687
4112
|
{
|
|
4113
|
+
[self offsetDiag:@"selection-complete" page:position scroll:_observedScrollView];
|
|
3688
4114
|
const auto emitter = [self eventEmitter];
|
|
3689
4115
|
if (emitter) emitter->onPageSelected({.position = (double)position});
|
|
3690
4116
|
}
|
|
3691
4117
|
|
|
4118
|
+
- (void)publishHeaderSettledAtTop:(BOOL)settled identityChanged:(BOOL)identityChanged
|
|
4119
|
+
{
|
|
4120
|
+
if (_headerSettledAtTop == settled && !identityChanged) return;
|
|
4121
|
+
_headerSettledAtTop = settled;
|
|
4122
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:@"header-settled-state page=%ld generation=%lu settled=%d",
|
|
4123
|
+
(long)_currentIndex, (unsigned long)_generation, settled]);
|
|
4124
|
+
[self emitDiagnostics:@"header-settled-state"];
|
|
4125
|
+
}
|
|
4126
|
+
|
|
4127
|
+
- (void)cancelHeaderSettledCheck
|
|
4128
|
+
{
|
|
4129
|
+
[_headerSettledTimer invalidate];
|
|
4130
|
+
_headerSettledTimer = nil;
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
- (void)updateHeaderSettledStateForceBusy:(BOOL)forceBusy
|
|
4134
|
+
{
|
|
4135
|
+
UIScrollView *owner = _observedScrollView;
|
|
4136
|
+
NSString *key = self.currentPageKey;
|
|
4137
|
+
BOOL valid = !_isBeingRecycled && self.window != nil && _headerView.window == self.window &&
|
|
4138
|
+
_headerHeight > 0 && owner.window == self.window && owner != nil &&
|
|
4139
|
+
CGRectGetWidth(owner.bounds) > 0 && CGRectGetHeight(owner.bounds) > 0 &&
|
|
4140
|
+
_observedPageGeneration == _generation && _observedPageIndex == _currentIndex &&
|
|
4141
|
+
_currentIndex >= 0 && _currentIndex < _pageControllers.count &&
|
|
4142
|
+
[_observedPageKey isEqualToString:key] && [owner isDescendantOfView:_pageControllers[_currentIndex].view];
|
|
4143
|
+
BOOL identityChanged = _headerSettledOwner != owner || ![_headerSettledKey isEqualToString:key] ||
|
|
4144
|
+
_headerSettledGeneration != _generation;
|
|
4145
|
+
if (identityChanged) {
|
|
4146
|
+
[self cancelHeaderSettledCheck];
|
|
4147
|
+
_headerCommandOwner = nil;
|
|
4148
|
+
_headerSettledOwner = owner;
|
|
4149
|
+
_headerSettledKey = [key copy];
|
|
4150
|
+
_headerSettledGeneration = _generation;
|
|
4151
|
+
_headerSettledGeometry = nil;
|
|
4152
|
+
_headerSettledWasBusy = YES;
|
|
4153
|
+
[self publishHeaderSettledAtTop:NO identityChanged:YES];
|
|
4154
|
+
}
|
|
4155
|
+
if (!valid || fabs(_headerOffset) > 0.5) {
|
|
4156
|
+
[self cancelHeaderSettledCheck];
|
|
4157
|
+
_headerSettledGeometry = nil;
|
|
4158
|
+
_headerSettledWasBusy = YES;
|
|
4159
|
+
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
4162
|
+
CGRect headerRect = [_headerView convertRect:_headerView.bounds toView:self.window];
|
|
4163
|
+
CGRect ownerRect = [owner convertRect:owner.bounds toView:self.window];
|
|
4164
|
+
UIEdgeInsets inset = owner.contentInset;
|
|
4165
|
+
UIEdgeInsets adjustedInset = owner.adjustedContentInset;
|
|
4166
|
+
NSString *geometry = [NSString stringWithFormat:
|
|
4167
|
+
@"%.4f:%ld:%ld:%@:%@:%@:%@:%@:%@:%@",
|
|
4168
|
+
_headerOffset, (long)_headerHeight, (long)_stickyHeaderHeight,
|
|
4169
|
+
NSStringFromCGPoint(owner.contentOffset), NSStringFromCGSize(owner.contentSize),
|
|
4170
|
+
NSStringFromCGRect(owner.bounds), NSStringFromUIEdgeInsets(inset),
|
|
4171
|
+
NSStringFromUIEdgeInsets(adjustedInset), NSStringFromCGRect(headerRect), NSStringFromCGRect(ownerRect)];
|
|
4172
|
+
UIGestureRecognizerState panState = owner.panGestureRecognizer.state;
|
|
4173
|
+
BOOL busy = forceBusy || _transitioning || _isPagerDragging || _transitionPageOffsets != nil ||
|
|
4174
|
+
_syntheticPageRestore || _pagerTopPageKey != nil || _headerCommandOwner == owner ||
|
|
4175
|
+
owner.isDragging || owner.isDecelerating || owner.isTracking ||
|
|
4176
|
+
panState == UIGestureRecognizerStateBegan || panState == UIGestureRecognizerStateChanged;
|
|
4177
|
+
CFTimeInterval now = CACurrentMediaTime();
|
|
4178
|
+
if (![geometry isEqualToString:_headerSettledGeometry] || busy || _headerSettledWasBusy) {
|
|
4179
|
+
_headerSettledSince = now;
|
|
4180
|
+
_headerSettledGeometry = geometry;
|
|
4181
|
+
[self cancelHeaderSettledCheck];
|
|
4182
|
+
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
4183
|
+
}
|
|
4184
|
+
_headerSettledWasBusy = busy;
|
|
4185
|
+
if (!busy && now - _headerSettledSince >= 0.15) {
|
|
4186
|
+
[self cancelHeaderSettledCheck];
|
|
4187
|
+
[self publishHeaderSettledAtTop:YES identityChanged:NO];
|
|
4188
|
+
return;
|
|
4189
|
+
}
|
|
4190
|
+
if (_headerSettledTimer == nil) {
|
|
4191
|
+
__weak RNCCollapsiblePagerViewComponentView *weakSelf = self;
|
|
4192
|
+
__weak UIScrollView *weakOwner = owner;
|
|
4193
|
+
NSUInteger generation = _generation;
|
|
4194
|
+
_headerSettledTimer = [NSTimer scheduledTimerWithTimeInterval:0.15 repeats:NO block:^(NSTimer *timer) {
|
|
4195
|
+
RNCCollapsiblePagerViewComponentView *strongSelf = weakSelf;
|
|
4196
|
+
if (strongSelf == nil) return;
|
|
4197
|
+
if (strongSelf->_headerSettledTimer != timer) return;
|
|
4198
|
+
strongSelf->_headerSettledTimer = nil;
|
|
4199
|
+
if (strongSelf->_generation == generation && [strongSelf.currentPageKey isEqualToString:key] &&
|
|
4200
|
+
strongSelf->_observedScrollView == weakOwner) [strongSelf updateHeaderSettledStateForceBusy:NO];
|
|
4201
|
+
}];
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
|
|
3692
4205
|
- (void)emitDiagnostics:(NSString *)reason
|
|
3693
4206
|
{
|
|
3694
4207
|
const auto emitter = [self eventEmitter];
|
|
@@ -3704,7 +4217,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3704
4217
|
.attachedPageCount = (int)attached,
|
|
3705
4218
|
.observedScrollableCount = (int)_originalInsets.count,
|
|
3706
4219
|
.retainedPages = std::string(_retainedPages.UTF8String ?: "[]"),
|
|
3707
|
-
.reason = std::string(reason.UTF8String ?: "unknown")
|
|
4220
|
+
.reason = std::string(reason.UTF8String ?: "unknown"),
|
|
4221
|
+
.pageKey = std::string(self.currentPageKey.UTF8String ?: ""),
|
|
4222
|
+
.headerSettledAtTop = (bool)_headerSettledAtTop
|
|
3708
4223
|
});
|
|
3709
4224
|
}
|
|
3710
4225
|
|
|
@@ -3713,6 +4228,77 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3713
4228
|
RCTRNCCollapsiblePagerViewHandleCommand(self, commandName, args);
|
|
3714
4229
|
}
|
|
3715
4230
|
|
|
4231
|
+
- (BOOL)matchesPagerTop:(UIScrollView *)scrollView
|
|
4232
|
+
{
|
|
4233
|
+
return scrollView != nil && scrollView == _pagerTopScrollView && scrollView == _observedScrollView &&
|
|
4234
|
+
_pagerTopPageKey != nil && [_pagerTopPageKey isEqualToString:self.currentPageKey] &&
|
|
4235
|
+
[_observedPageKey isEqualToString:_pagerTopPageKey] && _pagerTopPageIndex == _currentIndex &&
|
|
4236
|
+
_observedPageIndex == _currentIndex && _pagerTopGeneration == _generation &&
|
|
4237
|
+
_observedPageGeneration == _generation && scrollView.window != nil;
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
- (void)cancelPagerTop
|
|
4241
|
+
{
|
|
4242
|
+
if (_pagerTopPageKey != nil) [_commandBodyKeys removeObject:_pagerTopPageKey];
|
|
4243
|
+
_pagerTopScrollView = nil;
|
|
4244
|
+
_pagerTopPageKey = nil;
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
- (void)finishPagerTopIfReached
|
|
4248
|
+
{
|
|
4249
|
+
UIScrollView *scrollView = _pagerTopScrollView;
|
|
4250
|
+
if (_dispatchingPagerTop || ![self matchesPagerTop:scrollView]) return;
|
|
4251
|
+
CGFloat logical = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
4252
|
+
if (fabs(logical - _pagerTopLogicalTarget) > 0.5) return;
|
|
4253
|
+
_pageOffsets[_pagerTopPageKey] = @(_nativeSmoothHeaderScrollEnabled ? 0 : _pagerTopLogicalTarget);
|
|
4254
|
+
[_alignedBodyKeys addObject:_pagerTopPageKey];
|
|
4255
|
+
_currentLogicalOffset = logical;
|
|
4256
|
+
_headerOffset = _pagerTopLogicalTarget;
|
|
4257
|
+
[self applyHeaderOffset];
|
|
4258
|
+
[self offsetDiag:[NSString stringWithFormat:@"pager-top-complete-token=%lu-target=%.4f", (unsigned long)_pagerTopToken, _pagerTopLogicalTarget] page:_currentIndex scroll:scrollView];
|
|
4259
|
+
[self cancelPagerTop];
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
- (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
|
|
4263
|
+
{
|
|
4264
|
+
UIScrollView *scrollView = _observedScrollView;
|
|
4265
|
+
NSString *key = _observedPageKey;
|
|
4266
|
+
if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
4267
|
+
_transitionPageOffsets != nil || _syntheticPageRestore || scrollView == nil || scrollView.window == nil ||
|
|
4268
|
+
key == nil || ![key isEqualToString:self.currentPageKey] || _observedPageIndex != _currentIndex ||
|
|
4269
|
+
_observedPageGeneration != _generation || self.window == nil) return;
|
|
4270
|
+
[self cancelPagerTop];
|
|
4271
|
+
// Old List phase2 notifications must run before the new target owns this page.
|
|
4272
|
+
[NSNotificationCenter.defaultCenter postNotificationName:@"OneKeyNativeListCancelScrollCommand" object:scrollView];
|
|
4273
|
+
if ([scrollView.superview isKindOfClass:RNCNativeScrollerComponentView.class]) {
|
|
4274
|
+
[(RNCNativeScrollerComponentView *)scrollView.superview stopScrolling];
|
|
4275
|
+
} else {
|
|
4276
|
+
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
|
|
4277
|
+
}
|
|
4278
|
+
[_pendingPageRestoreKeys removeObject:key];
|
|
4279
|
+
[_alignedBodyKeys removeObject:key];
|
|
4280
|
+
[_suspendedBodyKeys removeObject:key];
|
|
4281
|
+
[_displayBodyOverrides removeObjectForKey:key];
|
|
4282
|
+
CGFloat logicalTarget = target == 1 ? _headerHeight : 0;
|
|
4283
|
+
_pageOffsets[key] = @(_nativeSmoothHeaderScrollEnabled ? 0 : logicalTarget);
|
|
4284
|
+
_pagerTopScrollView = scrollView;
|
|
4285
|
+
_pagerTopPageKey = [key copy];
|
|
4286
|
+
_pagerTopPageIndex = _currentIndex;
|
|
4287
|
+
_pagerTopGeneration = _generation;
|
|
4288
|
+
_pagerTopLogicalTarget = logicalTarget;
|
|
4289
|
+
_pagerTopToken++;
|
|
4290
|
+
[_commandBodyKeys addObject:key];
|
|
4291
|
+
_headerOffset = logicalTarget;
|
|
4292
|
+
[self applyHeaderOffset];
|
|
4293
|
+
CGFloat physicalTarget = RNCLimitRestoredScrollOffset(logicalTarget - scrollView.contentInset.top,
|
|
4294
|
+
scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
|
|
4295
|
+
[self offsetDiag:[NSString stringWithFormat:@"pager-top-begin-token=%lu-target=%ld-animated=%d", (unsigned long)_pagerTopToken, (long)target, animated] page:_currentIndex scroll:scrollView];
|
|
4296
|
+
_dispatchingPagerTop = YES;
|
|
4297
|
+
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:animated];
|
|
4298
|
+
_dispatchingPagerTop = NO;
|
|
4299
|
+
[self finishPagerTopIfReached];
|
|
4300
|
+
}
|
|
4301
|
+
|
|
3716
4302
|
- (void)setPage:(NSInteger)index
|
|
3717
4303
|
{
|
|
3718
4304
|
[self goTo:index animated:YES];
|