@onekeyfe/react-native-pager-view 3.0.157-alpha.257 → 3.0.157-alpha.258

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.
Files changed (33) hide show
  1. package/README.md +51 -4
  2. package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +237 -43
  3. package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +15 -11
  4. package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +573 -0
  5. package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +130 -0
  6. package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
  7. package/android/src/main/jni/CMakeLists.txt +19 -0
  8. package/android/src/main/jni/pagerview.h +11 -0
  9. package/android/src/main/res/values/native_scroll_coordinator_ids.xml +1 -0
  10. package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +32 -52
  11. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
  12. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
  13. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
  14. package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
  15. package/docs/SPEC.md +267 -34
  16. package/ios/RNCCollapsiblePagerViewComponentView.mm +187 -1
  17. package/ios/RNCNativeScrollerComponentView.h +17 -0
  18. package/ios/RNCNativeScrollerComponentView.mm +479 -0
  19. package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +346 -0
  20. package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
  21. package/ios/tests/RNCNativeScrollerTests.mm +252 -0
  22. package/lib/module/CollapsiblePagerView.js +1 -1
  23. package/lib/module/NativeScroller.js +171 -11
  24. package/lib/module/NativeScrollerNativeComponent.ts +101 -0
  25. package/lib/typescript/src/CollapsiblePagerView.d.ts +2 -3
  26. package/lib/typescript/src/NativeScroller.d.ts +15 -7
  27. package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
  28. package/package.json +6 -3
  29. package/react-native-pager-view.podspec +2 -1
  30. package/react-native.config.js +15 -0
  31. package/src/CollapsiblePagerView.tsx +4 -4
  32. package/src/NativeScroller.tsx +291 -14
  33. package/src/NativeScrollerNativeComponent.ts +101 -0
