@onekeyfe/react-native-pager-view 3.0.179-alpha.2026101502 → 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,319 +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
- (_externalOuterPageActive && !_externalOuterTransitioning);
2054
- BOOL hasOverlay = _topScrollEdgeHard && self.hasFixedTopOverlay &&
2055
- externalOwnerReady && self.window != nil;
2056
- BOOL hasSticky = _topScrollEdgeHard && _stickyHeaderView != nil &&
2057
- externalOwnerReady && self.window != nil;
2058
- BOOL enabled = (hasOverlay || hasSticky) && scrollView != nil;
2059
- BOOL topHostChanged = (_topEdgeInteraction != nil) != hasOverlay ||
2060
- (hasOverlay && _topEdgeInteractionHost != _topOverlayHostView);
2061
- if (_topEdgeScrollView != nil && (!enabled || _topEdgeScrollView != scrollView ||
2062
- topHostChanged)) {
2063
- [_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
2064
- _topEdgeInteraction.scrollView = nil;
2065
- [_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
2066
- _stickyEdgeInteraction.scrollView = nil;
2067
- [_topEdgeMarker removeFromSuperview];
2068
- [_stickyEdgeMarker removeFromSuperview];
2069
- _topEdgeScrollView.topEdgeEffect.style = _originalTopEdgeStyle;
2070
- _topEdgeInteraction = nil;
2071
- _stickyEdgeInteraction = nil;
2072
- _topEdgeMarker = nil;
2073
- _stickyEdgeMarker = nil;
2074
- _topEdgeInteractionHost = nil;
2075
- _stickyEdgeInteractionHost = nil;
2076
- _topEdgeScrollView = nil;
2077
- _originalTopEdgeStyle = nil;
2078
- }
2079
- if (enabled && _topEdgeScrollView == nil) {
2080
- _originalTopEdgeStyle = scrollView.topEdgeEffect.style;
2081
- _topEdgeScrollView = scrollView;
2082
- scrollView.topEdgeEffect.style = UIScrollEdgeEffectStyle.hardStyle;
2083
- }
2084
- if (hasOverlay && enabled && _topEdgeInteraction == nil) {
2085
- // UIKit needs a native element to shape the edge effect around React-drawn content.
2086
- _topEdgeMarker = [UILabel new];
2087
- _topEdgeMarker.frame = _topOverlayHostView.bounds;
2088
- _topEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
2089
- _topEdgeMarker.text = @" ";
2090
- _topEdgeMarker.textColor = UIColor.clearColor;
2091
- _topEdgeMarker.backgroundColor = UIColor.clearColor;
2092
- _topEdgeMarker.userInteractionEnabled = NO;
2093
- _topEdgeMarker.isAccessibilityElement = NO;
2094
- _topEdgeMarker.accessibilityElementsHidden = YES;
2095
- [_topOverlayHostView addSubview:_topEdgeMarker];
2096
- _topEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
2097
- _topEdgeInteraction.edge = UIRectEdgeTop;
2098
- _topEdgeInteraction.scrollView = scrollView;
2099
- [_topOverlayHostView addInteraction:_topEdgeInteraction];
2100
- _topEdgeInteractionHost = _topOverlayHostView;
2101
- }
2102
- BOOL legacyStickyPinned = !_nativeSmoothHeaderScrollEnabled &&
2103
- _stickyHeaderView != nil && _headerOffset >= _headerHeight - 0.5;
2104
- BOOL stickyEnabled = enabled && (_stickyHeaderIsPinned || legacyStickyPinned);
2105
- UIView *stickyInteractionHost = _stickyHeaderIsPinned
2106
- ? _pinnedStickyHostView : _stickyHeaderView;
2107
- if (_stickyEdgeInteractionHost != nil &&
2108
- (!stickyEnabled || _stickyEdgeInteractionHost != stickyInteractionHost)) {
2109
- [_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
2110
- _stickyEdgeInteraction.scrollView = nil;
2111
- [_stickyEdgeMarker removeFromSuperview];
2112
- _stickyEdgeMarker = nil;
2113
- _stickyEdgeInteraction = nil;
2114
- _stickyEdgeInteractionHost = nil;
2115
- }
2116
- if (stickyEnabled && _stickyEdgeInteraction == nil) {
2117
- _stickyEdgeMarker = [UILabel new];
2118
- _stickyEdgeMarker.frame = stickyInteractionHost.bounds;
2119
- _stickyEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
2120
- _stickyEdgeMarker.text = @" ";
2121
- _stickyEdgeMarker.textColor = UIColor.clearColor;
2122
- _stickyEdgeMarker.backgroundColor = UIColor.clearColor;
2123
- _stickyEdgeMarker.userInteractionEnabled = NO;
2124
- _stickyEdgeMarker.isAccessibilityElement = NO;
2125
- _stickyEdgeMarker.accessibilityElementsHidden = YES;
2126
- [stickyInteractionHost addSubview:_stickyEdgeMarker];
2127
- _stickyEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
2128
- _stickyEdgeInteraction.edge = UIRectEdgeTop;
2129
- _stickyEdgeInteraction.scrollView = scrollView;
2130
- [stickyInteractionHost addInteraction:_stickyEdgeInteraction];
2131
- _stickyEdgeInteractionHost = stickyInteractionHost;
2132
- }
2133
- [self updateScrollEdgeContentMasks];
2134
- }
2135
- }
2136
-
2137
1715
  - (void)dealloc
2138
1716
  {
2139
- [self updateTopScrollEdgeForScrollView:nil];
2140
1717
  [self cancelHeaderSettledCheck];
2141
1718
  [[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
2142
1719
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
@@ -2189,7 +1766,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2189
1766
  _hasPendingDirectNativePagerChange = NO;
2190
1767
  if (_directNativePagerEnabled == enabled) return;
2191
1768
 
2192
- [self restoreScrollEdgeContentMasks];
2193
1769
  [self detachScrollObserver];
2194
1770
  [self restoreSharedHeadersToContainer];
2195
1771
  if (_blockerGesture != nil) {
@@ -2265,33 +1841,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2265
1841
 
2266
1842
  #pragma mark - Smooth shared header ownership
2267
1843
 
2268
- - (void)restorePinnedStickyHeader
2269
- {
2270
- if (!_stickyHeaderIsPinned) return;
2271
- _stickyHeaderIsPinned = NO;
2272
- _stickyHeaderView.transform = CGAffineTransformIdentity;
2273
- _nativeTabBarView.transform = CGAffineTransformIdentity;
2274
- _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2275
- [_stickyHeaderView removeFromSuperview];
2276
- [_nativeTabBarView removeFromSuperview];
2277
- [_nativeSubHeaderView removeFromSuperview];
2278
- if (_stickyHeaderView != nil) [_sharedHeaderHostView addSubview:_stickyHeaderView];
2279
- [_sharedHeaderHostView addSubview:_nativeTabBarView];
2280
- [_sharedHeaderHostView addSubview:_nativeSubHeaderView];
2281
- _pinnedStickyHostView.hidden = YES;
2282
- _pinnedStickyHostView.layer.zPosition = 0;
2283
- [_containerView addSubview:_pinnedStickyHostView];
2284
- if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
2285
- ((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView = _sharedHeaderHostView;
2286
- }
2287
- [_nativeTabBarView updateAncestorGesturePrecedence];
2288
- [_nativeSubHeaderView updateAncestorGesturePrecedence];
2289
- }
2290
-
2291
1844
  - (void)restoreSharedHeadersToContainer
2292
1845
  {
2293
- [self restorePinnedStickyHeader];
2294
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2295
1846
  _sharedHeaderScrollView = nil;
2296
1847
  _sharedHeadersLiftedForPagerTransition = NO;
2297
1848
  _sharedHeaderHostView.layer.zPosition = 0;
@@ -2299,15 +1850,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2299
1850
  [_containerView addSubview:_sharedHeaderHostView];
2300
1851
  }
2301
1852
  [self layoutSharedHeaders];
2302
- if (self.hasFixedTopOverlay) {
2303
- [_containerView bringSubviewToFront:_topOverlayHostView];
2304
- }
2305
1853
  }
2306
1854
 
2307
1855
  - (void)attachSharedHeadersToScrollView:(UIScrollView *)scrollView
2308
1856
  {
2309
1857
  if (!_nativeSmoothHeaderScrollEnabled || _transitioning || _isPagerDragging ||
2310
- _externalOuterTransitioning ||
2311
1858
  scrollView == nil || _isBeingRecycled) return;
2312
1859
  if ((UIView *)scrollView == _sharedHeaderHostView ||
2313
1860
  [scrollView isDescendantOfView:_sharedHeaderHostView]) {
@@ -2319,16 +1866,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2319
1866
  }
2320
1867
  if (_sharedHeaderScrollView == scrollView &&
2321
1868
  _sharedHeaderHostView.superview == scrollView) {
2322
- _sharedHeaderHostView.layer.zPosition =
2323
- _topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
2324
1869
  [self layoutSharedHeaders];
2325
1870
  return;
2326
1871
  }
2327
1872
 
2328
1873
  _sharedHeaderScrollView = scrollView;
2329
1874
  _sharedHeadersLiftedForPagerTransition = NO;
2330
- _sharedHeaderHostView.layer.zPosition =
2331
- _topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
1875
+ _sharedHeaderHostView.layer.zPosition = 1000;
2332
1876
  if (_sharedHeaderHostView.superview != scrollView) {
2333
1877
  [scrollView addSubview:_sharedHeaderHostView];
2334
1878
  }
@@ -2375,114 +1919,56 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2375
1919
 
2376
1920
  _sharedHeaderHostView.frame = CGRectMake(
2377
1921
  0,
2378
- attachedToList ? -totalHeight : self.effectiveTopOverlayHeight,
1922
+ attachedToList ? -totalHeight : 0,
2379
1923
  width,
2380
1924
  totalHeight
2381
1925
  );
2382
1926
  CGFloat stickyY = _headerHeight;
2383
1927
  _headerView.frame = CGRectMake(0, 0, width, _headerHeight);
2384
- CGPoint pinnedOrigin = _stickyHeaderIsPinned
2385
- ? [_sharedHeaderScrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight) fromView:_containerView]
2386
- : CGPointMake(0, self.effectiveTopOverlayHeight);
2387
- _pinnedStickyHostView.frame = CGRectMake(
2388
- pinnedOrigin.x, pinnedOrigin.y, width, _stickyHeaderHeight);
2389
- CGFloat stickyChildY = _stickyHeaderIsPinned ? 0 : stickyY;
2390
- _stickyHeaderView.frame = CGRectMake(0, stickyChildY, width, _stickyHeaderHeight);
1928
+ _stickyHeaderView.frame = CGRectMake(0, stickyY, width, _stickyHeaderHeight);
2391
1929
  CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
2392
1930
  ? 0
2393
1931
  : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2394
1932
  _nativeTabBarView.frame = CGRectMake(
2395
1933
  0,
2396
- stickyChildY,
1934
+ stickyY,
2397
1935
  width,
2398
1936
  nativeTabBarVisibleHeight
2399
1937
  );
2400
1938
  _nativeSubHeaderView.frame = CGRectMake(
2401
1939
  0,
2402
- stickyChildY + nativeTabBarVisibleHeight,
1940
+ stickyY + nativeTabBarVisibleHeight,
2403
1941
  width,
2404
1942
  MIN(
2405
1943
  MAX(0, _stickyHeaderHeight - nativeTabBarVisibleHeight),
2406
1944
  _nativeSubHeaderHeight
2407
1945
  )
2408
1946
  );
2409
- _topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
2410
- _topOverlayView.frame = _topOverlayHostView.bounds;
2411
1947
  [self applyHeaderOffset];
2412
1948
  }
2413
1949
 
2414
- - (void)updatePinnedStickyHeader
1950
+ - (void)offsetDiag:(NSString *)reason page:(NSInteger)index scroll:(UIScrollView *)scroll
2415
1951
  {
2416
- BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
2417
- _sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
2418
- if (!attachedToList && !_stickyHeaderIsPinned) return;
2419
- UIScrollView *scrollView = _sharedHeaderScrollView;
2420
- NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
2421
- CGFloat refreshInset = scrollView.refreshControl != nil && appliedTop != nil
2422
- ? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
2423
- CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
2424
- CGFloat stickyTop = [_sharedHeaderHostView convertPoint:
2425
- CGPointMake(0, _headerHeight + pinnedTranslation) toView:self].y;
2426
- BOOL shouldPin = _topScrollEdgeHard &&
2427
- _stickyHeaderView != nil && attachedToList && !_transitioning &&
2428
- !_externalOuterTransitioning &&
2429
- !_isPagerDragging && _headerOffset >= _headerHeight - 0.5 &&
2430
- stickyTop <= self.effectiveTopOverlayHeight + 0.5;
2431
- if (_stickyHeaderIsPinned && shouldPin && _pinnedStickyHostView.superview != scrollView) {
2432
- [self updateTopScrollEdgeForScrollView:nil];
2433
- [scrollView addSubview:_pinnedStickyHostView];
2434
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2435
- }
2436
- if (_stickyHeaderIsPinned == shouldPin) {
2437
- if (shouldPin) {
2438
- CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
2439
- fromView:_containerView];
2440
- _pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y,
2441
- CGRectGetWidth(self.bounds), _stickyHeaderHeight);
2442
- }
2443
- return;
2444
- }
2445
-
2446
- _stickyHeaderIsPinned = shouldPin;
2447
- UIView *host = shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
2448
- _stickyHeaderView.transform = CGAffineTransformIdentity;
2449
- _nativeTabBarView.transform = CGAffineTransformIdentity;
2450
- _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2451
- [_stickyHeaderView removeFromSuperview];
2452
- [_nativeTabBarView removeFromSuperview];
2453
- [_nativeSubHeaderView removeFromSuperview];
2454
- if (_stickyHeaderView != nil) [host addSubview:_stickyHeaderView];
2455
- [host addSubview:_nativeTabBarView];
2456
- [host addSubview:_nativeSubHeaderView];
2457
- CGFloat childY = shouldPin ? 0 : _headerHeight;
2458
- CGFloat width = CGRectGetWidth(self.bounds);
2459
- CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
2460
- _stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
2461
- _nativeTabBarView.frame = CGRectMake(0, childY, width, tabHeight);
2462
- _nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
2463
- MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
2464
- _pinnedStickyHostView.hidden = !shouldPin;
2465
- if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
2466
- ((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView =
2467
- shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
2468
- }
2469
- if (shouldPin) {
2470
- [scrollView addSubview:_pinnedStickyHostView];
2471
- _pinnedStickyHostView.layer.zPosition = 1000;
2472
- CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
2473
- fromView:_containerView];
2474
- _pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y, width, _stickyHeaderHeight);
2475
- } else {
2476
- _pinnedStickyHostView.layer.zPosition = 0;
2477
- [_containerView addSubview:_pinnedStickyHostView];
2478
- }
2479
- [_nativeTabBarView updateAncestorGesturePrecedence];
2480
- [_nativeSubHeaderView updateAncestorGesturePrecedence];
2481
- [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
2482
1967
  }
2483
1968
 
2484
1969
  - (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
2485
1970
  {
1971
+ [self offsetDiag:@"prepare-check" page:pageIndex scroll:nil];
2486
1972
  if (!_nativeSmoothHeaderScrollEnabled ||
2487
1973
  pageIndex < 0 || pageIndex >= _pageControllers.count) return;
2488
1974
  UIScrollView *scrollView = [self verticalScrollViewForPageAtIndex:pageIndex];
@@ -2501,6 +1987,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2501
1987
  _transitionHeaderOffset = _headerOffset;
2502
1988
  _transitionPageOffsets = [_pageOffsets copy];
2503
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
2504
1995
  }
2505
1996
 
2506
1997
  - (void)updatePageStateTransitionProgress:(CGFloat)progress
@@ -2517,6 +2008,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2517
2008
  CGFloat firstQ = [_transitionPageOffsets[[self pageKeyForIndex:first]] doubleValue];
2518
2009
  CGFloat secondQ = [_transitionPageOffsets[[self pageKeyForIndex:second]] doubleValue];
2519
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
2520
2021
  _syntheticPageRestore = YES;
2521
2022
  NSInteger visibleFirst = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
2522
2023
  NSInteger visibleLast = MIN(visibleFirst + 1, (NSInteger)_pageControllers.count - 1);
@@ -2558,6 +2059,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2558
2059
  _transitionPageOffsets = nil;
2559
2060
  _transitionCompensatedPages = nil;
2560
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
2561
2066
  }
2562
2067
 
2563
2068
  #pragma mark - React mounting
@@ -2614,7 +2119,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2614
2119
  if (!_nativeSmoothHeaderScrollEnabled && _transitionPageOffsets == nil && !_syntheticPageRestore &&
2615
2120
  ![_pendingPageRestoreKeys containsObject:pageKey] &&
2616
2121
  (ownsObservedPage || _pageOffsets[pageKey] == nil)) {
2122
+ [self offsetDiag:@"mount-release-save-before" page:index scroll:scrollView];
2617
2123
  _pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
2124
+ [self offsetDiag:@"mount-release-save-after" page:index scroll:scrollView];
2618
2125
  }
2619
2126
  break;
2620
2127
  }
@@ -2630,27 +2137,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2630
2137
  - (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
2631
2138
  withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry
2632
2139
  {
2633
- if (_needsSlotRebuild && !_deferTopEdgeSlotRebuild &&
2634
- !_transitioning && !_isPagerDragging) {
2635
- [self rebuildSlots];
2636
- }
2637
- [self registerExternalOuterPager];
2638
- if ([self resolveExternalTopOverlay]) {
2639
- [self reapplyInsetsToObservedScrollView];
2640
- [self setNeedsLayout];
2641
- }
2642
- if (_pendingTopOverlayAppearanceCommit) {
2643
- _pendingTopOverlayAppearanceCommit = NO;
2644
- BOOL localOverlayReady = _topOverlayView.superview == _topOverlayHostView &&
2645
- _topOverlayView.window == self.window;
2646
- BOOL externalOverlayReady = _externalTopOverlayTag > 0 &&
2647
- _externalTopOverlayView.window == self.window;
2648
- if (!_isBeingRecycled && _topScrollEdgeHard && self.window != nil &&
2649
- (localOverlayReady || externalOverlayReady)) {
2650
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2651
- }
2652
- }
2653
- [self updateScrollEdgeContentMasks];
2654
2140
  [_mountingRemovedScrollViews removeAllObjects];
2655
2141
  if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled || self.window == nil ||
2656
2142
  _currentIndex < 0 || _currentIndex >= _pageControllers.count) return;
@@ -2728,11 +2214,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2728
2214
  if (_isBeingRecycled) return;
2729
2215
  NSInteger safeIndex = MIN(MAX(index, 0), _logicalChildren.count);
2730
2216
  [_logicalChildren insertObject:childComponentView atIndex:safeIndex];
2731
- if (_topScrollEdgeHard || _topOverlayView != nil) {
2732
- _needsSlotRebuild = YES;
2733
- } else {
2734
- [self rebuildSlots];
2735
- }
2217
+ [self rebuildSlots];
2736
2218
  }
2737
2219
 
2738
2220
  - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
@@ -2743,32 +2225,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2743
2225
  if (existingIndex == NSNotFound) return;
2744
2226
  [childComponentView removeFromSuperview];
2745
2227
  [_logicalChildren removeObjectAtIndex:existingIndex];
2746
- if (_topScrollEdgeHard || _topOverlayView != nil) {
2747
- _needsSlotRebuild = YES;
2748
- } else {
2749
- [self rebuildSlots];
2750
- }
2228
+ [self rebuildSlots];
2751
2229
  }
