@onekeyfe/react-native-pager-view 3.0.179-alpha.2026101503 → 3.0.179-alpha.302

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.
@@ -1,5 +1,4 @@
1
1
  #import "RNCCollapsiblePagerViewComponentView.h"
2
- #import "RNCPagerViewComponentView.h"
3
2
  #import "RNCNativeScrollerComponentView.h"
4
3
  #import "RNCCollapsiblePagerReleasedStatePolicy.h"
5
4
 
@@ -195,23 +194,6 @@ static BOOL RNCIsEnabledHorizontalScrollOwner(UIScrollView *scroll)
195
194
  scroll.contentSize.height <= CGRectGetHeight(scroll.bounds) + 0.5;
196
195
  }
197
196
 
198
- static UIScrollView *RNCNestedOuterReactPagerScrollView(UIView *view)
199
- {
200
- Class outerPagerClass = NSClassFromString(@"RNCPagerViewComponentView");
201
- if (outerPagerClass == Nil) return nil;
202
- UIScrollView *pagingAncestor = nil;
203
- for (UIView *ancestor = view.superview; ancestor != nil; ancestor = ancestor.superview) {
204
- if ([ancestor isKindOfClass:UIScrollView.class] &&
205
- ((UIScrollView *)ancestor).pagingEnabled) {
206
- pagingAncestor = (UIScrollView *)ancestor;
207
- }
208
- if ([ancestor isKindOfClass:outerPagerClass]) {
209
- return pagingAncestor.panGestureRecognizer.enabled ? pagingAncestor : nil;
210
- }
211
- }
212
- return nil;
213
- }
214
-
215
197
  static void RNCGuardNestedHorizontalPans(UIView *view, UIGestureRecognizer *guard)
216
198
  {
217
199
  if ([view isKindOfClass:UIScrollView.class]) {
@@ -237,7 +219,6 @@ static void RNCCollapsiblePagerLog(NSString *message)
237
219
  logFunction(logClass, selector, @"CollapsiblePager", message);
238
220
  }
239
221
 
240
-
241
222
  static BOOL RNCGetColorComponents(UIColor *color, UITraitCollection *traits,
242
223
  CGFloat *red, CGFloat *green, CGFloat *blue, CGFloat *alpha)
243
224
  {
@@ -1061,9 +1042,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1061
1042
  CGFloat maximum = MAX(0, inner.contentSize.width - CGRectGetWidth(inner.bounds));
1062
1043
  BOOL canScroll = maximum > 0 && (intent.x < 0
1063
1044
  ? inner.contentOffset.x < maximum - 0.5 : inner.contentOffset.x > 0.5);
1064
- if (canScroll) {
1065
- return NO;
1066
- }
1045
+ if (canScroll) return NO;
1067
1046
  }
1068
1047
  }
1069
1048
  if (gestureRecognizer == self.panGestureRecognizer && self.excludedHeaderView != nil) {
@@ -1073,8 +1052,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1073
1052
  return NO;
1074
1053
  }
1075
1054
  }
1076
- BOOL result = [super gestureRecognizerShouldBegin:gestureRecognizer];
1077
- return result;
1055
+ return [super gestureRecognizerShouldBegin:gestureRecognizer];
1078
1056
  }
1079
1057
 
1080
1058
  @end
@@ -1263,8 +1241,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1263
1241
 
1264
1242
  @interface RNCCollapsiblePagerOuterPagerPanGestureRecognizer : UIPanGestureRecognizer
1265
1243
  @property (nonatomic, strong) NSHashTable<UIGestureRecognizer *> *blockedPagerGestures;
1266
- @property (nonatomic, weak) UITouch *trackedTouch;
1267
- @property (nonatomic, assign) BOOL protectsNestedBody;
1268
1244
  @end
1269
1245
 
1270
1246
  @implementation RNCCollapsiblePagerOuterPagerPanGestureRecognizer
@@ -1289,14 +1265,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1289
1265
  return NO;
1290
1266
  }
1291
1267
 
1292
- - (void)reset
1293
- {
1294
- [super reset];
1295
- self.trackedTouch = nil;
1296
- self.protectsNestedBody = NO;
1297
- self.cancelsTouchesInView = YES;
1298
- }
1299
-
1300
1268
  @end
1301
1269
 
1302
1270
  @interface RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer : UIGestureRecognizer
@@ -1440,8 +1408,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1440
1408
  UIScrollViewDelegate,
1441
1409
  UIGestureRecognizerDelegate,
1442
1410
  RCTMountingTransactionObserving,
1443
- RNCNativeScrollerInsetCoordinator,
1444
- RNCPagerViewNestedOverlayObserver
1411
+ RNCNativeScrollerInsetCoordinator
1445
1412
  >