@@ -0,0 +1,479 @@
1
+ #import "RNCNativeScrollerComponentView.h"
2
+
3
+ #import <React/RCTConversions.h>
4
+ #import <react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h>
5
+ #import <react/renderer/components/pagerview/EventEmitters.h>
6
+ #import <react/renderer/components/pagerview/Props.h>
7
+ #import <react/renderer/components/pagerview/RCTComponentViewHelpers.h>
8
+
9
+ using namespace facebook::react;
10
+
11
+ @interface RNCNativeVerticalScrollView : UIScrollView
12
+ @end
13
+ @implementation RNCNativeVerticalScrollView
14
+ - (BOOL)touchesShouldCancelInContentView:(UIView *)view { return YES; }
15
+ @end
16
+
17
+ template <typename Event>
18
+ static Event RNCScrollerMetrics(UIScrollView *scroll, CGPoint velocity, CGPoint target)
19
+ {
20
+ UIEdgeInsets inset = scroll.contentInset;
21
+ return {
22
+ .contentInset = {.top = inset.top, .right = inset.right, .bottom = inset.bottom, .left = inset.left},
23
+ .contentOffset = {.x = scroll.contentOffset.x, .y = scroll.contentOffset.y},
24
+ .contentSize = {.width = scroll.contentSize.width, .height = scroll.contentSize.height},
25
+ .layoutMeasurement = {.width = scroll.bounds.size.width, .height = scroll.bounds.size.height},
26
+ .velocity = {.x = velocity.x, .y = velocity.y},
27
+ .targetContentOffset = {.x = target.x, .y = target.y},
28
+ .zoomScale = 1,
29
+ };
30
+ }
31
+
32
+ @interface RNCNativeScrollerComponentView () <UIScrollViewDelegate, RCTRNCNativeScrollerViewProtocol>
33
+ @end
34
+
35
+ @implementation RNCNativeScrollerComponentView {
36
+ RNCNativeScrollerShadowNode::ConcreteState::Shared _state;
37
+ RNCNativeVerticalScrollView *_scrollView;
38
+ UIView<RCTComponentViewProtocol> *_reactContent;
39
+ UIRefreshControl *_refreshControl;
40
+ UIEdgeInsets _callerInset;
41
+ BOOL _dragActive;
42
+ BOOL _momentumActive;
43
+ BOOL _refreshEventSentForDrag;
44
+ BOOL _needsContentClamp;
45
+ BOOL _recycled;
46
+ BOOL _stopping;
47
+ NSUInteger _generation;
48
+ CFTimeInterval _lastScrollEvent;
49
+ NSTimeInterval _scrollEventThrottle;
50
+ NSMapTable<id, NSNumber *> *_pagerStickyHeights;
51
+ NSNumber *_lastContentViewportHeight;
52
+ }
53
+
54
+ + (ComponentDescriptorProvider)componentDescriptorProvider
55
+ {
56
+ return concreteComponentDescriptorProvider<RNCNativeScrollerComponentDescriptor>();
57
+ }
58
+
59
+ - (instancetype)initWithFrame:(CGRect)frame
60
+ {
61
+ if ((self = [super initWithFrame:frame])) {
62
+ static const auto defaultProps = std::make_shared<const RNCNativeScrollerProps>();
63
+ _props = defaultProps;
64
+ _pagerStickyHeights = [NSMapTable weakToStrongObjectsMapTable];
65
+ _scrollView = [[RNCNativeVerticalScrollView alloc] initWithFrame:self.bounds];
66
+ _scrollView.delegate = self;
67
+ _scrollView.alwaysBounceVertical = YES;
68
+ _scrollView.alwaysBounceHorizontal = NO;
69
+ _scrollView.showsHorizontalScrollIndicator = NO;
70
+ _scrollView.directionalLockEnabled = YES;
71
+ _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
72
+ self.contentView = _scrollView;
73
+ }
74
+ return self;
75
+ }
76
+
77
+ - (UIScrollView *)scrollView { return _scrollView; }
78
+
79
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)child index:(NSInteger)index
80
+ {
81
+ // The wrapper supplies one Yoga-owned content view. Pager-owned shared headers
82
+ // are separate UIScrollView children and must never be removed or repositioned.
83
+ if (_reactContent != nil && _reactContent != child) {
84
+ [self stopScrolling];
85
+ [_reactContent removeFromSuperview];
86
+ }
87
+ _reactContent = child;
88
+ [_scrollView insertSubview:child atIndex:0];
89
+ }
90
+
91
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)child index:(NSInteger)index
92
+ {
93
+ if (child != _reactContent) return;
94
+ [self stopScrolling];
95
+ [child removeFromSuperview];
96
+ _reactContent = nil;
97
+ }
98
+
99
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
100
+ {
101
+ const auto &next = *std::static_pointer_cast<const RNCNativeScrollerProps>(props);
102
+ const auto &previous = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
103
+ BOOL wasRecycled = _recycled;
104
+ _recycled = NO;
105
+ _scrollView.delegate = self;
106
+ if (!next.scrollEnabled && _scrollView.scrollEnabled) [self stopScrolling];
107
+ _scrollView.scrollEnabled = next.scrollEnabled;
108
+ _scrollView.showsVerticalScrollIndicator = next.showsVerticalScrollIndicator;
109
+ _scrollView.showsHorizontalScrollIndicator = next.showsHorizontalScrollIndicator;
110
+ _scrollView.bounces = next.bounces;
111
+ if (wasRecycled || previous.alwaysBounceVertical != next.alwaysBounceVertical) {
112
+ _scrollView.alwaysBounceVertical = next.alwaysBounceVertical;
113
+ }
114
+ _scrollView.decelerationRate = isfinite(next.decelerationRate)
115
+ ? MIN(1, MAX(0, next.decelerationRate)) : UIScrollViewDecelerationRateNormal;
116
+ switch (next.keyboardDismissMode) {
117
+ case RNCNativeScrollerKeyboardDismissMode::OnDrag:
118
+ _scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; break;
119
+ case RNCNativeScrollerKeyboardDismissMode::Interactive:
120
+ _scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; break;
121
+ default: _scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeNone; break;
122
+ }
123
+ _scrollEventThrottle = isfinite(next.scrollEventThrottle)
124
+ ? MAX(0, next.scrollEventThrottle) / 1000.0 : 0;
125
+ if (_scrollEventThrottle <= 0.016) _scrollEventThrottle = 0;
126
+
127
+ UIEdgeInsets inset = UIEdgeInsetsMake(
128
+ isfinite(next.contentInsetTop) ? next.contentInsetTop : 0,
129
+ isfinite(next.contentInsetLeft) ? next.contentInsetLeft : 0,
130
+ isfinite(next.contentInsetBottom) ? next.contentInsetBottom : 0,
131
+ isfinite(next.contentInsetRight) ? next.contentInsetRight : 0);
132
+ if (!UIEdgeInsetsEqualToEdgeInsets(inset, _callerInset)) {
133
+ UIEdgeInsets oldInset = _callerInset;
134
+ UIEdgeInsets current = _scrollView.contentInset;
135
+ _callerInset = inset;
136
+ _scrollView.contentInset = UIEdgeInsetsMake(
137
+ current.top + inset.top - oldInset.top, current.left + inset.left - oldInset.left,
138
+ current.bottom + inset.bottom - oldInset.bottom, current.right + inset.right - oldInset.right);
139
+ for (UIView *owner = self.superview; owner != nil; owner = owner.superview) {
140
+ if ([owner conformsToProtocol:@protocol(RNCNativeScrollerInsetCoordinator)]) {
141
+ [(id<RNCNativeScrollerInsetCoordinator>)owner nativeScroller:self
142
+ callerInsetChangedFrom:oldInset to:inset];
143
+ }
144
+ }
145
+ }
146
+
147
+ BOOL createdRefreshControl = next.refreshEnabled && _refreshControl == nil;
148
+ if (createdRefreshControl) {
149
+ _refreshControl = [UIRefreshControl new];
150
+ [_refreshControl addTarget:self action:@selector(refreshTriggered:)
151
+ forControlEvents:UIControlEventValueChanged];
152
+ _scrollView.refreshControl = _refreshControl;
153
+ } else if (!next.refreshEnabled && _refreshControl != nil) {
154
+ [_refreshControl endRefreshing];
155
+ _scrollView.refreshControl = nil;
156
+ _refreshControl = nil;
157
+ }
158
+ if (_refreshControl != nil) {
159
+ _refreshControl.tintColor = next.refreshTintColor ? RCTUIColorFromSharedColor(next.refreshTintColor) : nil;
160
+ NSString *title = RCTNSStringFromString(next.refreshTitle);
161
+ UIColor *titleColor = next.refreshTitleColor ? RCTUIColorFromSharedColor(next.refreshTitleColor) : nil;
162
+ _refreshControl.attributedTitle = title.length == 0 ? nil : [[NSAttributedString alloc]
163
+ initWithString:title attributes:titleColor == nil ? @{} : @{NSForegroundColorAttributeName: titleColor}];
164
+ if (previous.refreshProgressViewOffset != next.refreshProgressViewOffset || createdRefreshControl) {
165
+ CGRect bounds = _refreshControl.bounds;
166
+ bounds.origin.y = isfinite(next.refreshProgressViewOffset) ? -next.refreshProgressViewOffset : 0;
167
+ _refreshControl.bounds = bounds;
168
+ }
169
+ if (next.refreshing && !_refreshControl.refreshing) {
170
+ [_refreshControl beginRefreshing];
171
+ } else if (!next.refreshing && (previous.refreshing || createdRefreshControl)) {
172
+ [_refreshControl endRefreshing];
173
+ }
174
+ }
175
+
176
+ CGSize size = CGSizeMake(isfinite(next.contentWidth) ? MAX(0, next.contentWidth) : 0,
177
+ isfinite(next.contentHeight) ? MAX(0, next.contentHeight) : 0);
178
+ if (!CGSizeEqualToSize(size, _scrollView.contentSize)) {
179
+ _needsContentClamp |= size.height < _scrollView.contentSize.height;
180
+ CGPoint offset = _scrollView.contentOffset;
181
+ BOOL wasPulling = offset.y < -_scrollView.adjustedContentInset.top;
182
+ _scrollView.contentSize = size;
183
+ // UIKit can synchronously discard a pull when content becomes empty.
184
+ // Upper-range shrink correction remains owned by the deferred clamp.
185
+ if (wasPulling && _scrollView.contentOffset.y != offset.y) {
186
+ [_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, offset.y) animated:NO];
187
+ }
188
+ [self scheduleContentClamp];
189
+ }
190
+ BOOL refreshEnded = previous.refreshing && !next.refreshing;
191
+ [super updateProps:props oldProps:oldProps];
192
+ if (refreshEnded) [self scheduleContentClamp];
193
+ [self updateFabricContentOrigin];
194
+ [self emitContentViewportChangeIfNeeded];
195
+ }
196
+
197
+ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
198
+ oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
199
+ {
200
+ CGSize previous = _scrollView.bounds.size;
201
+ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
202
+ [self updateFabricContentOrigin];
203
+ [self emitContentViewportChangeIfNeeded];
204
+ if (!CGSizeEqualToSize(previous, _scrollView.bounds.size)) {
205
+ _needsContentClamp = YES;
206
+ [self scheduleContentClamp];
207
+ }
208
+ }
209
+
210
+ - (CGFloat)contentViewportHeight
211
+ {
212
+ CGFloat stickyHeight = 0;
213
+ for (NSNumber *height in _pagerStickyHeights.objectEnumerator) stickyHeight += height.doubleValue;
214
+ return MAX(0, _scrollView.bounds.size.height - _callerInset.top - _callerInset.bottom - stickyHeight);
215
+ }
216
+
217
+ - (void)setPagerStickyHeight:(CGFloat)height forOwner:(id)owner
218
+ {
219
+ if (owner == nil) return;
220
+ CGFloat next = isfinite(height) ? MAX(0, height) : 0;
221
+ if (next == 0) [_pagerStickyHeights removeObjectForKey:owner];
222
+ else [_pagerStickyHeights setObject:@(next) forKey:owner];
223
+ [self emitContentViewportChangeIfNeeded];
224
+ }
225
+
226
+ - (void)emitContentViewportChangeIfNeeded
227
+ {
228
+ if (_recycled || !_eventEmitter) return;
229
+ CGFloat height = [self contentViewportHeight];
230
+ if (_lastContentViewportHeight != nil && _lastContentViewportHeight.doubleValue == height) return;
231
+ _lastContentViewportHeight = @(height);
232
+ std::static_pointer_cast<const RNCNativeScrollerEventEmitter>(_eventEmitter)
233
+ ->onContentViewportChange({.height = height});
234
+ }
235
+
236
+ - (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
237
+ {
238
+ if (_eventEmitter != eventEmitter) _lastContentViewportHeight = nil;
239
+ [super updateEventEmitter:eventEmitter];
240
+ [self emitContentViewportChangeIfNeeded];
241
+ }
242
+
243
+ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
244
+ {
245
+ _state = std::static_pointer_cast<const RNCNativeScrollerShadowNode::ConcreteState>(state);
246
+ [self updateFabricContentOrigin];
247
+ }
248
+
249
+ - (void)updateFabricContentOrigin
250
+ {
251
+ if (!_state || _recycled) return;
252
+ facebook::react::Point origin = {-static_cast<Float>(_scrollView.contentOffset.x),
253
+ -static_cast<Float>(_scrollView.contentOffset.y)};
254
+ _state->updateState([origin](const RNCNativeScrollerShadowNode::ConcreteState::Data &oldData)
255
+ -> RNCNativeScrollerShadowNode::ConcreteState::SharedData {
256
+ if (oldData.contentOrigin == origin) return nullptr;
257
+ auto next = oldData;
258
+ next.contentOrigin = origin;
259
+ return std::make_shared<const RNCNativeScrollerShadowNode::ConcreteState::Data>(next);
260
+ });
261
+ }
262
+
263
+ - (void)scheduleContentClamp
264
+ {
265
+ if (!_needsContentClamp) return;
266
+ NSUInteger generation = _generation;
267
+ __weak __typeof__(self) weakSelf = self;
268
+ dispatch_async(dispatch_get_main_queue(), ^{
269
+ __strong __typeof__(self) self = weakSelf;
270
+ if (self == nil || self->_generation != generation || self->_recycled) return;
271
+ [self clampContentOffsetIfNeeded];
272
+ });
273
+ }
274
+
275
+ - (void)clampContentOffsetIfNeeded
276
+ {
277
+ if (!_needsContentClamp || _scrollView.dragging || _scrollView.decelerating ||
278
+ _refreshControl.refreshing || _scrollView.bounds.size.height <= 0) return;
279
+ _needsContentClamp = NO;
280
+ CGFloat maximum = MAX(-_scrollView.adjustedContentInset.top,
281
+ _scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.adjustedContentInset.bottom);
282
+ if (_scrollView.contentOffset.y > maximum) {
283
+ [_scrollView setContentOffset:CGPointMake(_scrollView.contentOffset.x, maximum) animated:NO];
284
+ }
285
+ }
286
+
287
+ - (void)refreshTriggered:(UIRefreshControl *)control
288
+ {
289
+ if (_recycled || _refreshEventSentForDrag || control != _refreshControl) return;
290
+ const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
291
+ if (!props.refreshEnabled || props.refreshing) return;
292
+ _refreshEventSentForDrag = YES;
293
+ if (_eventEmitter) {
294
+ std::static_pointer_cast<const RNCNativeScrollerEventEmitter>(_eventEmitter)->onRefresh({});
295
+ }
296
+ }
297
+
298
+ // Every delegate path and imperative interruption uses the same terminal owner.
299
+ - (void)emitScrollEvent:(NSInteger)kind velocity:(CGPoint)velocity target:(CGPoint)target
300
+ {
301
+ if (_recycled || !_eventEmitter) return;
302
+ const auto emitter = std::static_pointer_cast<const RNCNativeScrollerEventEmitter>(_eventEmitter);
303
+ switch (kind) {
304
+ case 0: emitter->onScroll(RNCScrollerMetrics<RNCNativeScrollerEventEmitter::OnScroll>(_scrollView, velocity, target)); break;
305
+ case 1: emitter->onScrollBeginDrag(RNCScrollerMetrics<RNCNativeScrollerEventEmitter::OnScrollBeginDrag>(_scrollView, velocity, target)); break;
306
+ case 2: emitter->onScrollEndDrag(RNCScrollerMetrics<RNCNativeScrollerEventEmitter::OnScrollEndDrag>(_scrollView, velocity, target)); break;
307
+ case 3: emitter->onMomentumScrollBegin(RNCScrollerMetrics<RNCNativeScrollerEventEmitter::OnMomentumScrollBegin>(_scrollView, velocity, target)); break;
308
+ case 4: emitter->onMomentumScrollEnd(RNCScrollerMetrics<RNCNativeScrollerEventEmitter::OnMomentumScrollEnd>(_scrollView, velocity, target)); break;
309
+ }
310
+ }
311
+
312
+ - (void)finishMomentum
313
+ {
314
+ if (!_momentumActive) return;
315
+ _momentumActive = NO;
316
+ [self emitScrollEvent:4 velocity:CGPointZero target:_scrollView.contentOffset];
317
+ }
318
+
319
+ - (void)stopScrolling
320
+ {
321
+ if (_stopping) return;
322
+ _stopping = YES;
323
+ [_scrollView setContentOffset:_scrollView.contentOffset animated:NO];
324
+ if (_dragActive) {
325
+ _dragActive = NO;
326
+ [self emitScrollEvent:2 velocity:CGPointZero target:_scrollView.contentOffset];
327
+ BOOL enabled = _scrollView.panGestureRecognizer.enabled;
328
+ _scrollView.panGestureRecognizer.enabled = NO;
329
+ _scrollView.panGestureRecognizer.enabled = enabled;
330
+ }
331
+ [self finishMomentum];
332
+ _stopping = NO;
333
+ }
334
+
335
+ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
336
+ {
337
+ if (_recycled) return;
338
+ [self finishMomentum];
339
+ _dragActive = YES;
340
+ _refreshEventSentForDrag = NO;
341
+ _lastScrollEvent = 0;
342
+ [self emitScrollEvent:1 velocity:CGPointZero target:scrollView.contentOffset];
343
+ }
344
+
345
+ - (void)scrollViewDidScroll:(UIScrollView *)scrollView
346
+ {
347
+ if (_recycled) return;
348
+ [self updateFabricContentOrigin];
349
+ CFTimeInterval now = CACurrentMediaTime();
350
+ if (_lastScrollEvent != 0 && now - _lastScrollEvent < _scrollEventThrottle) return;
351
+ _lastScrollEvent = now;
352
+ [self emitScrollEvent:0 velocity:CGPointZero target:scrollView.contentOffset];
353
+ }
354
+
355
+ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
356
+ withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)target
357
+ {
358
+ if (_recycled || !_dragActive) return;
359
+ _dragActive = NO;
360
+ [self emitScrollEvent:2 velocity:velocity target:*target];
361
+ }
362
+
363
+ - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
364
+ {
365
+ if (_recycled) return;
366
+ if (_dragActive) {
367
+ _dragActive = NO;
368
+ [self emitScrollEvent:2 velocity:CGPointZero target:scrollView.contentOffset];
369
+ }
370
+ if (!decelerate) [self scheduleContentClamp];
371
+ }
372
+
373
+ - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
374
+ {
375
+ if (_recycled || _stopping || _momentumActive) return;
376
+ _momentumActive = YES;
377
+ [self emitScrollEvent:3 velocity:CGPointZero target:scrollView.contentOffset];
378
+ }
379
+
380
+ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
381
+ {
382
+ if (_recycled) return;
383
+ [self finishMomentum];
384
+ _lastScrollEvent = 0;
385
+ [self scrollViewDidScroll:scrollView];
386
+ [self scheduleContentClamp];
387
+ }
388
+
389
+ - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
390
+ {
391
+ [self scrollViewDidEndDecelerating:scrollView];
392
+ }
393
+
394
+ - (void)scrollTo:(double)x y:(double)y animated:(BOOL)animated
395
+ {
396
+ if (_recycled || !isfinite(x) || !isfinite(y)) return;
397
+ [self stopScrolling];
398
+ UIEdgeInsets inset = _scrollView.adjustedContentInset;
399
+ CGPoint target = CGPointMake(MAX(-inset.left, MIN(x,
400
+ MAX(-inset.left, _scrollView.contentSize.width - _scrollView.bounds.size.width + inset.right))),
401
+ MAX(-inset.top, MIN(y,
402
+ MAX(-inset.top, _scrollView.contentSize.height - _scrollView.bounds.size.height + inset.bottom))));
403
+ if (CGPointEqualToPoint(target, _scrollView.contentOffset)) return;
404
+ if (animated) {
405
+ _momentumActive = YES;
406
+ [self emitScrollEvent:3 velocity:CGPointZero target:target];
407
+ }
408
+ [_scrollView setContentOffset:target animated:animated];
409
+ }
410
+
411
+ - (void)scrollToEnd:(BOOL)animated
412
+ {
413
+ [self scrollTo:_scrollView.contentOffset.x
414
+ y:_scrollView.contentSize.height - _scrollView.bounds.size.height + _scrollView.adjustedContentInset.bottom
415
+ animated:animated];
416
+ }
417
+
418
+ - (void)setRefreshState:(BOOL)refreshing
419
+ {
420
+ [self setRefreshing:refreshing];
421
+ }
422
+
423
+ - (void)setRefreshing:(BOOL)refreshing
424
+ {
425
+ if (_recycled || _refreshControl == nil) return;
426
+ if (refreshing) {
427
+ if (!_refreshControl.refreshing) [_refreshControl beginRefreshing];
428
+ } else {
429
+ [_refreshControl endRefreshing];
430
+ [self scheduleContentClamp];
431
+ }
432
+ }
433
+
434
+ - (void)flashScrollIndicators { [_scrollView flashScrollIndicators]; }
435
+
436
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
437
+ {
438
+ RCTRNCNativeScrollerHandleCommand(self, commandName, args);
439
+ }
440
+
441
+ - (void)didMoveToWindow
442
+ {
443
+ [super didMoveToWindow];
444
+ if (self.window == nil) {
445
+ [self stopScrolling];
446
+ [_pagerStickyHeights removeAllObjects];
447
+ [self emitContentViewportChangeIfNeeded];
448
+ } else {
449
+ const auto &props = *std::static_pointer_cast<const RNCNativeScrollerProps>(_props);
450
+ if (props.refreshing) [_refreshControl beginRefreshing];
451
+ [self scheduleContentClamp];
452
+ }
453
+ }
454
+
455
+ - (void)prepareForRecycle
456
+ {
457
+ [self stopScrolling];
458
+ _recycled = YES;
459
+ _scrollView.delegate = nil;
460
+ ++_generation;
461
+ _state.reset();
462
+ [_pagerStickyHeights removeAllObjects];
463
+ _lastContentViewportHeight = nil;
464
+ [_refreshControl endRefreshing];
465
+ _scrollView.refreshControl = nil;
466
+ _refreshControl = nil;
467
+ [_reactContent removeFromSuperview];
468
+ _reactContent = nil;
469
+ _scrollView.contentInset = UIEdgeInsetsZero;
470
+ _scrollView.contentSize = CGSizeZero;
471
+ _scrollView.contentOffset = CGPointZero;
472
+ _callerInset = UIEdgeInsetsZero;
473
+ _needsContentClamp = NO;
474
+ _refreshEventSentForDrag = NO;
475
+ _lastScrollEvent = 0;
476
+ [super prepareForRecycle];
477
+ }
478
+
479
+ @end