2752
2230
 
2753
2231
  - (void)rebuildSlots
2754
2232
  {
2755
- if (_deferTopEdgeSlotRebuild) {
2756
- _needsSlotRebuild = YES;
2757
- return;
2758
- }
2759
2233
  if (_transitioning || _isPagerDragging) {
2760
2234
  _needsSlotRebuild = YES;
2761
2235
  return;
2762
2236
  }
2763
- if (_topScrollEdgeHard &&
2764
- _logicalChildren.count != _pageKeys.count + 2 &&
2765
- _logicalChildren.count != _pageKeys.count + 3) {
2766
- _needsSlotRebuild = YES;
2767
- return;
2768
- }
2769
2237
  _needsSlotRebuild = NO;
2770
- _deferTopEdgeSlotRebuild = NO;
2771
- [self restoreScrollEdgeContentMasks];
2772
2238
  [self detachScrollObserver];
2773
2239
  [self restoreSharedHeadersToContainer];
2774
2240
  // OneKey patch: Fabric reuses these views without resetting transforms
@@ -2780,15 +2246,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2780
2246
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
2781
2247
  [_headerView removeFromSuperview];
2782
2248
  [_stickyHeaderView removeFromSuperview];
2783
- [self updateTopScrollEdgeForScrollView:nil];
2784
- [_topOverlayView removeFromSuperview];
2785
2249
  for (UIViewController *controller in _pageControllers) {
2786
2250
  [controller.view removeFromSuperview];
2787
2251
  }
2788
2252
  _headerView = nil;
2789
2253
  _stickyHeaderView = nil;
2790
- _topOverlayView = nil;
2791
- [self updateTopScrollEdgeForScrollView:nil];
2792
2254
  [_pageControllers removeAllObjects];
2793
2255
 
2794
2256
  if (_logicalChildren.count > 0) {
@@ -2799,15 +2261,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2799
2261
  _stickyHeaderView = _logicalChildren[1];
2800
2262
  [_sharedHeaderHostView addSubview:_stickyHeaderView];
2801
2263
  }
2802
- BOOL hasOverlaySlot = _topScrollEdgeHard &&
2803
- _logicalChildren.count == _pageKeys.count + 3;
2804
- NSInteger pageSlotStart = hasOverlaySlot ? 3 : 2;
2805
- if (hasOverlaySlot) {
2806
- _topOverlayView = _logicalChildren[2];
2807
- [_topOverlayHostView addSubview:_topOverlayView];
2808
- }
2809
- [self resolveExternalTopOverlay];
2810
- for (NSInteger index = pageSlotStart; index < _logicalChildren.count; index++) {
2264
+ for (NSInteger index = 2; index < _logicalChildren.count; index++) {
2811
2265
  UIView *page = _logicalChildren[index];
2812
2266
  UIViewController *controller = [UIViewController new];
2813
2267
  [controller.view addSubview:page];
@@ -2846,10 +2300,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2846
2300
  if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
2847
2301
  if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
2848
2302
  [_containerView bringSubviewToFront:_sharedHeaderHostView];
2849
- if (_stickyHeaderIsPinned) {
2850
- [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
2851
- }
2852
- if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
2853
2303
  [self setNeedsLayout];
2854
2304
  dispatch_async(dispatch_get_main_queue(), ^{
2855
2305
  if (!self->_isBeingRecycled) {
@@ -2862,10 +2312,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2862
2312
  - (void)layoutSubviews
2863
2313
  {
2864
2314
  [super layoutSubviews];
2865
- [self syncScrollEdgeAppearance];
2866
2315
  _containerView.frame = self.bounds;
2867
- [self registerExternalOuterPager];
2868
- BOOL externalHeightChanged = [self resolveExternalTopOverlay];
2869
2316
  if (_directNativePagerEnabled) {
2870
2317
  _pagerScrollView.frame = _containerView.bounds;
2871
2318
  } else {
@@ -2928,9 +2375,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2928
2375
  }
2929
2376
  }
2930
2377
  [self layoutSharedHeaders];
2931
- if (externalHeightChanged) [self reapplyInsetsToObservedScrollView];
2932
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
2933
- [self updateScrollEdgeContentMasks];
2934
2378
  [self updateHeaderSettledStateForceBusy:NO];
2935
2379
  dispatch_async(dispatch_get_main_queue(), ^{
2936
2380
  if (!self->_isBeingRecycled) {
@@ -2942,16 +2386,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2942
2386
 
2943
2387
  - (void)prepareForRecycle
2944
2388
  {
2945
- [_externalOuterPager removeNestedOverlayObserver:self];
2946
- _externalOuterPager = nil;
2947
- [self cancelTopInsetReboundCheck];
2948
- [self restoreScrollEdgeContentMasks];
2949
- [self updateTopScrollEdgeForScrollView:nil];
2950
2389
  [self cancelHeaderSettledCheck];
2951
2390
  [self publishHeaderSettledAtTop:NO identityChanged:NO];
2952
2391
  _isBeingRecycled = YES;
2953
- _pendingTopOverlayAppearanceCommit = NO;
2954
- [self syncScrollEdgeAppearance];
2955
2392
  _generation++;
2956
2393
  [_mountingRemovedScrollViews removeAllObjects];
2957
2394
  _needsScrollObserverReattachAfterMount = NO;
@@ -2971,7 +2408,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2971
2408
  if (appliedTop != nil && scrollView.refreshControl != nil) {
2972
2409
  original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
2973
2410
  }
2411
+ [self offsetDiag:@"release-original-inset-before" page:_currentIndex scroll:scrollView];
2974
2412
  scrollView.contentInset = original;
2413
+ [self offsetDiag:@"release-original-inset-after" page:_currentIndex scroll:scrollView];
2975
2414
  }
2976
2415
  NSValue *indicatorValue = [_originalIndicatorInsets objectForKey:scrollView];
2977
2416
  if (indicatorValue != nil) {
@@ -3000,7 +2439,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3000
2439
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
3001
2440
  [_headerView removeFromSuperview];
3002
2441
  [_stickyHeaderView removeFromSuperview];
3003
- [_topOverlayView removeFromSuperview];
3004
2442
  for (UIViewController *controller in _pageControllers) {
3005
2443
  [controller.view removeFromSuperview];
3006
2444
  }
@@ -3008,9 +2446,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3008
2446
  [_pageControllers removeAllObjects];
3009
2447
  _headerView = nil;
3010
2448
  _stickyHeaderView = nil;
3011
- _topOverlayView = nil;
3012
- _topOverlayHostView.hidden = YES;
3013
- [self updateTopScrollEdgeForScrollView:nil];
3014
2449
  _pageViewController.dataSource = nil;
3015
2450
  _pageViewController.delegate = nil;
3016
2451
  _pagerScrollView.delegate = nil;
@@ -3043,13 +2478,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3043
2478
  _pendingInitialPage = 0;
3044
2479
  _headerHeight = 0;
3045
2480
  _stickyHeaderHeight = 0;
3046
- _topOverlayHeight = 0;
3047
- _externalTopOverlayTag = 0;
3048
- _externalTopOverlayView = nil;
3049
- _externalTopOverlayHeight = 0;
3050
- _externalOuterPageActive = NO;
3051
- _externalOuterTransitioning = NO;
3052
- _topScrollEdgeHard = NO;
3053
2481
  _nativeTabBarHeight = 44;
3054
2482
  _nativeSubHeaderHeight = 74;
3055
2483
  _headerOffset = 0;
@@ -3073,7 +2501,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3073
2501
  _pendingGoToIndex = -1;
3074
2502
  _pendingGoToAnimated = YES;
3075
2503
  _needsSlotRebuild = NO;
3076
- _deferTopEdgeSlotRebuild = NO;
3077
2504
  _hasAppliedInitialPage = NO;
3078
2505
  // OneKey patch: Fabric retains old props when recycling this view.
3079
2506
  _needsPropsReapply = YES;
@@ -3090,9 +2517,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3090
2517
  {
3091
2518
  const auto &oldViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(_props);
3092
2519
  const auto &newViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(props);
3093
- _applyingTopOverlayInsetChange =
3094
- oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard ||
3095
- oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight;
3096
2520
 
3097
2521
  if (_needsPropsReapply || oldViewProps.initialPage != newViewProps.initialPage) {
3098
2522
  _pendingInitialPage = MAX(0, newViewProps.initialPage);
@@ -3142,43 +2566,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3142
2566
  _stickyHeaderHeight = MAX(0, newViewProps.stickyHeaderHeight);
3143
2567
  insetsChanged = YES;
3144
2568
  }
3145
- if (_needsPropsReapply || oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard) {
3146
- _topScrollEdgeHard = newViewProps.topScrollEdgeHard;
3147
- if (oldViewProps.topScrollEdgeHard || newViewProps.topScrollEdgeHard) {
3148
- _deferTopEdgeSlotRebuild = YES;
3149
- [self rebuildSlots];
3150
- NSUInteger generation = _generation;
3151
- dispatch_async(dispatch_get_main_queue(), ^{
3152
- if (self->_generation != generation || self->_isBeingRecycled ||
3153
- !self->_deferTopEdgeSlotRebuild) return;
3154
- self->_deferTopEdgeSlotRebuild = NO;
3155
- [self rebuildSlots];
3156
- });
3157
- }
3158
- insetsChanged = YES;
3159
- }
3160
- if (_needsPropsReapply ||
3161
- oldViewProps.topOverlayColorScheme != newViewProps.topOverlayColorScheme) {
3162
- NSString *nextScheme = RCTNSStringFromString(newViewProps.topOverlayColorScheme);
3163
- if (self.hasFixedTopOverlay && _topScrollEdgeHard &&
3164
- _topOverlayColorScheme != nil &&
3165
- ![_topOverlayColorScheme isEqualToString:nextScheme]) {
3166
- _pendingTopOverlayAppearanceCommit = YES;
3167
- [self updateTopScrollEdgeForScrollView:nil];
3168
- }
3169
- _topOverlayColorScheme = nextScheme;
3170
- [self syncScrollEdgeAppearance];
3171
- }
3172
- if (_needsPropsReapply || oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight) {
3173
- _topOverlayHeight = MAX(0, newViewProps.topOverlayHeight);
3174
- insetsChanged = YES;
3175
- }
3176
- if (_needsPropsReapply ||
3177
- oldViewProps.externalTopOverlayTag != newViewProps.externalTopOverlayTag) {
3178
- _externalTopOverlayTag = newViewProps.externalTopOverlayTag;
3179
- [self registerExternalOuterPager];
3180
- insetsChanged = [self resolveExternalTopOverlay] || insetsChanged;
3181
- }
3182
2569
  if (_needsPropsReapply || oldViewProps.pageKeys != newViewProps.pageKeys) {
3183
2570
  NSString *json = RCTNSStringFromString(newViewProps.pageKeys);
3184
2571
  NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
@@ -3246,23 +2633,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3246
2633
 
3247
2634
  _needsPropsReapply = NO;
3248
2635
  [super updateProps:props oldProps:oldProps];
3249
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
3250
2636
  if (insetsChanged) {
3251
2637
  [self reapplyInsetsToObservedScrollView];
3252
2638
  [self setNeedsLayout];
3253
2639
  }
3254
- _applyingTopOverlayInsetChange = NO;
3255
2640
  if (!_nativeTabBarView.hidden) {
3256
- [_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
2641
+ [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
3257
2642
  }
3258
2643
  if (!_nativeSubHeaderView.hidden) {
3259
- [_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
2644
+ [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
3260
2645
  }
3261
2646
  [_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
3262
- if (_stickyHeaderIsPinned) {
3263
- [_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
3264
- }
3265
- if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
3266
2647
  }
3267
2648
 
3268
2649
  #pragma mark - Collapsible scroll coordination
@@ -3496,17 +2877,21 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3496
2877
  return;
3497
2878
  }
3498
2879
 
2880
+ [self offsetDiag:@"observer-candidate" page:_currentIndex scroll:candidate];
3499
2881
  BOOL replacingCurrentScrollView = _observingContentOffset &&
3500
2882
  (!_nativeSmoothHeaderScrollEnabled || [_observedPageKey isEqualToString:self.currentPageKey]);
3501
2883
  [self detachScrollObserver];
3502
2884
  if (replacingCurrentScrollView) {
3503
2885
  // Legacy replacement discards body state; smooth replacement waits for layout.
2886
+ [self offsetDiag:@"observer-replacement-cache-before" page:_currentIndex scroll:candidate];
3504
2887
  if (!_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
2888
+ [self offsetDiag:@"observer-replacement-cache-after" page:_currentIndex scroll:candidate];
3505
2889
  }
3506
2890
  _observedScrollView = candidate;
3507
2891
  _observedPageKey = [self.currentPageKey copy];
3508
2892
  _observedPageIndex = _currentIndex;
3509
2893
  _observedPageGeneration = _generation;
2894
+ [self offsetDiag:@"observer-attached-before-restore" page:_currentIndex scroll:candidate];
3510
2895
  [self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
3511
2896
  [candidate addObserver:self
3512
2897
  forKeyPath:@"contentOffset"
@@ -3524,7 +2909,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3524
2909
  _observingContentOffset = YES;
3525
2910
  _needsScrollObserverReattachAfterMount = NO;
3526
2911
  _currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
3527
- [self updateTopScrollEdgeForScrollView:candidate];
3528
2912
  [self attachSharedHeadersToScrollView:candidate];
3529
2913
  [self updateSharedHeaderPressCancellationGesture];
3530
2914
  [self updateHeaderSettledStateForceBusy:NO];
@@ -3532,9 +2916,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3532
2916
 
3533
2917
  - (void)detachScrollObserver
3534
2918
  {
3535
- [self cancelTopInsetReboundCheck];
3536
- [self restorePinnedStickyHeader];
3537
- [self updateTopScrollEdgeForScrollView:nil];
3538
2919
  [self cancelHeaderSettledCheck];
3539
2920
  _headerCommandOwner = nil;
3540
2921
  _headerSettledGeometry = nil;
@@ -3544,6 +2925,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3544
2925
  UIScrollView *scrollView = _observedScrollView;
3545
2926
  if (scrollView != nil) {
3546
2927
  [scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
2928
+ [self offsetDiag:@"detach-save-before" page:_currentIndex scroll:scrollView];
3547
2929
  NSString *ownerKey = _nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey;
3548
2930
  BOOL ownerKeyExists = ownerKey != nil && ([_pageKeys containsObject:ownerKey] ||
3549
2931
  (_observedPageIndex >= 0 && _observedPageIndex < _pageControllers.count &&
@@ -3551,6 +2933,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3551
2933
  if (ownerKey != nil && !_nativeSmoothHeaderScrollEnabled) {
3552
2934
  _pageOffsets[ownerKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
3553
2935
  }
2936
+ [self offsetDiag:@"detach-save-after" page:_currentIndex scroll:scrollView];
3554
2937
  if (_observingContentOffset) {
3555
2938
  [scrollView removeObserver:self
3556
2939
  forKeyPath:@"contentOffset"
@@ -3592,9 +2975,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3592
2975
  removeGestureRecognizer:_sharedHeaderOuterPagerGesture];
3593
2976
  }
3594
2977
  _sharedHeaderOuterPagerGesture.blockedPagerGestures = nil;
3595
- _sharedHeaderOuterPagerGesture.trackedTouch = nil;
3596
- _sharedHeaderOuterPagerGesture.protectsNestedBody = NO;
3597
- _sharedHeaderOuterPagerGesture.cancelsTouchesInView = YES;
3598
2978
  _sharedHeaderPressCancellationGestureHost = nil;
3599
2979
  _reactTouchHandler = nil;
3600
2980
  }
@@ -3644,7 +3024,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3644
3024
  }
3645
3025
  }
3646
3026
  if (_sharedHeaderPressCancellationGesture.view == gestureHost &&
3647
- _sharedHeaderOuterPagerGesture.view == _containerView &&
3027
+ _sharedHeaderOuterPagerGesture.view == _sharedHeaderHostView &&
3648
3028
  _reactTouchHandler == reactTouchHandler) {
3649
3029
  _sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
3650
3030
  return;
@@ -3686,7 +3066,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3686
3066
  _reactTouchHandler = reactTouchHandler;
3687
3067
  _sharedHeaderPressCancellationGestureHost = gestureHost;
3688
3068
  [gestureHost addGestureRecognizer:_sharedHeaderPressCancellationGesture];
3689
- [_containerView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
3069
+ [_sharedHeaderHostView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
3690
3070
  RNCCollapsiblePagerLog([NSString stringWithFormat:
3691
3071
  @"press-cancel-bridge attached=1 outer-pagers=%lu",
3692
3072
  (unsigned long)outerPagerGestures.count]);
@@ -3758,11 +3138,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3758
3138
 
3759
3139
  - (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer
3760
3140
  {
3761
- if (recognizer == _pendingTopInsetReboundScrollView.panGestureRecognizer &&
3762
- (recognizer.state == UIGestureRecognizerStateBegan ||
3763
- recognizer.state == UIGestureRecognizerStateChanged)) {
3764
- [self cancelTopInsetReboundCheck];
3765
- }
3766
3141
  if (recognizer == _observedScrollView.panGestureRecognizer) {
3767
3142
  [self updateHeaderSettledStateForceBusy:
3768
3143
  recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged];
@@ -3830,7 +3205,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3830
3205
  [_managedRefreshControls removeObjectForKey:scrollView];
3831
3206
  if (current != nil) [_managedRefreshControls setObject:current forKey:scrollView];
3832
3207
  }
3833
- [RNCRefreshControlOffsetState setOffset:self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight
3208
+ [RNCRefreshControlOffsetState setOffset:_headerHeight + _stickyHeaderHeight
3834
3209
  forControl:current owner:self];
3835
3210
  }
3836
3211
 
@@ -3844,53 +3219,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3844
3219
  [_managedRefreshControls removeObjectForKey:scrollView];
3845
3220
  }
3846
3221
 
3847
- - (void)cancelTopInsetReboundCheck
3848
- {
3849
- [_topInsetReboundTimer invalidate];
3850
- _topInsetReboundTimer = nil;
3851
- _pendingTopInsetReboundScrollView = nil;
3852
- _pendingTopInsetReboundPageKey = nil;
3853
- }
3854
-
3855
- - (void)scheduleTopInsetReboundCheck
3856
- {
3857
- if (_pendingTopInsetReboundScrollView == nil ||
3858
- _pendingTopInsetReboundScrollView != _observedScrollView) return;
3859
- [_topInsetReboundTimer invalidate];
3860
- __weak __typeof__(self) weakSelf = self;
3861
- _topInsetReboundTimer = [NSTimer scheduledTimerWithTimeInterval:0.15 repeats:NO block:^(NSTimer *timer) {
3862
- __strong __typeof__(weakSelf) self = weakSelf;
3863
- if (self == nil || self->_topInsetReboundTimer != timer) return;
3864
- self->_topInsetReboundTimer = nil;
3865
- UIScrollView *scrollView = self->_pendingTopInsetReboundScrollView;
3866
- if (scrollView == nil || scrollView != self->_observedScrollView ||
3867
- self->_pendingTopInsetReboundGeneration != self->_generation ||
3868
- ![self->_pendingTopInsetReboundPageKey isEqualToString:self.currentPageKey] ||
3869
- self->_isBeingRecycled || self.window == nil ||
3870
- self->_transitioning || self->_isPagerDragging ||
3871
- self->_headerCommandOwner == scrollView || self->_pagerTopPageKey != nil) {
3872
- [self cancelTopInsetReboundCheck];
3873
- return;
3874
- }
3875
- UIGestureRecognizerState pan = scrollView.panGestureRecognizer.state;
3876
- if (scrollView.refreshControl.isRefreshing || scrollView.isDragging ||
3877
- scrollView.isDecelerating || scrollView.isTracking ||
3878
- pan == UIGestureRecognizerStateBegan || pan == UIGestureRecognizerStateChanged ||
3879
- self->_syntheticPageRestore || self->_transitionPageOffsets != nil) {
3880
- [self scheduleTopInsetReboundCheck];
3881
- return;
3882
- }
3883
- CGFloat minimumOffset = -scrollView.adjustedContentInset.top;
3884
- [self cancelTopInsetReboundCheck];
3885
- if (scrollView.contentOffset.y < minimumOffset - 0.5) {
3886
- // UIKit can finish a refresh rebound at the inset that preceded an overlay resize.
3887
- self->_correctingTopInsetRebound = YES;
3888
- [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, minimumOffset) animated:NO];
3889
- self->_correctingTopInsetRebound = NO;
3890
- }
3891
- }];
3892
- }
3893
-
3894
3222
  // OneKey patch: release observers, headers and saved state while the scroll view
3895
3223
  // still represents the old list, before Fabric applies its next owner's props.
3896
3224
  - (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)resetSavedOffset
@@ -3899,7 +3227,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3899
3227
  [self releaseRefreshControlForScrollView:scrollView];
3900
3228
  if (scrollView == _observedScrollView) {
3901
3229
  [self detachScrollObserver];
3230
+ [self offsetDiag:resetSavedOffset ? @"release-reset-before" : @"release-retain-before" page:_currentIndex scroll:scrollView];
3902
3231
  if (resetSavedOffset && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
3232
+ [self offsetDiag:@"release-reset-after" page:_currentIndex scroll:scrollView];
3903
3233
  }
3904
3234
  if (scrollView == _sharedHeaderScrollView) {
3905
3235
  [self restoreSharedHeadersToContainer];
@@ -3919,8 +3249,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3919
3249
  [_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
3920
3250
  scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
3921
3251
  [_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
3252
+ [self offsetDiag:@"release-original-offset-before" page:_currentIndex scroll:scrollView];
3922
3253
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
3923
3254
  animated:NO];
3255
+ [self offsetDiag:@"release-original-offset-after" page:_currentIndex scroll:scrollView];
3924
3256
  [_originalInsets removeObjectForKey:scrollView];
3925
3257
  [_appliedTopInsets removeObjectForKey:scrollView];
3926
3258
  [_originalIndicatorInsets removeObjectForKey:scrollView];
@@ -3951,6 +3283,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3951
3283
  {
3952
3284
  // Smooth pages retain their complete offset while replacement identity and
3953
3285
  // layout settle. Legacy pages retain the historical header-only policy.
3286
+ [self offsetDiag:restore ? @"restore-enter" : @"insets-enter" page:pageIndex scroll:scrollView];
3954
3287
  NSString *pageKey = [self pageKeyForIndex:pageIndex];
3955
3288
  RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
3956
3289
  BOOL deferredReleasedIdentity = NO;
@@ -3996,8 +3329,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3996
3329
  deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
3997
3330
  RNCCollapsiblePagerFallbackDebounceInterval;
3998
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
3999
3335
  if (!deferredReleasedIdentity) {
3336
+ [self offsetDiag:@"released-identity-cache-before" page:pageIndex scroll:scrollView];
4000
3337
  if (!sameList && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[pageKey] = @(_headerOffset);
3338
+ [self offsetDiag:@"released-identity-cache-after" page:pageIndex scroll:scrollView];
4001
3339
  [_releasedPageScrollStates removeObjectForKey:pageKey];
4002
3340
  }
4003
3341
  }
@@ -4022,24 +3360,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4022
3360
  // suppress its later vertical movement while the pager is still pending.
4023
3361
  scrollView.directionalLockEnabled = _nativeSmoothHeaderScrollEnabled
4024
3362
  ? NO : [_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
4025
- [self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight + self.effectiveTopOverlayHeight];
3363
+ [self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight];
4026
3364
  [self updateRefreshControlForScrollView:scrollView];
4027
3365
  CGFloat previousTopInset = scrollView.contentInset.top;
4028
3366
  CGFloat previousLogicalOffset = scrollView.contentOffset.y + previousTopInset;
4029
- // Preserve UIKit's rebound after refresh while the content is beyond its top inset.
4030
- BOOL wasReturningFromPull = scrollView.contentOffset.y < -scrollView.adjustedContentInset.top - 0.5;
4031
3367
  NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
4032
3368
  // UIRefreshControl owns its temporary top inset, including while ending.
4033
3369
  CGFloat refreshInset = appliedTop != nil && scrollView.refreshControl != nil
4034
3370
  ? MAX(0, previousTopInset - appliedTop.doubleValue) : 0;
4035
3371
  UIEdgeInsets next = original;
4036
- next.top += self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight;
3372
+ next.top += _headerHeight + _stickyHeaderHeight;
4037
3373
  CGFloat pagerTopInset = next.top;
4038
3374
  next.top += refreshInset;
4039
3375
  [_appliedTopInsets setObject:@(pagerTopInset) forKey:scrollView];
4040
3376
  // Short pages still need enough range to collapse the header; long pages
4041
3377
  // must end at the last row rather than leave a header-sized blank footer.
4042
- next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight - self.effectiveTopOverlayHeight
3378
+ next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight
4043
3379
  - original.top - original.bottom - scrollView.contentSize.height);
4044
3380
  if (UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, next) && !restore) {
4045
3381
  // UICollectionView may report contentSize during a bounce without changing
@@ -4049,7 +3385,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4049
3385
  }
4050
3386
  BOOL previousSyntheticRestore = _syntheticPageRestore;
4051
3387
  if (_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:pageKey]) _syntheticPageRestore = YES;
3388
+ [self offsetDiag:@"inset-assignment-before" page:pageIndex scroll:scrollView];
4052
3389
  scrollView.contentInset = next;
3390
+ [self offsetDiag:@"inset-assignment-after" page:pageIndex scroll:scrollView];
4053
3391
  UIEdgeInsets indicatorInsets = scrollView.verticalScrollIndicatorInsets;
4054
3392
  indicatorInsets.top = pagerTopInset;
4055
3393
  scrollView.verticalScrollIndicatorInsets = indicatorInsets;
@@ -4081,6 +3419,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4081
3419
  } else {
4082
3420
  restoredLogicalOffset = deferredReleasedIdentity || saved == nil ? _headerOffset : saved.doubleValue;
4083
3421
  }
3422
+ #if DEBUG
3423
+ [self offsetDiag:[NSString stringWithFormat:@"restore-candidate-saved=%.4f-headerAlignedLogical=%.4f", saved.doubleValue, restoredLogicalOffset] page:pageIndex scroll:scrollView];
3424
+ #endif
4084
3425
  intendedLogicalOffset = restoredLogicalOffset;
4085
3426
  // Cancellation restores the visible source, while retaining its unreachable reading intent.
4086
3427
  if (_nativeSmoothHeaderScrollEnabled && _headerOffset >= _headerHeight &&
@@ -4093,6 +3434,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4093
3434
  }
4094
3435
  // An inactive list may shrink after its offset observer was detached.
4095
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
4096
3440
  targetOffset = RNCLimitRestoredScrollOffset(
4097
3441
  targetOffset,
4098
3442
  scrollView.contentSize.height,
@@ -4101,17 +3445,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4101
3445
  next.bottom
4102
3446
  );
4103
3447
  }
3448
+ #if DEBUG
3449
+ [self offsetDiag:[NSString stringWithFormat:@"restore-final-target=%.4f-restore=%d", targetOffset, restore] page:pageIndex scroll:scrollView];
3450
+ #endif
4104
3451
  // A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
4105
- if (restore || (previousTopInset != next.top && !wasReturningFromPull)) {
3452
+ if (restore || previousTopInset != next.top) {
4106
3453
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, targetOffset) animated:NO];
4107
3454
  }
4108
- if (_applyingTopOverlayInsetChange && !restore && wasReturningFromPull &&
4109
- previousTopInset != next.top && scrollView == _observedScrollView) {
4110
- _pendingTopInsetReboundScrollView = scrollView;
4111
- _pendingTopInsetReboundPageKey = [pageKey copy];
4112
- _pendingTopInsetReboundGeneration = _generation;
4113
- [self scheduleTopInsetReboundCheck];
4114
- }
4115
3455
  if (_nativeSmoothHeaderScrollEnabled && restore && targetOffset + next.top + 0.5 < intendedLogicalOffset) {
4116
3456
  [_pendingPageRestoreKeys addObject:pageKey];
4117
3457
  }
@@ -4121,6 +3461,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4121
3461
  if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:scrollView];
4122
3462
  else [self applyHeaderOffset];
4123
3463
  }
3464
+ [self offsetDiag:@"restore-post" page:pageIndex scroll:scrollView];
4124
3465
  }
4125
3466
 
4126
3467
  - (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
@@ -4137,7 +3478,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4137
3478
  _observedPageKey == nil || ![_observedPageKey isEqualToString:self.currentPageKey] ||
4138
3479
  _observedPageIndex != _currentIndex || _observedPageGeneration != _generation ||
4139
3480
  _transitioning || _isPagerDragging || _transitionPageOffsets != nil || _syntheticPageRestore) return;
4140
- [self cancelTopInsetReboundCheck];
4141
3481
  NSInteger phase = [notification.userInfo[@"phase"] integerValue];
4142
3482
  [self updateHeaderSettledStateForceBusy:NO];
4143
3483
  if (phase == 1) _headerCommandOwner = scrollView;
@@ -4170,6 +3510,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4170
3510
  _headerOffset = 0;
4171
3511
  [self applyHeaderOffset];
4172
3512
  }
3513
+ [self offsetDiag:@"list-command-body-takeover" page:_currentIndex scroll:scrollView];
4173
3514
  }
4174
3515
 
4175
3516
  - (void)nativeScrollerWillReceiveScrollCommand:(RNCNativeScrollerComponentView *)scroller
@@ -4181,12 +3522,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4181
3522
  _observedPageIndex != _currentIndex || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
4182
3523
  _observedPageGeneration != _generation || _transitioning || _isPagerDragging ||
4183
3524
  _transitionPageOffsets != nil || _syntheticPageRestore) return;
4184
- [self cancelTopInsetReboundCheck];
4185
3525
  if (!_dispatchingPagerTop) {
4186
3526
  [self cancelPagerTop];
4187
3527
  [_alignedBodyKeys removeObject:ownerKey];
4188
3528
  }
4189
3529
  if (!_nativeSmoothHeaderScrollEnabled) return;
3530
+ [self offsetDiag:@"command-pending-cancel-before" page:_observedPageIndex scroll:scrollView];
4190
3531
  [_pendingPageRestoreKeys removeObject:ownerKey];
4191
3532
  _currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
4192
3533
  [_commandBodyKeys removeObject:ownerKey];
@@ -4195,6 +3536,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4195
3536
  [_displayBodyOverrides removeObjectForKey:ownerKey];
4196
3537
  _pageOffsets[ownerKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
4197
3538
  [self updateHeaderForScrollView:scrollView];
3539
+ [self offsetDiag:@"command-pending-cancel-after" page:_observedPageIndex scroll:scrollView];
4198
3540
  }
4199
3541
 
4200
3542
 
@@ -4256,14 +3598,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4256
3598
  return;
4257
3599
  }
4258
3600
  if (context == RNCCollapsiblePagerContentOffsetContext && object == _observedScrollView) {
4259
- if (_pendingTopInsetReboundScrollView == object &&
4260
- ([keyPath isEqualToString:@"contentOffset"] || [keyPath isEqualToString:@"contentInset"])) {
4261
- if (((UIScrollView *)object).panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
4262
- [self cancelTopInsetReboundCheck];
4263
- } else {
4264
- [self scheduleTopInsetReboundCheck];
4265
- }
4266
- }
4267
3601
  if (![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
4268
3602
  [self updateHeaderSettledStateForceBusy:YES];
4269
3603
  }
@@ -4328,12 +3662,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4328
3662
  }
4329
3663
  UIScrollView *scrollView = (UIScrollView *)object;
4330
3664
  _currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
4331
- 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
4332
3669
  if ([_alignedBodyKeys containsObject:_observedPageKey] ||
4333
3670
  (_nativeSmoothHeaderScrollEnabled && scrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
4334
3671
  !scrollView.isDecelerating && ![_commandBodyKeys containsObject:_observedPageKey])) return;
4335
3672
  _pageOffsets[_nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey] =
4336
3673
  @(_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
3674
+ #if DEBUG
3675
+ if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-after" page:_currentIndex scroll:scrollView];
3676
+ #endif
4337
3677
  [self updateHeaderForScrollView:scrollView];
4338
3678
  return;
4339
3679
  }
@@ -4374,22 +3714,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4374
3714
  ? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
4375
3715
  CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
4376
3716
  CGAffineTransform stickyTransform = CGAffineTransformMakeTranslation(0, pinnedTranslation);
4377
- [self updatePinnedStickyHeader];
4378
- _stickyHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4379
- _nativeTabBarView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4380
- _nativeSubHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
4381
- [self updateScrollEdgeContentMasks];
3717
+ _stickyHeaderView.transform = stickyTransform;
3718
+ _nativeTabBarView.transform = stickyTransform;
3719
+ _nativeSubHeaderView.transform = stickyTransform;
4382
3720
  return;
4383
3721
  }
4384
- [self updatePinnedStickyHeader];
4385
3722
  CGAffineTransform collapseTransform = CGAffineTransformMakeTranslation(0, -_headerOffset);
4386
3723
  _sharedHeaderHostView.transform = collapseTransform;
4387
3724
  _headerView.transform = CGAffineTransformIdentity;
4388
3725
  _stickyHeaderView.transform = CGAffineTransformIdentity;
4389
3726
  _nativeTabBarView.transform = CGAffineTransformIdentity;
4390
3727
  _nativeSubHeaderView.transform = CGAffineTransformIdentity;
4391
- [self updateTopScrollEdgeForScrollView:_observedScrollView];
4392
- [self updateScrollEdgeContentMasks];
4393
3728
  }
4394
3729
 
4395
3730
  #pragma mark - Pager navigation
@@ -4401,7 +3736,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4401
3736
 
4402
3737
  - (void)goTo:(NSInteger)index animated:(BOOL)animated
4403
3738
  {
4404
- [self cancelTopInsetReboundCheck];
3739
+ [self offsetDiag:@"go-to" page:index scroll:_observedScrollView];
4405
3740
  if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
4406
3741
  _hasReceivedPageCommand = YES;
4407
3742
  _hasAppliedInitialPage = YES;
@@ -4532,7 +3867,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4532
3867
  if (!self->_transitioning && !self->_needsSlotRebuild) {
4533
3868
  [self attachScrollObserverForCurrentPage];
4534
3869
  }
4535
- [self updateScrollEdgeContentMasks];
4536
3870
  });
4537
3871
  }
4538
3872
 
@@ -4584,6 +3918,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4584
3918
  - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
4585
3919
  {
4586
3920
  [self updateHeaderSettledStateForceBusy:YES];
3921
+ [self offsetDiag:@"pager-drag-begin" page:_currentIndex scroll:_observedScrollView];
4587
3922
  if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
4588
3923
  [_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
4589
3924
  }
@@ -4740,7 +4075,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4740
4075
  CGFloat offset = progress - position;
4741
4076
  position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
4742
4077
  [self updatePageStateTransitionProgress:progress];
4743
- [self updateScrollEdgeContentMasks];
4744
4078
  [_nativeTabBarView setProgress:progress];
4745
4079
  const auto emitter = [self eventEmitter];
4746
4080
  if (emitter) {
@@ -4759,7 +4093,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4759
4093
  tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
4760
4094
  }
4761
4095
  [self updatePageStateTransitionProgress:tabProgress];
4762
- [self updateScrollEdgeContentMasks];
4763
4096
  [_nativeTabBarView setProgress:tabProgress];
4764
4097
  const auto emitter = [self eventEmitter];
4765
4098
  if (emitter) {
@@ -4777,6 +4110,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4777
4110
 
4778
4111
  - (void)emitPageSelected:(NSInteger)position
4779
4112
  {
4113
+ [self offsetDiag:@"selection-complete" page:position scroll:_observedScrollView];
4780
4114
  const auto emitter = [self eventEmitter];
4781
4115
  if (emitter) emitter->onPageSelected({.position = (double)position});
4782
4116
  }
@@ -4921,12 +4255,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4921
4255
  _currentLogicalOffset = logical;
4922
4256
  _headerOffset = _pagerTopLogicalTarget;
4923
4257
  [self applyHeaderOffset];
4258
+ [self offsetDiag:[NSString stringWithFormat:@"pager-top-complete-token=%lu-target=%.4f", (unsigned long)_pagerTopToken, _pagerTopLogicalTarget] page:_currentIndex scroll:scrollView];
4924
4259
  [self cancelPagerTop];
4925
4260
  }
4926
4261
 
4927
4262
  - (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
4928
4263
  {
4929
- [self cancelTopInsetReboundCheck];
4930
4264
  UIScrollView *scrollView = _observedScrollView;
4931
4265
  NSString *key = _observedPageKey;
4932
4266
  if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
@@ -4958,6 +4292,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
4958
4292
  [self applyHeaderOffset];
4959
4293
  CGFloat physicalTarget = RNCLimitRestoredScrollOffset(logicalTarget - scrollView.contentInset.top,
4960
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];
4961
4296
  _dispatchingPagerTop = YES;
4962
4297
  [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:animated];
4963
4298
  _dispatchingPagerTop = NO;
@@ -5022,22 +4357,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5022
4357
  : velocity;
5023
4358
  BOOL hasMotion = MAX(fabs(intent.x), fabs(intent.y)) >= 1;
5024
4359
  BOOL horizontal = hasMotion && fabs(intent.x) > fabs(intent.y);
5025
- if (_sharedHeaderOuterPagerGesture.protectsNestedBody) {
5026
- NSInteger pageCount = _pageControllers.count;
5027
- BOOL validPage = _currentIndex >= 0 && _currentIndex < pageCount;
5028
- NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
5029
- NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
5030
- BOOL outward = validPage &&
5031
- ((_currentIndex == firstPageIndex && intent.x > 0) ||
5032
- (_currentIndex == lastPageIndex && intent.x < 0));
5033
- UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
5034
- BOOL protectInnerPage = horizontal && validPage && pageCount > 1 &&
5035
- _nestedScrollEnabled && _scrollEnabled && _pagerScrollView.scrollEnabled &&
5036
- _pagerScrollView.panGestureRecognizer.enabled && outer != nil && outer.scrollEnabled &&
5037
- [_sharedHeaderOuterPagerGesture.blockedPagerGestures
5038
- containsObject:outer.panGestureRecognizer] && !outward;
5039
- return protectInnerPage;
5040
- }
5041
4360
  RNCCollapsiblePagerLog([NSString stringWithFormat:
5042
4361
  @"header-axis role=outer-pager-block dx=%.2f dy=%.2f velocity=(%.2f,%.2f) result=%d outer-pagers=%lu",
5043
4362
  intent.x,
@@ -5077,21 +4396,24 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5077
4396
  return vertical;
5078
4397
  }
5079
4398
  if (gestureRecognizer != _blockerGesture) return YES;
5080
- UIPanGestureRecognizer *pan = [gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]
5081
- ? (UIPanGestureRecognizer *)gestureRecognizer : nil;
5082
- 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];
5083
4404
  NSInteger pageCount = _pageControllers.count;
4405
+ if (pageCount == 0 || _currentIndex < 0 || _currentIndex >= pageCount) return NO;
4406
+
5084
4407
  CGFloat absVelocityX = fabs(velocity.x);
5085
4408
  CGFloat absVelocityY = fabs(velocity.y);
5086
- NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
5087
- NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
5088
- BOOL validPage = pageCount > 0 && _currentIndex >= 0 && _currentIndex < pageCount;
5089
- BOOL horizontal = absVelocityX > absVelocityY && absVelocityX >= 1.0f;
5090
- BOOL outward = validPage &&
5091
- ((_currentIndex == firstPageIndex && velocity.x > 0) ||
5092
- (_currentIndex == lastPageIndex && velocity.x < 0));
5093
- BOOL shouldBegin = _nestedScrollEnabled && pan != nil && horizontal && outward;
5094
- 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;
5095
4417
  }
5096
4418
 
5097
4419
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
@@ -5101,29 +4423,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5101
4423
  _sharedHeaderPressCancellationGestureHost == nil) return NO;
5102
4424
  UIView *touchView = touch.view;
5103
4425
  BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
5104
- [touchView isDescendantOfView:_sharedHeaderHostView] ||
5105
- touchView == _pinnedStickyHostView ||
5106
- [touchView isDescendantOfView:_pinnedStickyHostView];
5107
- if (_sharedHeaderOuterPagerGesture.trackedTouch != nil &&
5108
- _sharedHeaderOuterPagerGesture.trackedTouch != touch) return NO;
5109
- UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
5110
- BOOL beginsInNestedBody = !beginsInSharedHeader && _nestedScrollEnabled &&
5111
- _scrollEnabled && _pagerScrollView.scrollEnabled &&
5112
- _pagerScrollView.panGestureRecognizer.enabled && _pageControllers.count > 1 &&
5113
- outer != nil && outer.scrollEnabled &&
5114
- [_sharedHeaderOuterPagerGesture.blockedPagerGestures containsObject:outer.panGestureRecognizer] &&
5115
- (touchView == _pagerScrollView || [touchView isDescendantOfView:_pagerScrollView]);
5116
- if (!beginsInSharedHeader && !beginsInNestedBody) return NO;
5117
-
5118
- _sharedHeaderOuterPagerGesture.trackedTouch = touch;
5119
- _sharedHeaderOuterPagerGesture.protectsNestedBody = beginsInNestedBody;
5120
- _sharedHeaderOuterPagerGesture.cancelsTouchesInView = !beginsInNestedBody;
4426
+ [touchView isDescendantOfView:_sharedHeaderHostView];
4427
+ if (!beginsInSharedHeader) return NO;
5121
4428
 
5122
4429
  RNCCollapsiblePagerLog([NSString stringWithFormat:
5123
- @"outer-pager-touch target=%@ outer-pagers=%lu nested-body=%d",
4430
+ @"outer-pager-touch target=%@ outer-pagers=%lu",
5124
4431
  touchView == nil ? @"none" : NSStringFromClass(touchView.class),
5125
- (unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count,
5126
- beginsInNestedBody]);
4432
+ (unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count]);
5127
4433
  return YES;
5128
4434
  }
5129
4435
  if (gestureRecognizer == _contentPressCancellationGesture) {
@@ -5132,17 +4438,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5132
4438
  return _nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
5133
4439
  _contentPressCancellationGesture.view != nil &&
5134
4440
  [touchView isDescendantOfView:_pagerScrollView] &&
5135
- ![touchView isDescendantOfView:_sharedHeaderHostView] &&
5136
- ![touchView isDescendantOfView:_pinnedStickyHostView];
4441
+ ![touchView isDescendantOfView:_sharedHeaderHostView];
5137
4442
  }
5138
4443
  if (gestureRecognizer == _sharedHeaderPressCancellationGesture) {
5139
4444
  if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
5140
4445
  _sharedHeaderPressCancellationGestureHost == nil) return NO;
5141
4446
  UIView *touchView = touch.view;
5142
4447
  BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
5143
- [touchView isDescendantOfView:_sharedHeaderHostView] ||
5144
- touchView == _pinnedStickyHostView ||
5145
- [touchView isDescendantOfView:_pinnedStickyHostView];
4448
+ [touchView isDescendantOfView:_sharedHeaderHostView];
5146
4449
  if (!beginsInSharedHeader) return NO;
5147
4450
 
5148
4451
  RNCCollapsiblePagerLog([NSString stringWithFormat:
@@ -5153,9 +4456,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
5153
4456
  if (gestureRecognizer == _verticalPagerGesture) {
5154
4457
  UIView *touchView = touch.view;
5155
4458
  return touchView != _sharedHeaderHostView &&
5156
- ![touchView isDescendantOfView:_sharedHeaderHostView] &&
5157
- touchView != _pinnedStickyHostView &&
5158
- ![touchView isDescendantOfView:_pinnedStickyHostView];
4459
+ ![touchView isDescendantOfView:_sharedHeaderHostView];
5159
4460
  }
5160
4461
  // A new touch can cancel UIKit's animation before its first frame. Queue a
5161
4462
  // non-animated settle for the next safe transition boundary instead of