1446
1413
  - (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
1447
1414
  - (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
@@ -1451,8 +1418,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1451
1418
  - (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
1452
1419
  - (void)restoreSharedHeadersToContainer;
1453
1420
  - (void)layoutSharedHeaders;
1454
- - (void)restorePinnedStickyHeader;
1455
- - (void)updatePinnedStickyHeader;
1456
1421
  - (UIScrollView *)currentVerticalScrollViewForParentPager;
1457
1422
  - (UIScrollView *)verticalScrollViewForPageAtIndex:(NSInteger)pageIndex;
1458
1423
  - (UIScrollView *)findExplicitNativeScrollerInView:(UIView *)view;
@@ -1464,32 +1429,19 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1464
1429
  - (void)detachSharedHeaderPressCancellationGesture;
1465
1430
  - (void)updateRefreshControlForScrollView:(UIScrollView *)scrollView;
1466
1431
  - (void)releaseRefreshControlForScrollView:(UIScrollView *)scrollView;
1467
- - (void)cancelTopInsetReboundCheck;
1468
- - (void)scheduleTopInsetReboundCheck;
1469
- - (void)syncScrollEdgeAppearance;
1470
- - (void)updateScrollEdgeContentMasks;
1471
- - (void)restoreScrollEdgeContentMasks;
1472
1432
  - (void)sharedHeaderPressCancellationGestureChanged:(UIPanGestureRecognizer *)recognizer;
1473
1433
  - (void)outerPagerGestureChanged:(UIPanGestureRecognizer *)recognizer;
1474
1434
  - (void)verticalPagerGuardGestureChanged:(UIPanGestureRecognizer *)recognizer;
1475
1435
  - (void)connectVerticalPagerGuard;
1476
1436
  - (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer;
1477
- - (BOOL)resolveExternalTopOverlay;
1478
- - (void)registerExternalOuterPager;
1479
- - (CGFloat)effectiveTopOverlayHeight;
1480
- - (BOOL)hasFixedTopOverlay;
1481
1437
  @end
1482
1438
 
1483
1439
  @implementation RNCCollapsiblePagerViewComponentView {
1440
+ #if DEBUG
1441
+ NSUInteger _offsetDiagSequence;
1442
+ #endif
1484
1443
  UIView *_containerView;
1485
1444
  RNCCollapsiblePagerSharedHeaderView *_sharedHeaderHostView;
1486
- UIView *_pinnedStickyHostView;
1487
- BOOL _stickyHeaderIsPinned;
1488
- UIView *_sharedHeaderMaskView;
1489
- UIView *_originalSharedHeaderMaskView;
1490
- NSMapTable<UIView *, id> *_originalPageMasks;
1491
- NSMapTable<UIView *, UIView *> *_ownedPageMasks;
1492
- CGFloat _pageMaskBoundary;
1493
1445
  UIPageViewController *_pageViewController;
1494
1446
  UIScrollView *_pageViewControllerScrollView;
1495
1447
  UIScrollView *_pagerScrollView;
@@ -1497,16 +1449,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1497
1449
  NSMutableArray<UIViewController *> *_pageControllers;
1498
1450
  UIView *_headerView;
1499
1451
  UIView *_stickyHeaderView;
1500
- UIView *_topOverlayView;
1501
- UIView *_topOverlayHostView;
1502
- UIScrollEdgeElementContainerInteraction *_topEdgeInteraction;
1503
- UIScrollEdgeElementContainerInteraction *_stickyEdgeInteraction;
1504
- UILabel *_topEdgeMarker;
1505
- UILabel *_stickyEdgeMarker;
1506
- __weak UIView *_topEdgeInteractionHost;
1507
- __weak UIView *_stickyEdgeInteractionHost;
1508
- __weak UIScrollView *_topEdgeScrollView;
1509
- UIScrollEdgeEffectStyle *_originalTopEdgeStyle;
1510
1452
  RNCCollapsiblePagerNativeTabBarView *_nativeTabBarView;
1511
1453
  RNCCollapsiblePagerNativeSubHeaderView *_nativeSubHeaderView;
1512
1454
  NSInteger _currentIndex;
@@ -1514,22 +1456,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1514
1456
  NSInteger _pendingInitialPage;
1515
1457
  NSInteger _headerHeight;
1516
1458
  NSInteger _stickyHeaderHeight;
1517
- CGFloat _topOverlayHeight;
1518
- NSInteger _externalTopOverlayTag;
1519
- __weak UIView *_externalTopOverlayView;
1520
- CGFloat _externalTopOverlayHeight;
1521
- __weak RNCPagerViewComponentView *_externalOuterPager;
1522
- BOOL _externalOuterPageActive;
1523
- BOOL _externalOuterTransitioning;
1524
- BOOL _topScrollEdgeHard;
1525
- NSString *_topOverlayColorScheme;
1526
- BOOL _pendingTopOverlayAppearanceCommit;
1527
- BOOL _applyingTopOverlayInsetChange;
1528
- BOOL _correctingTopInsetRebound;
1529
- __weak UIScrollView *_pendingTopInsetReboundScrollView;
1530
- NSString *_pendingTopInsetReboundPageKey;
1531
- NSUInteger _pendingTopInsetReboundGeneration;
1532
- NSTimer *_topInsetReboundTimer;
1533
1459
  CGFloat _nativeTabBarHeight;
1534
1460
  CGFloat _nativeSubHeaderHeight;
1535
1461
  CGFloat _headerOffset;
@@ -1549,7 +1475,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1549
1475
  NSInteger _pendingGoToIndex;
1550
1476
  BOOL _pendingGoToAnimated;
1551
1477
  BOOL _needsSlotRebuild;
1552
- BOOL _deferTopEdgeSlotRebuild;
1553
1478
  BOOL _hasAppliedInitialPage;
1554
1479
  BOOL _needsPropsReapply;
1555
1480
  NSString *_layoutDirection;
@@ -1587,6 +1512,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1587
1512
  NSUInteger _observedPageGeneration;
1588
1513
  NSDictionary<NSString *, NSNumber *> *_transitionPageOffsets;
1589
1514
  NSMutableIndexSet *_transitionCompensatedPages;
1515
+ #if DEBUG
1516
+ NSInteger _transitionDiagBucket;
1517
+ #endif
1590
1518
  NSInteger _transitionSourceIndex;
1591
1519
  BOOL _syntheticPageRestore;
1592
1520
  BOOL _headerSettledAtTop;
@@ -1652,8 +1580,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1652
1580
  _originalAlwaysBounceVertical = [NSMapTable weakToStrongObjectsMapTable];
1653
1581
  _originalDirectionalLockEnabled = [NSMapTable weakToStrongObjectsMapTable];
1654
1582
  _originalInsetAdjustmentBehavior = [NSMapTable weakToStrongObjectsMapTable];
1655
- _originalPageMasks = [NSMapTable strongToStrongObjectsMapTable];
1656
- _ownedPageMasks = [NSMapTable strongToStrongObjectsMapTable];
1657
1583
  _pageKeys = @[];
1658
1584
  _retainedPages = @"[]";
1659
1585
  _currentIndex = 0;
@@ -1681,12 +1607,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1681
1607
  _sharedHeaderHostView = [RNCCollapsiblePagerSharedHeaderView new];
1682
1608
  _sharedHeaderHostView.clipsToBounds = NO;
1683
1609
  [_containerView addSubview:_sharedHeaderHostView];
1684
- _pinnedStickyHostView = [UIView new];
1685
- _pinnedStickyHostView.hidden = YES;
1686
- [_containerView addSubview:_pinnedStickyHostView];
1687
- _topOverlayHostView = [UIView new];
1688
- _topOverlayHostView.hidden = YES;
1689
- [_containerView addSubview:_topOverlayHostView];
1690
1610
 
1691
1611
  _verticalPagerGesture =
1692
1612
  [[RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer alloc]
@@ -1744,39 +1664,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1744
1664
  viewProps.pointerEvents == PointerEventsMode::BoxOnly) {
1745
1665
  return [super hitTest:point withEvent:event];
1746
1666
  }
1747
- if (_topOverlayView != nil) {
1748
- CGPoint overlayPoint = [_topOverlayView convertPoint:point fromView:self];
1749
- UIView *overlayHitView = [_topOverlayView hitTest:overlayPoint withEvent:event];
1750
- if (overlayHitView != nil) return overlayHitView;
1751
- }
1752
- if (_stickyHeaderIsPinned) {
1753
- CGPoint stickyPoint = [_pinnedStickyHostView convertPoint:point fromView:self];
1754
- UIView *stickyHitView = [_pinnedStickyHostView hitTest:stickyPoint withEvent:event];
1755
- if (stickyHitView != nil) return stickyHitView;
1756
- }
1757
- CGPoint headerPoint = [_sharedHeaderHostView convertPoint:point fromView:self];
1758
- BOOL sharedHeaderPointIsVisible =
1759
- _sharedHeaderHostView.maskView != _sharedHeaderMaskView ||
1760
- _sharedHeaderMaskView == nil ||
1761
- CGRectContainsPoint(_sharedHeaderMaskView.frame, headerPoint);
1762
1667
  if (_nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
1763
- _sharedHeaderHostView.superview != _containerView &&
1764
- sharedHeaderPointIsVisible) {
1668
+ _sharedHeaderHostView.superview != _containerView) {
1669
+ CGPoint headerPoint = [_sharedHeaderHostView convertPoint:point fromView:self];
1765
1670
  UIView *headerHitView =
1766
1671
  [_sharedHeaderHostView hitTest:headerPoint withEvent:event];
1767
1672
  if (headerHitView != nil) return headerHitView;
1768
1673
  }
1769
- UIView *hitView = [super hitTest:point withEvent:event];
1770
- if (hitView != nil && !sharedHeaderPointIsVisible &&
1771
- (hitView == _sharedHeaderHostView ||
1772
- [hitView isDescendantOfView:_sharedHeaderHostView])) return nil;
1773
- if (hitView != nil && _ownedPageMasks.count > 0 &&
1774
- [_containerView convertPoint:point fromView:self].y < _pageMaskBoundary) {
1775
- for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
1776
- if ([hitView isDescendantOfView:page]) return nil;
1777
- }
1778
- }
1779
- return hitView;
1674
+ return [super hitTest:point withEvent:event];
1780
1675
  }
1781
1676
 
1782
1677
  - (void)willMoveToSuperview:(UIView *)newSuperview
@@ -1791,18 +1686,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1791
1686
  - (void)didMoveToWindow
1792
1687
  {
1793
1688
  [super didMoveToWindow];
1794
- [self registerExternalOuterPager];
1795
- BOOL externalHeightChanged = [self resolveExternalTopOverlay];
1796
- if (self.window == nil) [self restoreScrollEdgeContentMasks];
1797
- [self syncScrollEdgeAppearance];
1798
- [self emitTopScrollEdgeCapability];
1799
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
1800
1689
  [self updateSharedHeaderPressCancellationGesture];
1801
1690
  [self updateHeaderSettledStateForceBusy:NO];
1802
1691
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
1803
- [self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight + self.effectiveTopOverlayHeight];
1692
+ [self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight];
1804
1693
  }
1805
- if (externalHeightChanged) [self reapplyInsetsToObservedScrollView];
1806
1694
  // OneKey patch: removing a decelerating page from its window can suppress
1807
1695
  // UIKit's final scroll callback. Restore the last acknowledged page on reattach.
1808
1696
  if (self.window != nil && _isPagerDragging &&
@@ -1824,320 +1712,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1824
1712
  }
1825
1713
  }
1826
1714
 
1827
- - (CGFloat)effectiveTopOverlayHeight
1828
- {
1829
- return _topOverlayView != nil ? _topOverlayHeight : _externalTopOverlayHeight;
1830
- }
1831
-
1832
- - (BOOL)hasFixedTopOverlay
1833
- {
1834
- return _topOverlayView != nil ||
1835
- (_externalTopOverlayTag > 0 && _externalTopOverlayHeight > 0);
1836
- }
1837
-
1838
- - (void)registerExternalOuterPager
1839
- {
1840
- RNCPagerViewComponentView *outer = nil;
1841
- if (_externalTopOverlayTag > 0 && self.window != nil && !_isBeingRecycled) {
1842
- for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
1843
- if ([ancestor isKindOfClass:RNCPagerViewComponentView.class]) {
1844
- outer = (RNCPagerViewComponentView *)ancestor;
1845
- break;
1846
- }
1847
- }
1848
- }
1849
- if (_externalOuterPager == outer) return;
1850
- [_externalOuterPager removeNestedOverlayObserver:self];
1851
- _externalOuterPager = outer;
1852
- _externalOuterPageActive = NO;
1853
- _externalOuterTransitioning = NO;
1854
- [outer addNestedOverlayObserver:self];
1855
- }
1856
-
1857
- - (BOOL)resolveExternalTopOverlay
1858
- {
1859
- UIView *nextView = nil;
1860
- CGFloat nextHeight = 0;
1861
- if (_externalTopOverlayTag > 0 && _topOverlayView == nil &&
1862
- self.window != nil && !_isBeingRecycled) {
1863
- for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
1864
- UIView *candidate = [ancestor viewWithTag:_externalTopOverlayTag];
1865
- if (candidate == nil || candidate == self ||
1866
- [candidate isDescendantOfView:self] || candidate.window != self.window) continue;
1867
- nextView = candidate;
1868
- break;
1869
- }
1870
- if (nextView != nil && CGRectGetHeight(nextView.bounds) > 0) {
1871
- nextHeight = MAX(0, CGRectGetMaxY([nextView convertRect:nextView.bounds
1872
- toView:_containerView]));
1873
- }
1874
- }
1875
- if (nextView == nil && _externalOuterTransitioning &&
1876
- _externalTopOverlayTag > 0 && self.window != nil) {
1877
- nextHeight = _externalTopOverlayHeight;
1878
- }
1879
- BOOL changed = _externalTopOverlayView != nextView ||
1880
- fabs(_externalTopOverlayHeight - nextHeight) > 0.5;
1881
- _externalTopOverlayView = nextView;
1882
- _externalTopOverlayHeight = nextHeight;
1883
- _topOverlayHostView.hidden = !self.hasFixedTopOverlay;
1884
- _topOverlayHostView.userInteractionEnabled = _topOverlayView != nil;
1885
- return changed;
1886
- }
1887
-
1888
- - (void)outerPagerDidUpdateOverlayOwner:(BOOL)active transitioning:(BOOL)transitioning
1889
- {
1890
- if (_isBeingRecycled || _externalOuterPager == nil || _externalTopOverlayTag <= 0) return;
1891
- if (_externalOuterPageActive == active && _externalOuterTransitioning == transitioning) return;
1892
- BOOL wasTransitioning = _externalOuterTransitioning;
1893
- _externalOuterPageActive = active;
1894
- _externalOuterTransitioning = transitioning;
1895
- if (transitioning && !wasTransitioning) {
1896
- [self updateTopScrollEdgeForScrollView:nil];
1897
- [self liftSharedHeadersForPagerTransition:@"outer"];
1898
- } else if (!transitioning && wasTransitioning &&
1899
- !_transitioning && !_isPagerDragging && active) {
1900
- [self attachSharedHeadersToScrollView:_observedScrollView];
1901
- }
1902
- [self updateTopScrollEdgeForScrollView:transitioning ? nil : _observedScrollView];
1903
- [self updateScrollEdgeContentMasks];
1904
- [self setNeedsLayout];
1905
- }
1906
-
1907
- - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
1908
- {
1909
- [super traitCollectionDidChange:previousTraitCollection];
1910
- if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) {
1911
- [self syncScrollEdgeAppearance];
1912
- }
1913
- }
1914
-
1915
- - (void)syncScrollEdgeAppearance
1916
- {
1917
- BOOL active = _topScrollEdgeHard &&
1918
- (self.hasFixedTopOverlay || _stickyHeaderView != nil) && self.window != nil &&
1919
- !_isBeingRecycled;
1920
- UIUserInterfaceStyle style = UIUserInterfaceStyleUnspecified;
1921
- if (active) {
1922
- if ([_topOverlayColorScheme isEqualToString:@"dark"]) {
1923
- style = UIUserInterfaceStyleDark;
1924
- } else if ([_topOverlayColorScheme isEqualToString:@"light"]) {
1925
- style = UIUserInterfaceStyleLight;
1926
- } else {
1927
- style = self.traitCollection.userInterfaceStyle;
1928
- }
1929
- }
1930
- if (_topOverlayHostView.overrideUserInterfaceStyle == style) return;
1931
- _topOverlayHostView.overrideUserInterfaceStyle = style;
1932
- _pinnedStickyHostView.overrideUserInterfaceStyle = style;
1933
- }
1934
-
1935
- - (void)restoreScrollEdgeContentMasks
1936
- {
1937
- if (_sharedHeaderHostView.maskView == _sharedHeaderMaskView) {
1938
- _sharedHeaderHostView.maskView = _originalSharedHeaderMaskView;
1939
- }
1940
- _sharedHeaderMaskView = nil;
1941
- _originalSharedHeaderMaskView = nil;
1942
- for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
1943
- if (page.maskView == [_ownedPageMasks objectForKey:page]) {
1944
- id original = [_originalPageMasks objectForKey:page];
1945
- page.maskView = original == NSNull.null ? nil : original;
1946
- }
1947
- }
1948
- [_ownedPageMasks removeAllObjects];
1949
- [_originalPageMasks removeAllObjects];
1950
- _pageMaskBoundary = 0;
1951
- }
1952
-
1953
- - (void)updateScrollEdgeContentMasks
1954
- {
1955
- BOOL enabled = _topScrollEdgeHard &&
1956
- (self.hasFixedTopOverlay || _stickyHeaderView != nil) &&
1957
- self.window != nil && !_isBeingRecycled;
1958
- if (!enabled) {
1959
- [self restoreScrollEdgeContentMasks];
1960
- return;
1961
- }
1962
- if (self.hasFixedTopOverlay && _sharedHeaderHostView.window == self.window &&
1963
- CGRectGetHeight(_sharedHeaderHostView.bounds) > 0 &&
1964
- CGRectGetHeight(_topOverlayHostView.bounds) > 0) {
1965
- if (_sharedHeaderMaskView == nil) {
1966
- _originalSharedHeaderMaskView = _sharedHeaderHostView.maskView;
1967
- _sharedHeaderMaskView = [UIView new];
1968
- _sharedHeaderMaskView.backgroundColor = UIColor.blackColor;
1969
- _sharedHeaderHostView.maskView = _sharedHeaderMaskView;
1970
- }
1971
- CGFloat topBottom = CGRectGetMaxY([_topOverlayHostView
1972
- convertRect:_topOverlayHostView.bounds toView:_containerView]);
1973
- CGFloat localTop = [_containerView convertPoint:CGPointMake(0, topBottom)
1974
- toView:_sharedHeaderHostView].y;
1975
- _sharedHeaderMaskView.frame = CGRectMake(0, localTop,
1976
- CGRectGetWidth(_sharedHeaderHostView.bounds),
1977
- MAX(0, CGRectGetHeight(_sharedHeaderHostView.bounds) - localTop));
1978
- } else if (_sharedHeaderMaskView != nil) {
1979
- if (_sharedHeaderHostView.maskView == _sharedHeaderMaskView) {
1980
- _sharedHeaderHostView.maskView = _originalSharedHeaderMaskView;
1981
- }
1982
- _sharedHeaderMaskView = nil;
1983
- _originalSharedHeaderMaskView = nil;
1984
- }
1985
-
1986
- BOOL transitioning = _transitioning || _isPagerDragging || _externalOuterTransitioning ||
1987
- _sharedHeadersLiftedForPagerTransition;
1988
- CGFloat boundary = CGRectGetMaxY([_topOverlayHostView
1989
- convertRect:_topOverlayHostView.bounds toView:_containerView]);
1990
- UIView *sticky = _stickyHeaderIsPinned ? _pinnedStickyHostView : _stickyHeaderView;
1991
- if (sticky != nil && !sticky.hidden && sticky.window == self.window &&
1992
- CGRectGetHeight(sticky.bounds) > 0) {
1993
- boundary = MAX(boundary, CGRectGetMaxY([sticky
1994
- convertRect:sticky.bounds toView:_containerView]));
1995
- }
1996
- _pageMaskBoundary = boundary;
1997
- NSMutableSet<UIView *> *visiblePages = [NSMutableSet new];
1998
- if (transitioning) {
1999
- for (UIViewController *controller in _pageControllers) {
2000
- UIView *page = controller.view;
2001
- if (page.window != self.window ||
2002
- !CGRectIntersectsRect([page convertRect:page.bounds toView:_containerView],
2003
- _containerView.bounds)) continue;
2004
- [visiblePages addObject:page];
2005
- UIView *mask = [_ownedPageMasks objectForKey:page];
2006
- if (mask == nil) {
2007
- [_originalPageMasks setObject:page.maskView ?: NSNull.null forKey:page];
2008
- mask = [UIView new];
2009
- mask.backgroundColor = UIColor.blackColor;
2010
- [_ownedPageMasks setObject:mask forKey:page];
2011
- page.maskView = mask;
2012
- }
2013
- CGFloat localTop = [_containerView convertPoint:CGPointMake(0, boundary)
2014
- toView:page].y;
2015
- mask.frame = CGRectMake(0, localTop, CGRectGetWidth(page.bounds),
2016
- MAX(0, CGRectGetHeight(page.bounds) - localTop));
2017
- }
2018
- }
2019
- for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
2020
- if ([visiblePages containsObject:page]) continue;
2021
- UIView *mask = [_ownedPageMasks objectForKey:page];
2022
- if (page.maskView == mask) {
2023
- id original = [_originalPageMasks objectForKey:page];
2024
- page.maskView = original == NSNull.null ? nil : original;
2025
- }
2026
- [_ownedPageMasks removeObjectForKey:page];
2027
- [_originalPageMasks removeObjectForKey:page];
2028
- }
2029
- }
2030
-
2031
- - (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
2032
- {
2033
- [super updateEventEmitter:eventEmitter];
2034
- [self emitTopScrollEdgeCapability];
2035
- }
2036
-
2037
- - (void)emitTopScrollEdgeCapability
2038
- {
2039
- if (self.window == nil || _isBeingRecycled) return;
2040
- const auto emitter = [self eventEmitter];
2041
- if (!emitter) return;
2042
- BOOL available = NO;
2043
- if (@available(iOS 26.0, *)) available = YES;
2044
- emitter->onScrollEdgeCapability({.available = (bool)available});
2045
- }
2046
-
2047
- - (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView
2048
- {
2049
- [self syncScrollEdgeAppearance];
2050
- if (@available(iOS 26.0, *)) {
2051
- if (_pendingTopOverlayAppearanceCommit) scrollView = nil;
2052
- BOOL externalOwnerReady = _externalTopOverlayTag <= 0 ||
2053
- _externalOuterPager == nil ||
2054
- (_externalOuterPageActive && !_externalOuterTransitioning);
2055
- BOOL hasOverlay = _topScrollEdgeHard && self.hasFixedTopOverlay &&
2056
- externalOwnerReady && self.window != nil;
2057
- BOOL hasSticky = _topScrollEdgeHard && _stickyHeaderView != nil &&
2058
- externalOwnerReady && self.window != nil;
2059
- BOOL enabled = (hasOverlay || hasSticky) && scrollView != nil;
2060
- BOOL topHostChanged = (_topEdgeInteraction != nil) != hasOverlay ||
2061
- (hasOverlay && _topEdgeInteractionHost != _topOverlayHostView);
2062
- if (_topEdgeScrollView != nil && (!enabled || _topEdgeScrollView != scrollView ||
2063
- topHostChanged)) {
2064
- [_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
2065
- _topEdgeInteraction.scrollView = nil;
2066
- [_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
2067
- _stickyEdgeInteraction.scrollView = nil;
2068
- [_topEdgeMarker removeFromSuperview];
2069
- [_stickyEdgeMarker removeFromSuperview];
2070
- _topEdgeScrollView.topEdgeEffect.style = _originalTopEdgeStyle;
2071
- _topEdgeInteraction = nil;
2072
- _stickyEdgeInteraction = nil;
2073
- _topEdgeMarker = nil;
2074
- _stickyEdgeMarker = nil;
2075
- _topEdgeInteractionHost = nil;
2076
- _stickyEdgeInteractionHost = nil;
2077
- _topEdgeScrollView = nil;
2078
- _originalTopEdgeStyle = nil;
2079
- }
2080
- if (enabled && _topEdgeScrollView == nil) {
2081
- _originalTopEdgeStyle = scrollView.topEdgeEffect.style;
2082
- _topEdgeScrollView = scrollView;
2083
- scrollView.topEdgeEffect.style = UIScrollEdgeEffectStyle.hardStyle;
2084
- }
2085
- if (hasOverlay && enabled && _topEdgeInteraction == nil) {
2086
- // UIKit needs a native element to shape the edge effect around React-drawn content.
2087
- _topEdgeMarker = [UILabel new];
2088
- _topEdgeMarker.frame = _topOverlayHostView.bounds;
2089
- _topEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
2090
- _topEdgeMarker.text = @" ";
2091
- _topEdgeMarker.textColor = UIColor.clearColor;
2092
- _topEdgeMarker.backgroundColor = UIColor.clearColor;
2093
- _topEdgeMarker.userInteractionEnabled = NO;
2094
- _topEdgeMarker.isAccessibilityElement = NO;
2095
- _topEdgeMarker.accessibilityElementsHidden = YES;
2096
- [_topOverlayHostView addSubview:_topEdgeMarker];
2097
- _topEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
2098
- _topEdgeInteraction.edge = UIRectEdgeTop;
2099
- _topEdgeInteraction.scrollView = scrollView;
2100
- [_topOverlayHostView addInteraction:_topEdgeInteraction];
2101
- _topEdgeInteractionHost = _topOverlayHostView;
2102
- }
2103
- BOOL legacyStickyPinned = !_nativeSmoothHeaderScrollEnabled &&
2104
- _stickyHeaderView != nil && _headerOffset >= _headerHeight - 0.5;
2105
- BOOL stickyEnabled = enabled && (_stickyHeaderIsPinned || legacyStickyPinned);
2106
- UIView *stickyInteractionHost = _stickyHeaderIsPinned
2107
- ? _pinnedStickyHostView : _stickyHeaderView;
2108
- if (_stickyEdgeInteractionHost != nil &&
2109
- (!stickyEnabled || _stickyEdgeInteractionHost != stickyInteractionHost)) {
2110
- [_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
2111
- _stickyEdgeInteraction.scrollView = nil;
2112
- [_stickyEdgeMarker removeFromSuperview];
2113
- _stickyEdgeMarker = nil;
2114
- _stickyEdgeInteraction = nil;
2115
- _stickyEdgeInteractionHost = nil;
2116
- }
2117
- if (stickyEnabled && _stickyEdgeInteraction == nil) {
2118
- _stickyEdgeMarker = [UILabel new];
2119
- _stickyEdgeMarker.frame = stickyInteractionHost.bounds;
2120
- _stickyEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
2121
- _stickyEdgeMarker.text = @" ";
2122
- _stickyEdgeMarker.textColor = UIColor.clearColor;
2123
- _stickyEdgeMarker.backgroundColor = UIColor.clearColor;
2124
- _stickyEdgeMarker.userInteractionEnabled = NO;
2125
- _stickyEdgeMarker.isAccessibilityElement = NO;
2126
- _stickyEdgeMarker.accessibilityElementsHidden = YES;
2127
- [stickyInteractionHost addSubview:_stickyEdgeMarker];
2128
- _stickyEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
2129
- _stickyEdgeInteraction.edge = UIRectEdgeTop;
2130
- _stickyEdgeInteraction.scrollView = scrollView;
2131
- [stickyInteractionHost addInteraction:_stickyEdgeInteraction];
2132
- _stickyEdgeInteractionHost = stickyInteractionHost;
2133
- }
2134
- [self updateScrollEdgeContentMasks];
2135
- }
2136
- }
2137
-
2138
1715
  - (void)dealloc
2139
1716
  {
2140
- [self updateTopScrollEdgeForScrollView:nil];
2141
1717
  [self cancelHeaderSettledCheck];
2142
1718
  [[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
2143
1719
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
@@ -2190,7 +1766,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2190
1766
  _hasPendingDirectNativePagerChange = NO;
2191
1767
  if (_directNativePagerEnabled == enabled) return;
2192
1768
 
2193
- [self restoreScrollEdgeContentMasks];
2194
1769
  [self detachScrollObserver];
2195
1770
  [self restoreSharedHeadersToContainer];
2196
1771
  if (_blockerGesture != nil) {
@@ -2266,33 +1841,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2266
1841
 
2267
1842
  #pragma mark - Smooth shared header ownership
2268
1843
 
2269
- - (void)restorePinnedStickyHeader
2270
- {
2271
- if (!_stickyHeaderIsPinned) return;
2272
- _stickyHeaderIsPinned = NO;
2273
- _stickyHeaderView.transform = CGAffineTransformIdentity;
2274
- _nativeTabBarView.transform = CGAffineTransformIdentity;
2275
- _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2276
- [_stickyHeaderView removeFromSuperview];
2277
- [_nativeTabBarView removeFromSuperview];
2278
- [_nativeSubHeaderView removeFromSuperview];
2279
- if (_stickyHeaderView != nil) [_sharedHeaderHostView addSubview:_stickyHeaderView];
2280
- [_sharedHeaderHostView addSubview:_nativeTabBarView];
2281
- [_sharedHeaderHostView addSubview:_nativeSubHeaderView];
2282
- _pinnedStickyHostView.hidden = YES;
2283
- _pinnedStickyHostView.layer.zPosition = 0;
2284
- [_containerView addSubview:_pinnedStickyHostView];
2285
- if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
2286
- ((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView = _sharedHeaderHostView;
2287
- }
2288
- [_nativeTabBarView updateAncestorGesturePrecedence];
2289
- [_nativeSubHeaderView updateAncestorGesturePrecedence];
2290
- }
2291
-
2292
1844
  - (void)restoreSharedHeadersToContainer
2293
1845
  {
2294
- [self restorePinnedStickyHeader];
2295
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2296
1846
  _sharedHeaderScrollView = nil;
2297
1847
  _sharedHeadersLiftedForPagerTransition = NO;
2298
1848
  _sharedHeaderHostView.layer.zPosition = 0;
@@ -2300,15 +1850,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2300
1850
  [_containerView addSubview:_sharedHeaderHostView];
2301
1851
  }
2302
1852
  [self layoutSharedHeaders];
2303
- if (self.hasFixedTopOverlay) {
2304
- [_containerView bringSubviewToFront:_topOverlayHostView];
2305
- }
2306
1853
  }
2307
1854
 
2308
1855
  - (void)attachSharedHeadersToScrollView:(UIScrollView *)scrollView
2309
1856
  {
2310
1857
  if (!_nativeSmoothHeaderScrollEnabled || _transitioning || _isPagerDragging ||
2311
- _externalOuterTransitioning ||
2312
1858
  scrollView == nil || _isBeingRecycled) return;
2313
1859
  if ((UIView *)scrollView == _sharedHeaderHostView ||
2314
1860
  [scrollView isDescendantOfView:_sharedHeaderHostView]) {
@@ -2320,16 +1866,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2320
1866
  }
2321
1867
  if (_sharedHeaderScrollView == scrollView &&
2322
1868
  _sharedHeaderHostView.superview == scrollView) {
2323
- _sharedHeaderHostView.layer.zPosition =
2324
- _topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
2325
1869
  [self layoutSharedHeaders];
2326
1870
  return;
2327
1871
  }
2328
1872
 
2329
1873
  _sharedHeaderScrollView = scrollView;
2330
1874
  _sharedHeadersLiftedForPagerTransition = NO;
2331
- _sharedHeaderHostView.layer.zPosition =
2332
- _topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
1875
+ _sharedHeaderHostView.layer.zPosition = 1000;
2333
1876
  if (_sharedHeaderHostView.superview != scrollView) {
2334
1877
  [scrollView addSubview:_sharedHeaderHostView];
2335
1878
  }
@@ -2376,114 +1919,56 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2376
1919
 
2377
1920
  _sharedHeaderHostView.frame = CGRectMake(
2378
1921
  0,
2379
- attachedToList ? -totalHeight : self.effectiveTopOverlayHeight,
1922
+ attachedToList ? -totalHeight : 0,
2380
1923
  width,
2381
1924
  totalHeight
2382
1925
  );
2383
1926
  CGFloat stickyY = _headerHeight;
2384
1927
  _headerView.frame = CGRectMake(0, 0, width, _headerHeight);
2385
- CGPoint pinnedOrigin = _stickyHeaderIsPinned
2386
- ? [_sharedHeaderScrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight) fromView:_containerView]
2387
- : CGPointMake(0, self.effectiveTopOverlayHeight);
2388
- _pinnedStickyHostView.frame = CGRectMake(
2389
- pinnedOrigin.x, pinnedOrigin.y, width, _stickyHeaderHeight);
2390
- CGFloat stickyChildY = _stickyHeaderIsPinned ? 0 : stickyY;
2391
- _stickyHeaderView.frame = CGRectMake(0, stickyChildY, width, _stickyHeaderHeight);
1928
+ _stickyHeaderView.frame = CGRectMake(0, stickyY, width, _stickyHeaderHeight);
2392
1929
  CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
2393
1930
  ? 0
2394
1931
  : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2395
1932
  _nativeTabBarView.frame = CGRectMake(
2396
1933
  0,
2397
- stickyChildY,
1934
+ stickyY,
2398
1935
  width,
2399
1936
  nativeTabBarVisibleHeight
2400
1937
  );
2401
1938
  _nativeSubHeaderView.frame = CGRectMake(
2402
1939
  0,
2403
- stickyChildY + nativeTabBarVisibleHeight,
1940
+ stickyY + nativeTabBarVisibleHeight,
2404
1941
  width,
2405
1942
  MIN(
2406
1943
  MAX(0, _stickyHeaderHeight - nativeTabBarVisibleHeight),
2407
1944
  _nativeSubHeaderHeight
2408
1945
  )
2409
1946
  );
2410
- _topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
2411
- _topOverlayView.frame = _topOverlayHostView.bounds;
2412
1947
  [self applyHeaderOffset];
2413
1948
  }
2414
1949
 
2415
- - (void)updatePinnedStickyHeader
1950
+ - (void)offsetDiag:(NSString *)reason page:(NSInteger)index scroll:(UIScrollView *)scroll
2416
1951
  {
2417
- BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
2418
- _sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
2419
- if (!attachedToList && !_stickyHeaderIsPinned) return;
2420
- UIScrollView *scrollView = _sharedHeaderScrollView;
2421
- NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
2422
- CGFloat refreshInset = scrollView.refreshControl != nil && appliedTop != nil
2423
- ? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
2424
- CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
2425
- CGFloat stickyTop = [_sharedHeaderHostView convertPoint:
2426
- CGPointMake(0, _headerHeight + pinnedTranslation) toView:self].y;
2427
- BOOL shouldPin = _topScrollEdgeHard &&
2428
- _stickyHeaderView != nil && attachedToList && !_transitioning &&
2429
- !_externalOuterTransitioning &&
2430
- !_isPagerDragging && _headerOffset >= _headerHeight - 0.5 &&
2431
- stickyTop <= self.effectiveTopOverlayHeight + 0.5;
2432
- if (_stickyHeaderIsPinned && shouldPin && _pinnedStickyHostView.superview != scrollView) {
2433
- [self updateTopScrollEdgeForScrollView:nil];
2434
- [scrollView addSubview:_pinnedStickyHostView];
2435
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2436
- }
2437
- if (_stickyHeaderIsPinned == shouldPin) {
2438
- if (shouldPin) {
2439
- CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
2440
- fromView:_containerView];
2441
- _pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y,
2442
- CGRectGetWidth(self.bounds), _stickyHeaderHeight);
2443
- }
2444
- return;
2445
- }
2446
-
2447
- _stickyHeaderIsPinned = shouldPin;
2448
- UIView *host = shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
2449
- _stickyHeaderView.transform = CGAffineTransformIdentity;
2450
- _nativeTabBarView.transform = CGAffineTransformIdentity;
2451
- _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2452
- [_stickyHeaderView removeFromSuperview];
2453
- [_nativeTabBarView removeFromSuperview];
2454
- [_nativeSubHeaderView removeFromSuperview];
2455
- if (_stickyHeaderView != nil) [host addSubview:_stickyHeaderView];
2456
- [host addSubview:_nativeTabBarView];
2457
- [host addSubview:_nativeSubHeaderView];
2458
- CGFloat childY = shouldPin ? 0 : _headerHeight;
2459
- CGFloat width = CGRectGetWidth(self.bounds);
2460
- CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2461
- _stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
2462
- _nativeTabBarView.frame = CGRectMake(0, childY, width, tabHeight);
2463
- _nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
2464
- MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
2465
- _pinnedStickyHostView.hidden = !shouldPin;
2466
- if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
2467
- ((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView =
2468
- shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
2469
- }
2470
- if (shouldPin) {
2471
- [scrollView addSubview:_pinnedStickyHostView];
2472
- _pinnedStickyHostView.layer.zPosition = 1000;
2473
- CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
2474
- fromView:_containerView];
2475
- _pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y, width, _stickyHeaderHeight);
2476
- } else {
2477
- _pinnedStickyHostView.layer.zPosition = 0;
2478
- [_containerView addSubview:_pinnedStickyHostView];
2479
- }
2480
- [_nativeTabBarView updateAncestorGesturePrecedence];
2481
- [_nativeSubHeaderView updateAncestorGesturePrecedence];
2482
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
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
2483
1967
  }
2484
1968
 
2485
1969
  - (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
2486
1970
  {
1971
+ [self offsetDiag:@"prepare-check" page:pageIndex scroll:nil];
2487
1972
  if (!_nativeSmoothHeaderScrollEnabled ||
2488
1973
  pageIndex < 0 || pageIndex >= _pageControllers.count) return;
2489
1974
  UIScrollView *scrollView = [self verticalScrollViewForPageAtIndex:pageIndex];
@@ -2502,6 +1987,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2502
1987
  _transitionHeaderOffset = _headerOffset;
2503
1988
  _transitionPageOffsets = [_pageOffsets copy];
2504
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
2505
1995
  }
2506
1996
 
2507
1997
  - (void)updatePageStateTransitionProgress:(CGFloat)progress
@@ -2518,6 +2008,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2518
2008
  CGFloat firstQ = [_transitionPageOffsets[[self pageKeyForIndex:first]] doubleValue];
2519
2009
  CGFloat secondQ = [_transitionPageOffsets[[self pageKeyForIndex:second]] doubleValue];
2520
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
2521
2021
  _syntheticPageRestore = YES;
2522
2022
  NSInteger visibleFirst = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
2523
2023
  NSInteger visibleLast = MIN(visibleFirst + 1, (NSInteger)_pageControllers.count - 1);
@@ -2559,6 +2059,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2559
2059
  _transitionPageOffsets = nil;
2560
2060
  _transitionCompensatedPages = nil;
2561
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
2562
2066
  }
2563
2067
 
2564
2068
  #pragma mark - React mounting
@@ -2615,7 +2119,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2615
2119
  if (!_nativeSmoothHeaderScrollEnabled && _transitionPageOffsets == nil && !_syntheticPageRestore &&
2616
2120
  ![_pendingPageRestoreKeys containsObject:pageKey] &&
2617
2121
  (ownsObservedPage || _pageOffsets[pageKey] == nil)) {
2122
+ [self offsetDiag:@"mount-release-save-before" page:index scroll:scrollView];
2618
2123
  _pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
2124
+ [self offsetDiag:@"mount-release-save-after" page:index scroll:scrollView];
2619
2125
  }
2620
2126
  break;
2621
2127
  }
@@ -2631,27 +2137,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2631
2137
  - (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
2632
2138
  withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry
2633
2139
  {
2634
- if (_needsSlotRebuild && !_deferTopEdgeSlotRebuild &&
2635
- !_transitioning && !_isPagerDragging) {
2636
- [self rebuildSlots];
2637
- }
2638
- [self registerExternalOuterPager];
2639
- if ([self resolveExternalTopOverlay]) {
2640
- [self reapplyInsetsToObservedScrollView];
2641
- [self setNeedsLayout];
2642
- }
2643
- if (_pendingTopOverlayAppearanceCommit) {
2644
- _pendingTopOverlayAppearanceCommit = NO;
2645
- BOOL localOverlayReady = _topOverlayView.superview == _topOverlayHostView &&
2646
- _topOverlayView.window == self.window;
2647
- BOOL externalOverlayReady = _externalTopOverlayTag > 0 &&
2648
- _externalTopOverlayView.window == self.window;
2649
- if (!_isBeingRecycled && _topScrollEdgeHard && self.window != nil &&
2650
- (localOverlayReady || externalOverlayReady)) {
2651
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2652
- }
2653
- }
2654
- [self updateScrollEdgeContentMasks];
2655
2140
  [_mountingRemovedScrollViews removeAllObjects];
2656
2141
  if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled || self.window == nil ||
2657
2142
  _currentIndex < 0 || _currentIndex >= _pageControllers.count) return;
@@ -2729,11 +2214,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2729
2214
  if (_isBeingRecycled) return;
2730
2215
  NSInteger safeIndex = MIN(MAX(index, 0), _logicalChildren.count);
2731
2216
  [_logicalChildren insertObject:childComponentView atIndex:safeIndex];
2732
- if (_topScrollEdgeHard || _topOverlayView != nil) {
2733
- _needsSlotRebuild = YES;
2734
- } else {
2735
- [self rebuildSlots];
2736
- }
2217
+ [self rebuildSlots];
2737
2218
  }
2738
2219
 
2739
2220
  - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
@@ -2744,32 +2225,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2744
2225
  if (existingIndex == NSNotFound) return;
2745
2226
  [childComponentView removeFromSuperview];
2746
2227
  [_logicalChildren removeObjectAtIndex:existingIndex];
2747
- if (_topScrollEdgeHard || _topOverlayView != nil) {
2748
- _needsSlotRebuild = YES;
2749
- } else {
2750
- [self rebuildSlots];
2751
- }
2228
+ [self rebuildSlots];
2752
2229
  }
2753
2230
 
2754
2231
  - (void)rebuildSlots
2755
2232
  {
2756
- if (_deferTopEdgeSlotRebuild) {
2757
- _needsSlotRebuild = YES;
2758
- return;
2759
- }
2760
2233
  if (_transitioning || _isPagerDragging) {
2761
2234
  _needsSlotRebuild = YES;
2762
2235
  return;
2763
2236
  }
2764
- if (_topScrollEdgeHard &&
2765
- _logicalChildren.count != _pageKeys.count + 2 &&
2766
- _logicalChildren.count != _pageKeys.count + 3) {
2767
- _needsSlotRebuild = YES;
2768
- return;
2769
- }
2770
2237
  _needsSlotRebuild = NO;
2771
- _deferTopEdgeSlotRebuild = NO;
2772
- [self restoreScrollEdgeContentMasks];
2773
2238
  [self detachScrollObserver];
2774
2239
  [self restoreSharedHeadersToContainer];
2775
2240
  // OneKey patch: Fabric reuses these views without resetting transforms
@@ -2781,15 +2246,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2781
2246
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2782
2247
  [_headerView removeFromSuperview];
2783
2248
  [_stickyHeaderView removeFromSuperview];
2784
- [self updateTopScrollEdgeForScrollView:nil];
2785
- [_topOverlayView removeFromSuperview];
2786
2249
  for (UIViewController *controller in _pageControllers) {
2787
2250
  [controller.view removeFromSuperview];
2788
2251
  }
2789
2252
  _headerView = nil;
2790
2253
  _stickyHeaderView = nil;
2791
- _topOverlayView = nil;
2792
- [self updateTopScrollEdgeForScrollView:nil];
2793
2254
  [_pageControllers removeAllObjects];
2794
2255
 
2795
2256
  if (_logicalChildren.count > 0) {
@@ -2800,15 +2261,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2800
2261
  _stickyHeaderView = _logicalChildren[1];
2801
2262
  [_sharedHeaderHostView addSubview:_stickyHeaderView];
2802
2263
  }
2803
- BOOL hasOverlaySlot = _topScrollEdgeHard &&
2804
- _logicalChildren.count == _pageKeys.count + 3;
2805
- NSInteger pageSlotStart = hasOverlaySlot ? 3 : 2;
2806
- if (hasOverlaySlot) {
2807
- _topOverlayView = _logicalChildren[2];
2808
- [_topOverlayHostView addSubview:_topOverlayView];
2809
- }
2810
- [self resolveExternalTopOverlay];
2811
- for (NSInteger index = pageSlotStart; index < _logicalChildren.count; index++) {
2264
+ for (NSInteger index = 2; index < _logicalChildren.count; index++) {
2812
2265
  UIView *page = _logicalChildren[index];
2813
2266
  UIViewController *controller = [UIViewController new];
2814
2267
  [controller.view addSubview:page];
@@ -2847,10 +2300,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2847
2300
  if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
2848
2301
  if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
2849
2302
  [_containerView bringSubviewToFront:_sharedHeaderHostView];
2850
- if (_stickyHeaderIsPinned) {
2851
- [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
2852
- }
2853
- if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
2854
2303
  [self setNeedsLayout];
2855
2304
  dispatch_async(dispatch_get_main_queue(), ^{
2856
2305
  if (!self->_isBeingRecycled) {
@@ -2863,10 +2312,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2863
2312
  - (void)layoutSubviews
2864
2313
  {
2865
2314
  [super layoutSubviews];
2866
- [self syncScrollEdgeAppearance];
2867
2315
  _containerView.frame = self.bounds;
2868
- [self registerExternalOuterPager];
2869
- BOOL externalHeightChanged = [self resolveExternalTopOverlay];
2870
2316
  if (_directNativePagerEnabled) {
2871
2317
  _pagerScrollView.frame = _containerView.bounds;
2872
2318
  } else {
@@ -2929,9 +2375,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2929
2375
  }
2930
2376
  }
2931
2377
  [self layoutSharedHeaders];
2932
- if (externalHeightChanged) [self reapplyInsetsToObservedScrollView];
2933
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2934
- [self updateScrollEdgeContentMasks];
2935
2378
  [self updateHeaderSettledStateForceBusy:NO];
2936
2379
  dispatch_async(dispatch_get_main_queue(), ^{
2937
2380
  if (!self->_isBeingRecycled) {
@@ -2943,16 +2386,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2943
2386
 
2944
2387
  - (void)prepareForRecycle
2945
2388
  {
2946
- [_externalOuterPager removeNestedOverlayObserver:self];
2947
- _externalOuterPager = nil;
2948
- [self cancelTopInsetReboundCheck];
2949
- [self restoreScrollEdgeContentMasks];
2950
- [self updateTopScrollEdgeForScrollView:nil];
2951
2389
  [self cancelHeaderSettledCheck];
2952
2390
  [self publishHeaderSettledAtTop:NO identityChanged:NO];
2953
2391
  _isBeingRecycled = YES;
2954
- _pendingTopOverlayAppearanceCommit = NO;
2955
- [self syncScrollEdgeAppearance];
2956
2392
  _generation++;
2957
2393
  [_mountingRemovedScrollViews removeAllObjects];
2958
2394
  _needsScrollObserverReattachAfterMount = NO;
@@ -2972,7 +2408,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2972
2408
  if (appliedTop != nil && scrollView.refreshControl != nil) {
2973
2409
  original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
2974
2410
  }
2411
+ [self offsetDiag:@"release-original-inset-before" page:_currentIndex scroll:scrollView];
2975
2412
  scrollView.contentInset = original;
2413
+ [self offsetDiag:@"release-original-inset-after" page:_currentIndex scroll:scrollView];
2976
2414
  }
2977
2415
  NSValue *indicatorValue = [_originalIndicatorInsets objectForKey:scrollView];
2978
2416
  if (indicatorValue != nil) {
@@ -3001,7 +2439,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3001
2439
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
3002
2440
  [_headerView removeFromSuperview];
3003
2441
  [_stickyHeaderView removeFromSuperview];
3004
- [_topOverlayView removeFromSuperview];
3005
2442
  for (UIViewController *controller in _pageControllers) {
3006
2443
  [controller.view removeFromSuperview];
3007
2444
  }
@@ -3009,9 +2446,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3009
2446
  [_pageControllers removeAllObjects];
3010
2447
  _headerView = nil;
3011
2448
  _stickyHeaderView = nil;
3012
- _topOverlayView = nil;
3013
- _topOverlayHostView.hidden = YES;
3014
- [self updateTopScrollEdgeForScrollView:nil];
3015
2449
  _pageViewController.dataSource = nil;
3016
2450
  _pageViewController.delegate = nil;
3017
2451
  _pagerScrollView.delegate = nil;
@@ -3044,13 +2478,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3044
2478
  _pendingInitialPage = 0;
3045
2479
  _headerHeight = 0;
3046
2480
  _stickyHeaderHeight = 0;
3047
- _topOverlayHeight = 0;
3048
- _externalTopOverlayTag = 0;
3049
- _externalTopOverlayView = nil;
3050
- _externalTopOverlayHeight = 0;
3051
- _externalOuterPageActive = NO;
3052
- _externalOuterTransitioning = NO;
3053
- _topScrollEdgeHard = NO;
3054
2481
  _nativeTabBarHeight = 44;
3055
2482
  _nativeSubHeaderHeight = 74;
3056
2483
  _headerOffset = 0;
@@ -3074,7 +2501,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3074
2501
  _pendingGoToIndex = -1;
3075
2502
  _pendingGoToAnimated = YES;
3076
2503
  _needsSlotRebuild = NO;
3077
- _deferTopEdgeSlotRebuild = NO;
3078
2504
  _hasAppliedInitialPage = NO;
3079
2505
  // OneKey patch: Fabric retains old props when recycling this view.
3080
2506
  _needsPropsReapply = YES;
@@ -3091,9 +2517,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3091
2517
  {
3092
2518
  const auto &oldViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(_props);
3093
2519
  const auto &newViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(props);
3094
- _applyingTopOverlayInsetChange =
3095
- oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard ||
3096
- oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight;
3097
2520
 
3098
2521
  if (_needsPropsReapply || oldViewProps.initialPage != newViewProps.initialPage) {
3099
2522
  _pendingInitialPage = MAX(0, newViewProps.initialPage);
@@ -3143,43 +2566,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3143
2566
  _stickyHeaderHeight = MAX(0, newViewProps.stickyHeaderHeight);
3144
2567
  insetsChanged = YES;
3145
2568
  }
3146
- if (_needsPropsReapply || oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard) {
3147
- _topScrollEdgeHard = newViewProps.topScrollEdgeHard;
3148
- if (oldViewProps.topScrollEdgeHard || newViewProps.topScrollEdgeHard) {
3149
- _deferTopEdgeSlotRebuild = YES;
3150
- [self rebuildSlots];
3151
- NSUInteger generation = _generation;
3152
- dispatch_async(dispatch_get_main_queue(), ^{
3153
- if (self->_generation != generation || self->_isBeingRecycled ||
3154
- !self->_deferTopEdgeSlotRebuild) return;
3155
- self->_deferTopEdgeSlotRebuild = NO;
3156
- [self rebuildSlots];
3157
- });
3158
- }
3159
- insetsChanged = YES;
3160
- }
3161
- if (_needsPropsReapply ||
3162
- oldViewProps.topOverlayColorScheme != newViewProps.topOverlayColorScheme) {
3163
- NSString *nextScheme = RCTNSStringFromString(newViewProps.topOverlayColorScheme);
3164
- if (self.hasFixedTopOverlay && _topScrollEdgeHard &&
3165
- _topOverlayColorScheme != nil &&
3166
- ![_topOverlayColorScheme isEqualToString:nextScheme]) {
3167
- _pendingTopOverlayAppearanceCommit = YES;
3168
- [self updateTopScrollEdgeForScrollView:nil];
3169
- }
3170
- _topOverlayColorScheme = nextScheme;
3171
- [self syncScrollEdgeAppearance];
3172
- }
3173
- if (_needsPropsReapply || oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight) {
3174
- _topOverlayHeight = MAX(0, newViewProps.topOverlayHeight);
3175
- insetsChanged = YES;
3176
- }
3177
- if (_needsPropsReapply ||
3178
- oldViewProps.externalTopOverlayTag != newViewProps.externalTopOverlayTag) {
3179
- _externalTopOverlayTag = newViewProps.externalTopOverlayTag;
3180
- [self registerExternalOuterPager];
3181
- insetsChanged = [self resolveExternalTopOverlay] || insetsChanged;
3182
- }
3183
2569
  if (_needsPropsReapply || oldViewProps.pageKeys != newViewProps.pageKeys) {
3184
2570
  NSString *json = RCTNSStringFromString(newViewProps.pageKeys);
3185
2571
  NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
@@ -3247,23 +2633,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3247
2633
 
3248
2634
  _needsPropsReapply = NO;
3249
2635
  [super updateProps:props oldProps:oldProps];
3250
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
3251
2636
  if (insetsChanged) {
3252
2637
  [self reapplyInsetsToObservedScrollView];
3253
2638
  [self setNeedsLayout];
3254
2639
  }
3255
- _applyingTopOverlayInsetChange = NO;
3256
2640
  if (!_nativeTabBarView.hidden) {
3257
- [_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
2641
+ [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
3258
2642
  }
3259
2643
  if (!_nativeSubHeaderView.hidden) {
3260
- [_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
2644
+ [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
3261
2645
  }
3262
2646
  [_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
3263
- if (_stickyHeaderIsPinned) {
3264
- [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
3265
- }
3266
- if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
3267
2647
  }
3268
2648
 
3269
2649
  #pragma mark - Collapsible scroll coordination
@@ -3497,17 +2877,21 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3497
2877
  return;
3498
2878
  }
3499
2879
 
2880
+ [self offsetDiag:@"observer-candidate" page:_currentIndex scroll:candidate];
3500
2881
  BOOL replacingCurrentScrollView = _observingContentOffset &&
3501
2882
  (!_nativeSmoothHeaderScrollEnabled || [_observedPageKey isEqualToString:self.currentPageKey]);
3502
2883
  [self detachScrollObserver];
3503
2884
  if (replacingCurrentScrollView) {
3504
2885
  // Legacy replacement discards body state; smooth replacement waits for layout.
2886
+ [self offsetDiag:@"observer-replacement-cache-before" page:_currentIndex scroll:candidate];
3505
2887
  if (!_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
2888
+ [self offsetDiag:@"observer-replacement-cache-after" page:_currentIndex scroll:candidate];
3506
2889
  }
3507
2890
  _observedScrollView = candidate;
3508
2891
  _observedPageKey = [self.currentPageKey copy];
3509
2892
  _observedPageIndex = _currentIndex;
3510
2893
  _observedPageGeneration = _generation;
2894
+ [self offsetDiag:@"observer-attached-before-restore" page:_currentIndex scroll:candidate];
3511
2895
  [self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
3512
2896
  [candidate addObserver:self
3513
2897
  forKeyPath:@"contentOffset"
@@ -3525,7 +2909,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3525
2909
  _observingContentOffset = YES;
3526
2910
  _needsScrollObserverReattachAfterMount = NO;
3527
2911
  _currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
3528
- [self updateTopScrollEdgeForScrollView:candidate];
3529
2912
  [self attachSharedHeadersToScrollView:candidate];
3530
2913
  [self updateSharedHeaderPressCancellationGesture];
3531
2914
  [self updateHeaderSettledStateForceBusy:NO];
@@ -3533,9 +2916,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3533
2916
 
3534
2917
  - (void)detachScrollObserver
3535
2918
  {
3536
- [self cancelTopInsetReboundCheck];
3537
- [self restorePinnedStickyHeader];
3538
- [self updateTopScrollEdgeForScrollView:nil];
3539
2919
  [self cancelHeaderSettledCheck];
3540
2920
  _headerCommandOwner = nil;
3541
2921
  _headerSettledGeometry = nil;
@@ -3545,6 +2925,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3545
2925
  UIScrollView *scrollView = _observedScrollView;
3546
2926
  if (scrollView != nil) {
3547
2927
  [scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
2928
+ [self offsetDiag:@"detach-save-before" page:_currentIndex scroll:scrollView];
3548
2929
  NSString *ownerKey = _nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey;
3549
2930
  BOOL ownerKeyExists = ownerKey != nil && ([_pageKeys containsObject:ownerKey] ||
3550
2931
  (_observedPageIndex >= 0 && _observedPageIndex < _pageControllers.count &&
@@ -3552,6 +2933,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3552
2933
  if (ownerKey != nil && !_nativeSmoothHeaderScrollEnabled) {
3553
2934
  _pageOffsets[ownerKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
3554
2935
  }
2936
+ [self offsetDiag:@"detach-save-after" page:_currentIndex scroll:scrollView];
3555
2937
  if (_observingContentOffset) {
3556
2938
  [scrollView removeObserver:self
3557
2939
  forKeyPath:@"contentOffset"
@@ -3593,9 +2975,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3593
2975
  removeGestureRecognizer:_sharedHeaderOuterPagerGesture];
3594
2976
  }
3595
2977
  _sharedHeaderOuterPagerGesture.blockedPagerGestures = nil;
3596
- _sharedHeaderOuterPagerGesture.trackedTouch = nil;
3597
- _sharedHeaderOuterPagerGesture.protectsNestedBody = NO;
3598
- _sharedHeaderOuterPagerGesture.cancelsTouchesInView = YES;
3599
2978
  _sharedHeaderPressCancellationGestureHost = nil;
3600
2979
  _reactTouchHandler = nil;
3601
2980
  }
@@ -3645,7 +3024,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3645
3024
  }
3646
3025
  }
3647
3026
  if (_sharedHeaderPressCancellationGesture.view == gestureHost &&
3648
- _sharedHeaderOuterPagerGesture.view == _containerView &&
3027
+ _sharedHeaderOuterPagerGesture.view == _sharedHeaderHostView &&
3649
3028
  _reactTouchHandler == reactTouchHandler) {
3650
3029
  _sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
3651
3030
  return;
@@ -3687,7 +3066,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3687
3066
  _reactTouchHandler = reactTouchHandler;
3688
3067
  _sharedHeaderPressCancellationGestureHost = gestureHost;
3689
3068
  [gestureHost addGestureRecognizer:_sharedHeaderPressCancellationGesture];
3690
- [_containerView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
3069
+ [_sharedHeaderHostView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
3691
3070
  RNCCollapsiblePagerLog([NSString stringWithFormat:
3692
3071
  @"press-cancel-bridge attached=1 outer-pagers=%lu",
3693
3072
  (unsigned long)outerPagerGestures.count]);
@@ -3759,11 +3138,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3759
3138
 
3760
3139
  - (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer
3761
3140
  {
3762
- if (recognizer == _pendingTopInsetReboundScrollView.panGestureRecognizer &&
3763
- (recognizer.state == UIGestureRecognizerStateBegan ||
3764
- recognizer.state == UIGestureRecognizerStateChanged)) {
3765
- [self cancelTopInsetReboundCheck];
3766
- }
3767
3141
  if (recognizer == _observedScrollView.panGestureRecognizer) {
3768
3142
  [self updateHeaderSettledStateForceBusy:
3769
3143
  recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged];
@@ -3831,7 +3205,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3831
3205
  [_managedRefreshControls removeObjectForKey:scrollView];
3832
3206
  if (current != nil) [_managedRefreshControls setObject:current forKey:scrollView];
3833
3207
  }
3834
- [RNCRefreshControlOffsetState setOffset:self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight
3208
+ [RNCRefreshControlOffsetState setOffset:_headerHeight + _stickyHeaderHeight
3835
3209
  forControl:current owner:self];
3836
3210
  }
3837
3211
 
@@ -3845,53 +3219,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3845
3219
  [_managedRefreshControls removeObjectForKey:scrollView];
3846
3220
  }
3847
3221
 
3848
- - (void)cancelTopInsetReboundCheck
3849
- {
3850
- [_topInsetReboundTimer invalidate];
3851
- _topInsetReboundTimer = nil;
3852
- _pendingTopInsetReboundScrollView = nil;
3853
- _pendingTopInsetReboundPageKey = nil;
3854
- }
3855
-
3856
- - (void)scheduleTopInsetReboundCheck
3857
- {
3858
- if (_pendingTopInsetReboundScrollView == nil ||
3859
- _pendingTopInsetReboundScrollView != _observedScrollView) return;
3860
- [_topInsetReboundTimer invalidate];
3861
- __weak __typeof__(self) weakSelf = self;
3862
- _topInsetReboundTimer = [NSTimer scheduledTimerWithTimeInterval:0.15 repeats:NO block:^(NSTimer *timer) {
3863
- __strong __typeof__(weakSelf) self = weakSelf;
3864
- if (self == nil || self->_topInsetReboundTimer != timer) return;
3865
- self->_topInsetReboundTimer = nil;
3866
- UIScrollView *scrollView = self->_pendingTopInsetReboundScrollView;
3867
- if (scrollView == nil || scrollView != self->_observedScrollView ||
3868
- self->_pendingTopInsetReboundGeneration != self->_generation ||
3869
- ![self->_pendingTopInsetReboundPageKey isEqualToString:self.currentPageKey] ||
3870
- self->_isBeingRecycled || self.window == nil ||
3871
- self->_transitioning || self->_isPagerDragging ||
3872
- self->_headerCommandOwner == scrollView || self->_pagerTopPageKey != nil) {
3873
- [self cancelTopInsetReboundCheck];
3874
- return;
3875
- }
3876
- UIGestureRecognizerState pan = scrollView.panGestureRecognizer.state;
3877
- if (scrollView.refreshControl.isRefreshing || scrollView.isDragging ||
3878
- scrollView.isDecelerating || scrollView.isTracking ||
3879
- pan == UIGestureRecognizerStateBegan || pan == UIGestureRecognizerStateChanged ||
3880
- self->_syntheticPageRestore || self->_transitionPageOffsets != nil) {
3881
- [self scheduleTopInsetReboundCheck];
3882
- return;
3883
- }
3884
- CGFloat minimumOffset = -scrollView.adjustedContentInset.top;
3885
- [self cancelTopInsetReboundCheck];
3886
- if (scrollView.contentOffset.y < minimumOffset - 0.5) {
3887
- // UIKit can finish a refresh rebound at the inset that preceded an overlay resize.
3888
- self->_correctingTopInsetRebound = YES;
3889
- [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, minimumOffset) animated:NO];
3890
- self->_correctingTopInsetRebound = NO;
3891
- }
3892
- }];
3893
- }
3894
-
3895
3222
  // OneKey patch: release observers, headers and saved state while the scroll view
3896
3223
  // still represents the old list, before Fabric applies its next owner's props.
3897
3224
  - (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)resetSavedOffset
@@ -3900,7 +3227,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3900
3227
  [self releaseRefreshControlForScrollView:scrollView];
3901
3228
  if (scrollView == _observedScrollView) {
3902
3229
  [self detachScrollObserver];
3230
+ [self offsetDiag:resetSavedOffset ? @"release-reset-before" : @"release-retain-before" page:_currentIndex scroll:scrollView];
3903
3231
  if (resetSavedOffset && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
3232
+ [self offsetDiag:@"release-reset-after" page:_currentIndex scroll:scrollView];
3904
3233
  }
3905
3234
  if (scrollView == _sharedHeaderScrollView) {
3906
3235
  [self restoreSharedHeadersToContainer];
@@ -3920,8 +3249,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3920
3249
  [_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
3921
3250
  scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
3922
3251
  [_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
3252
+ [self offsetDiag:@"release-original-offset-before" page:_currentIndex scroll:scrollView];
3923
3253
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
3924
3254
  animated:NO];
3255
+ [self offsetDiag:@"release-original-offset-after" page:_currentIndex scroll:scrollView];
3925
3256
  [_originalInsets removeObjectForKey:scrollView];
3926
3257
  [_appliedTopInsets removeObjectForKey:scrollView];
3927
3258
  [_originalIndicatorInsets removeObjectForKey:scrollView];
@@ -3952,6 +3283,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3952
3283
  {
3953
3284
  // Smooth pages retain their complete offset while replacement identity and
3954
3285
  // layout settle. Legacy pages retain the historical header-only policy.
3286
+ [self offsetDiag:restore ? @"restore-enter" : @"insets-enter" page:pageIndex scroll:scrollView];
3955
3287
  NSString *pageKey = [self pageKeyForIndex:pageIndex];
3956
3288
  RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
3957
3289
  BOOL deferredReleasedIdentity = NO;
@@ -3997,8 +3329,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3997
3329
  deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
3998
3330
  RNCCollapsiblePagerFallbackDebounceInterval;
3999
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
4000
3335
  if (!deferredReleasedIdentity) {
3336
+ [self offsetDiag:@"released-identity-cache-before" page:pageIndex scroll:scrollView];
4001
3337
  if (!sameList && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[pageKey] = @(_headerOffset);
3338
+ [self offsetDiag:@"released-identity-cache-after" page:pageIndex scroll:scrollView];
4002
3339
  [_releasedPageScrollStates removeObjectForKey:pageKey];
4003
3340
  }
4004
3341
  }
@@ -4023,24 +3360,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4023
3360
  // suppress its later vertical movement while the pager is still pending.
4024
3361
  scrollView.directionalLockEnabled = _nativeSmoothHeaderScrollEnabled
4025
3362
  ? NO : [_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
4026
- [self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight + self.effectiveTopOverlayHeight];
3363
+ [self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight];
4027
3364
  [self updateRefreshControlForScrollView:scrollView];
4028
3365
  CGFloat previousTopInset = scrollView.contentInset.top;
4029
3366
  CGFloat previousLogicalOffset = scrollView.contentOffset.y + previousTopInset;
4030
- // Preserve UIKit's rebound after refresh while the content is beyond its top inset.
4031
- BOOL wasReturningFromPull = scrollView.contentOffset.y < -scrollView.adjustedContentInset.top - 0.5;
4032
3367
  NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
4033
3368
  // UIRefreshControl owns its temporary top inset, including while ending.
4034
3369
  CGFloat refreshInset = appliedTop != nil && scrollView.refreshControl != nil
4035
3370
  ? MAX(0, previousTopInset - appliedTop.doubleValue) : 0;
4036
3371
  UIEdgeInsets next = original;
4037
- next.top += self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight;
3372
+ next.top += _headerHeight + _stickyHeaderHeight;
4038
3373
  CGFloat pagerTopInset = next.top;
4039
3374
  next.top += refreshInset;
4040
3375
  [_appliedTopInsets setObject:@(pagerTopInset) forKey:scrollView];
4041
3376
  // Short pages still need enough range to collapse the header; long pages
4042
3377
  // must end at the last row rather than leave a header-sized blank footer.
4043
- next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight - self.effectiveTopOverlayHeight
3378
+ next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight
4044
3379
  - original.top - original.bottom - scrollView.contentSize.height);
4045
3380
  if (UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, next) && !restore) {
4046
3381
  // UICollectionView may report contentSize during a bounce without changing
@@ -4050,7 +3385,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4050
3385
  }
4051
3386
  BOOL previousSyntheticRestore = _syntheticPageRestore;
4052
3387
  if (_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:pageKey]) _syntheticPageRestore = YES;
3388
+ [self offsetDiag:@"inset-assignment-before" page:pageIndex scroll:scrollView];
4053
3389
  scrollView.contentInset = next;
3390
+ [self offsetDiag:@"inset-assignment-after" page:pageIndex scroll:scrollView];
4054
3391
  UIEdgeInsets indicatorInsets = scrollView.verticalScrollIndicatorInsets;
4055
3392
  indicatorInsets.top = pagerTopInset;
4056
3393
  scrollView.verticalScrollIndicatorInsets = indicatorInsets;
@@ -4082,6 +3419,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4082
3419
  } else {
4083
3420
  restoredLogicalOffset = deferredReleasedIdentity || saved == nil ? _headerOffset : saved.doubleValue;
4084
3421
  }
3422
+ #if DEBUG
3423
+ [self offsetDiag:[NSString stringWithFormat:@"restore-candidate-saved=%.4f-headerAlignedLogical=%.4f", saved.doubleValue, restoredLogicalOffset] page:pageIndex scroll:scrollView];
3424
+ #endif
4085
3425
  intendedLogicalOffset = restoredLogicalOffset;
4086
3426
  // Cancellation restores the visible source, while retaining its unreachable reading intent.
4087
3427
  if (_nativeSmoothHeaderScrollEnabled && _headerOffset >= _headerHeight &&
@@ -4094,6 +3434,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4094
3434
  }
4095
3435
  // An inactive list may shrink after its offset observer was detached.
4096
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
4097
3440
  targetOffset = RNCLimitRestoredScrollOffset(
4098
3441
  targetOffset,
4099
3442
  scrollView.contentSize.height,
@@ -4102,17 +3445,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4102
3445
  next.bottom
4103
3446
  );
4104
3447
  }
3448
+ #if DEBUG
3449
+ [self offsetDiag:[NSString stringWithFormat:@"restore-final-target=%.4f-restore=%d", targetOffset, restore] page:pageIndex scroll:scrollView];
3450
+ #endif
4105
3451
  // A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
4106
- if (restore || (previousTopInset != next.top && !wasReturningFromPull)) {
3452
+ if (restore || previousTopInset != next.top) {
4107
3453
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, targetOffset) animated:NO];
4108
3454
  }
4109
- if (_applyingTopOverlayInsetChange && !restore && wasReturningFromPull &&
4110
- previousTopInset != next.top && scrollView == _observedScrollView) {
4111
- _pendingTopInsetReboundScrollView = scrollView;
4112
- _pendingTopInsetReboundPageKey = [pageKey copy];
4113
- _pendingTopInsetReboundGeneration = _generation;
4114
- [self scheduleTopInsetReboundCheck];
4115
- }
4116
3455
  if (_nativeSmoothHeaderScrollEnabled && restore && targetOffset + next.top + 0.5 < intendedLogicalOffset) {
4117
3456
  [_pendingPageRestoreKeys addObject:pageKey];
4118
3457
  }
@@ -4122,6 +3461,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4122
3461
  if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:scrollView];
4123
3462
  else [self applyHeaderOffset];
4124
3463
  }
3464
+ [self offsetDiag:@"restore-post" page:pageIndex scroll:scrollView];
4125
3465
  }
4126
3466
 
4127
3467
  - (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
@@ -4138,7 +3478,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4138
3478
  _observedPageKey == nil || ![_observedPageKey isEqualToString:self.currentPageKey] ||
4139
3479
  _observedPageIndex != _currentIndex || _observedPageGeneration != _generation ||
4140
3480
  _transitioning || _isPagerDragging || _transitionPageOffsets != nil || _syntheticPageRestore) return;
4141
- [self cancelTopInsetReboundCheck];
4142
3481
  NSInteger phase = [notification.userInfo[@"phase"] integerValue];
4143
3482
  [self updateHeaderSettledStateForceBusy:NO];
4144
3483
  if (phase == 1) _headerCommandOwner = scrollView;
@@ -4171,6 +3510,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4171
3510
  _headerOffset = 0;
4172
3511
  [self applyHeaderOffset];
4173
3512
  }
3513
+ [self offsetDiag:@"list-command-body-takeover" page:_currentIndex scroll:scrollView];
4174
3514
  }
4175
3515
 
4176
3516
  - (void)nativeScrollerWillReceiveScrollCommand:(RNCNativeScrollerComponentView *)scroller
@@ -4182,12 +3522,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4182
3522
  _observedPageIndex != _currentIndex || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
4183
3523
  _observedPageGeneration != _generation || _transitioning || _isPagerDragging ||
4184
3524
  _transitionPageOffsets != nil || _syntheticPageRestore) return;
4185
- [self cancelTopInsetReboundCheck];
4186
3525
  if (!_dispatchingPagerTop) {
4187
3526
  [self cancelPagerTop];
4188
3527
  [_alignedBodyKeys removeObject:ownerKey];
4189
3528
  }
4190
3529
  if (!_nativeSmoothHeaderScrollEnabled) return;
3530
+ [self offsetDiag:@"command-pending-cancel-before" page:_observedPageIndex scroll:scrollView];
4191
3531
  [_pendingPageRestoreKeys removeObject:ownerKey];
4192
3532
  _currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
4193
3533
  [_commandBodyKeys removeObject:ownerKey];
@@ -4196,6 +3536,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4196
3536
  [_displayBodyOverrides removeObjectForKey:ownerKey];
4197
3537
  _pageOffsets[ownerKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
4198
3538
  [self updateHeaderForScrollView:scrollView];
3539
+ [self offsetDiag:@"command-pending-cancel-after" page:_observedPageIndex scroll:scrollView];
4199
3540
  }
4200
3541
 
4201
3542
 
@@ -4257,14 +3598,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4257
3598
  return;
4258
3599
  }
4259
3600
  if (context == RNCCollapsiblePagerContentOffsetContext && object == _observedScrollView) {
4260
- if (_pendingTopInsetReboundScrollView == object &&
4261
- ([keyPath isEqualToString:@"contentOffset"] || [keyPath isEqualToString:@"contentInset"])) {
4262
- if (((UIScrollView *)object).panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
4263
- [self cancelTopInsetReboundCheck];
4264
- } else {
4265
- [self scheduleTopInsetReboundCheck];
4266
- }
4267
- }
4268
3601
  if (![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
4269
3602
  [self updateHeaderSettledStateForceBusy:YES];
4270
3603
  }
@@ -4329,12 +3662,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4329
3662
  }
4330
3663
  UIScrollView *scrollView = (UIScrollView *)object;
4331
3664
  _currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
4332
- if (_correctingTopInsetRebound) return;
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
4333
3669
  if ([_alignedBodyKeys containsObject:_observedPageKey] ||
4334
3670
  (_nativeSmoothHeaderScrollEnabled && scrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
4335
3671
  !scrollView.isDecelerating && ![_commandBodyKeys containsObject:_observedPageKey])) return;
4336
3672
  _pageOffsets[_nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey] =
4337
3673
  @(_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
3674
+ #if DEBUG
3675
+ if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-after" page:_currentIndex scroll:scrollView];
3676
+ #endif
4338
3677
  [self updateHeaderForScrollView:scrollView];
4339
3678
  return;
4340
3679
  }
@@ -4375,22 +3714,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4375
3714
  ? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
4376
3715
  CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
4377
3716
  CGAffineTransform stickyTransform = CGAffineTransformMakeTranslation(0, pinnedTranslation);
4378
- [self updatePinnedStickyHeader];
4379
- _stickyHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4380
- _nativeTabBarView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4381
- _nativeSubHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4382
- [self updateScrollEdgeContentMasks];
3717
+ _stickyHeaderView.transform = stickyTransform;
3718
+ _nativeTabBarView.transform = stickyTransform;
3719
+ _nativeSubHeaderView.transform = stickyTransform;
4383
3720
  return;
4384
3721
  }
4385
- [self updatePinnedStickyHeader];
4386
3722
  CGAffineTransform collapseTransform = CGAffineTransformMakeTranslation(0, -_headerOffset);
4387
3723
  _sharedHeaderHostView.transform = collapseTransform;
4388
3724
  _headerView.transform = CGAffineTransformIdentity;
4389
3725
  _stickyHeaderView.transform = CGAffineTransformIdentity;
4390
3726
  _nativeTabBarView.transform = CGAffineTransformIdentity;
4391
3727
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
4392
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
4393
- [self updateScrollEdgeContentMasks];
4394
3728
  }
4395
3729
 
4396
3730
  #pragma mark - Pager navigation
@@ -4402,7 +3736,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4402
3736
 
4403
3737
  - (void)goTo:(NSInteger)index animated:(BOOL)animated
4404
3738
  {
4405
- [self cancelTopInsetReboundCheck];
3739
+ [self offsetDiag:@"go-to" page:index scroll:_observedScrollView];
4406
3740
  if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
4407
3741
  _hasReceivedPageCommand = YES;
4408
3742
  _hasAppliedInitialPage = YES;
@@ -4533,7 +3867,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4533
3867
  if (!self->_transitioning && !self->_needsSlotRebuild) {
4534
3868
  [self attachScrollObserverForCurrentPage];
4535
3869
  }
4536
- [self updateScrollEdgeContentMasks];
4537
3870
  });
4538
3871
  }
4539
3872
 
@@ -4585,6 +3918,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4585
3918
  - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
4586
3919
  {
4587
3920
  [self updateHeaderSettledStateForceBusy:YES];
3921
+ [self offsetDiag:@"pager-drag-begin" page:_currentIndex scroll:_observedScrollView];
4588
3922
  if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
4589
3923
  [_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
4590
3924
  }
@@ -4741,7 +4075,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4741
4075
  CGFloat offset = progress - position;
4742
4076
  position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
4743
4077
  [self updatePageStateTransitionProgress:progress];
4744
- [self updateScrollEdgeContentMasks];
4745
4078
  [_nativeTabBarView setProgress:progress];
4746
4079
  const auto emitter = [self eventEmitter];
4747
4080
  if (emitter) {
@@ -4760,7 +4093,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4760
4093
  tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
4761
4094
  }
4762
4095
  [self updatePageStateTransitionProgress:tabProgress];
4763
- [self updateScrollEdgeContentMasks];
4764
4096
  [_nativeTabBarView setProgress:tabProgress];
4765
4097
  const auto emitter = [self eventEmitter];
4766
4098
  if (emitter) {
@@ -4778,6 +4110,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4778
4110
 
4779
4111
  - (void)emitPageSelected:(NSInteger)position
4780
4112
  {
4113
+ [self offsetDiag:@"selection-complete" page:position scroll:_observedScrollView];
4781
4114
  const auto emitter = [self eventEmitter];
4782
4115
  if (emitter) emitter->onPageSelected({.position = (double)position});
4783
4116
  }
@@ -4922,12 +4255,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4922
4255
  _currentLogicalOffset = logical;
4923
4256
  _headerOffset = _pagerTopLogicalTarget;
4924
4257
  [self applyHeaderOffset];
4258
+ [self offsetDiag:[NSString stringWithFormat:@"pager-top-complete-token=%lu-target=%.4f", (unsigned long)_pagerTopToken, _pagerTopLogicalTarget] page:_currentIndex scroll:scrollView];
4925
4259
  [self cancelPagerTop];
4926
4260
  }
4927
4261
 
4928
4262
  - (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
4929
4263
  {
4930
- [self cancelTopInsetReboundCheck];
4931
4264
  UIScrollView *scrollView = _observedScrollView;
4932
4265
  NSString *key = _observedPageKey;
4933
4266
  if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
@@ -4959,6 +4292,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4959
4292
  [self applyHeaderOffset];
4960
4293
  CGFloat physicalTarget = RNCLimitRestoredScrollOffset(logicalTarget - scrollView.contentInset.top,
4961
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];
4962
4296
  _dispatchingPagerTop = YES;
4963
4297
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:animated];
4964
4298
  _dispatchingPagerTop = NO;
@@ -5023,22 +4357,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5023
4357
  : velocity;
5024
4358
  BOOL hasMotion = MAX(fabs(intent.x), fabs(intent.y)) >= 1;
5025
4359
  BOOL horizontal = hasMotion && fabs(intent.x) > fabs(intent.y);
5026
- if (_sharedHeaderOuterPagerGesture.protectsNestedBody) {
5027
- NSInteger pageCount = _pageControllers.count;
5028
- BOOL validPage = _currentIndex >= 0 && _currentIndex < pageCount;
5029
- NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
5030
- NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
5031
- BOOL outward = validPage &&
5032
- ((_currentIndex == firstPageIndex && intent.x > 0) ||
5033
- (_currentIndex == lastPageIndex && intent.x < 0));
5034
- UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
5035
- BOOL protectInnerPage = horizontal && validPage && pageCount > 1 &&
5036
- _nestedScrollEnabled && _scrollEnabled && _pagerScrollView.scrollEnabled &&
5037
- _pagerScrollView.panGestureRecognizer.enabled && outer != nil && outer.scrollEnabled &&
5038
- [_sharedHeaderOuterPagerGesture.blockedPagerGestures
5039
- containsObject:outer.panGestureRecognizer] && !outward;
5040
- return protectInnerPage;
5041
- }
5042
4360
  RNCCollapsiblePagerLog([NSString stringWithFormat:
5043
4361
  @"header-axis role=outer-pager-block dx=%.2f dy=%.2f velocity=(%.2f,%.2f) result=%d outer-pagers=%lu",
5044
4362
  intent.x,
@@ -5078,21 +4396,24 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5078
4396
  return vertical;
5079
4397
  }
5080
4398
  if (gestureRecognizer != _blockerGesture) return YES;
5081
- UIPanGestureRecognizer *pan = [gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]
5082
- ? (UIPanGestureRecognizer *)gestureRecognizer : nil;
5083
- CGPoint velocity = pan != nil ? [pan velocityInView:self] : CGPointZero;
4399
+ if (!_nestedScrollEnabled) return NO;
4400
+ if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) return NO;
4401
+
4402
+ UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
4403
+ CGPoint velocity = [pan velocityInView:self];
5084
4404
  NSInteger pageCount = _pageControllers.count;
4405
+ if (pageCount == 0 || _currentIndex < 0 || _currentIndex >= pageCount) return NO;
4406
+
5085
4407
  CGFloat absVelocityX = fabs(velocity.x);
5086
4408
  CGFloat absVelocityY = fabs(velocity.y);
5087
- NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
5088
- NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
5089
- BOOL validPage = pageCount > 0 && _currentIndex >= 0 && _currentIndex < pageCount;
5090
- BOOL horizontal = absVelocityX > absVelocityY && absVelocityX >= 1.0f;
5091
- BOOL outward = validPage &&
5092
- ((_currentIndex == firstPageIndex && velocity.x > 0) ||
5093
- (_currentIndex == lastPageIndex && velocity.x < 0));
5094
- BOOL shouldBegin = _nestedScrollEnabled && pan != nil && horizontal && outward;
5095
- return shouldBegin;
4409
+ if (absVelocityX <= absVelocityY || absVelocityX < 1.0f) return NO;
4410
+
4411
+ BOOL isLtr = self.isLtrLayout;
4412
+ NSInteger firstPageIndex = isLtr ? 0 : pageCount - 1;
4413
+ NSInteger lastPageIndex = isLtr ? pageCount - 1 : 0;
4414
+ if (_currentIndex == firstPageIndex && velocity.x > 0) return YES;
4415
+ if (_currentIndex == lastPageIndex && velocity.x < 0) return YES;
4416
+ return NO;
5096
4417
  }
5097
4418
 
5098
4419
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
@@ -5102,29 +4423,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5102
4423
  _sharedHeaderPressCancellationGestureHost == nil) return NO;
5103
4424
  UIView *touchView = touch.view;
5104
4425
  BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
5105
- [touchView isDescendantOfView:_sharedHeaderHostView] ||
5106
- touchView == _pinnedStickyHostView ||
5107
- [touchView isDescendantOfView:_pinnedStickyHostView];
5108
- if (_sharedHeaderOuterPagerGesture.trackedTouch != nil &&
5109
- _sharedHeaderOuterPagerGesture.trackedTouch != touch) return NO;
5110
- UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
5111
- BOOL beginsInNestedBody = !beginsInSharedHeader && _nestedScrollEnabled &&
5112
- _scrollEnabled && _pagerScrollView.scrollEnabled &&
5113
- _pagerScrollView.panGestureRecognizer.enabled && _pageControllers.count > 1 &&
5114
- outer != nil && outer.scrollEnabled &&
5115
- [_sharedHeaderOuterPagerGesture.blockedPagerGestures containsObject:outer.panGestureRecognizer] &&
5116
- (touchView == _pagerScrollView || [touchView isDescendantOfView:_pagerScrollView]);
5117
- if (!beginsInSharedHeader && !beginsInNestedBody) return NO;
5118
-
5119
- _sharedHeaderOuterPagerGesture.trackedTouch = touch;
5120
- _sharedHeaderOuterPagerGesture.protectsNestedBody = beginsInNestedBody;
5121
- _sharedHeaderOuterPagerGesture.cancelsTouchesInView = !beginsInNestedBody;
4426
+ [touchView isDescendantOfView:_sharedHeaderHostView];
4427
+ if (!beginsInSharedHeader) return NO;
5122
4428
 
5123
4429
  RNCCollapsiblePagerLog([NSString stringWithFormat:
5124
- @"outer-pager-touch target=%@ outer-pagers=%lu nested-body=%d",
4430
+ @"outer-pager-touch target=%@ outer-pagers=%lu",
5125
4431
  touchView == nil ? @"none" : NSStringFromClass(touchView.class),
5126
- (unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count,
5127
- beginsInNestedBody]);
4432
+ (unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count]);
5128
4433
  return YES;
5129
4434
  }
5130
4435
  if (gestureRecognizer == _contentPressCancellationGesture) {
@@ -5133,17 +4438,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5133
4438
  return _nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
5134
4439
  _contentPressCancellationGesture.view != nil &&
5135
4440
  [touchView isDescendantOfView:_pagerScrollView] &&
5136
- ![touchView isDescendantOfView:_sharedHeaderHostView] &&
5137
- ![touchView isDescendantOfView:_pinnedStickyHostView];
4441
+ ![touchView isDescendantOfView:_sharedHeaderHostView];
5138
4442
  }
5139
4443
  if (gestureRecognizer == _sharedHeaderPressCancellationGesture) {
5140
4444
  if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
5141
4445
  _sharedHeaderPressCancellationGestureHost == nil) return NO;
5142
4446
  UIView *touchView = touch.view;
5143
4447
  BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
5144
- [touchView isDescendantOfView:_sharedHeaderHostView] ||
5145
- touchView == _pinnedStickyHostView ||
5146
- [touchView isDescendantOfView:_pinnedStickyHostView];
4448
+ [touchView isDescendantOfView:_sharedHeaderHostView];
5147
4449
  if (!beginsInSharedHeader) return NO;
5148
4450
 
5149
4451
  RNCCollapsiblePagerLog([NSString stringWithFormat:
@@ -5154,9 +4456,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5154
4456
  if (gestureRecognizer == _verticalPagerGesture) {
5155
4457
  UIView *touchView = touch.view;
5156
4458
  return touchView != _sharedHeaderHostView &&
5157
- ![touchView isDescendantOfView:_sharedHeaderHostView] &&
5158
- touchView != _pinnedStickyHostView &&
5159
- ![touchView isDescendantOfView:_pinnedStickyHostView];
4459
+ ![touchView isDescendantOfView:_sharedHeaderHostView];
5160
4460
  }
5161
4461
  // A new touch can cancel UIKit's animation before its first frame. Queue a
5162
4462
  // non-animated settle for the next safe transition boundary instead of