@lodev09/react-native-true-sheet 3.9.4 → 3.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +10 -6
- package/ios/TrueSheetView.mm +44 -21
- package/ios/TrueSheetViewController.h +1 -0
- package/ios/TrueSheetViewController.mm +46 -20
- package/ios/core/TrueSheetDetentCalculator.h +6 -10
- package/ios/core/TrueSheetDetentCalculator.mm +60 -49
- package/package.json +1 -1
|
@@ -65,12 +65,15 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
65
65
|
get() = PointerEvents.BOX_NONE
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Clears stale `nestedScrollingChildRef` from BottomSheetBehavior.
|
|
68
|
+
* Clears stale `nestedScrollingChildRef` from BottomSheetBehavior and forces re-discovery.
|
|
69
69
|
*
|
|
70
70
|
* `BottomSheetBehavior.onLayoutChild` calls `findScrollingChild()` which traverses the
|
|
71
|
-
* entire sheet subtree.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* entire sheet subtree. The cached `nestedScrollingChildRef` can become stale when:
|
|
72
|
+
* 1. A child sheet with a ScrollView is dismissed and its ScrollView returns to this hierarchy
|
|
73
|
+
* 2. A ScrollView is conditionally removed and re-added (e.g. React conditional rendering)
|
|
74
|
+
*
|
|
75
|
+
* When stale (GC'd target or view no longer in sheet), we clear the ref and request layout
|
|
76
|
+
* so `onLayoutChild` re-runs `findScrollingChild()` to discover the current ScrollView.
|
|
74
77
|
*/
|
|
75
78
|
private fun clearStaleNestedScrollingChildRef() {
|
|
76
79
|
val sheet = delegate?.findSheetView() ?: return
|
|
@@ -80,9 +83,10 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
80
83
|
field.isAccessible = true
|
|
81
84
|
@Suppress("UNCHECKED_CAST")
|
|
82
85
|
val ref = field.get(behavior) as? java.lang.ref.WeakReference<android.view.View> ?: return
|
|
83
|
-
val view = ref.get()
|
|
84
|
-
if (!view.isDescendantOf(sheet)) {
|
|
86
|
+
val view = ref.get()
|
|
87
|
+
if (view == null || !view.isDescendantOf(sheet)) {
|
|
85
88
|
ref.clear()
|
|
89
|
+
sheet.requestLayout()
|
|
86
90
|
}
|
|
87
91
|
} catch (_: Exception) {}
|
|
88
92
|
}
|
package/ios/TrueSheetView.mm
CHANGED
|
@@ -62,6 +62,8 @@ using namespace facebook::react;
|
|
|
62
62
|
BOOL _pendingNavigationRepresent;
|
|
63
63
|
BOOL _pendingMountEvent;
|
|
64
64
|
BOOL _pendingSizeChange;
|
|
65
|
+
BOOL _pendingPropsUpdate;
|
|
66
|
+
NSArray *_pendingDetents;
|
|
65
67
|
RNScreensEventObserver *_screensEventObserver;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -172,7 +174,12 @@ using namespace facebook::react;
|
|
|
172
174
|
_pendingLayoutUpdate = YES;
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
|
-
|
|
177
|
+
|
|
178
|
+
if (_controller.isBeingPresented) {
|
|
179
|
+
_pendingDetents = detents;
|
|
180
|
+
} else {
|
|
181
|
+
_controller.detents = detents;
|
|
182
|
+
}
|
|
176
183
|
|
|
177
184
|
// Background color
|
|
178
185
|
_controller.backgroundColor = RCTUIColorFromSharedColor(newProps.backgroundColor);
|
|
@@ -231,7 +238,7 @@ using namespace facebook::react;
|
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
_controller.pageSizing = newProps.pageSizing;
|
|
234
|
-
_controller.
|
|
241
|
+
_controller.dismissible = newProps.dismissible;
|
|
235
242
|
_controller.draggable = newProps.draggable;
|
|
236
243
|
_controller.dimmed = newProps.dimmed;
|
|
237
244
|
|
|
@@ -317,24 +324,9 @@ using namespace facebook::react;
|
|
|
317
324
|
}
|
|
318
325
|
|
|
319
326
|
if (_controller.isPresented) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
UIView *presenterView = _controller.presentingViewController.view;
|
|
324
|
-
[_controller setupAnchorViewInView:presenterView];
|
|
325
|
-
|
|
326
|
-
[_controller setupSheetSizing];
|
|
327
|
-
|
|
328
|
-
[_controller.sheetPresentationController animateChanges:^{
|
|
329
|
-
[self->_controller setupSheetProps];
|
|
330
|
-
if (pendingLayoutUpdate) {
|
|
331
|
-
[self->_controller setupSheetDetentsForDetentsChange];
|
|
332
|
-
} else {
|
|
333
|
-
[self->_controller setupSheetDetents];
|
|
334
|
-
}
|
|
335
|
-
[self->_controller applyActiveDetent];
|
|
336
|
-
}];
|
|
337
|
-
[_controller setupDraggable];
|
|
327
|
+
[self applySheetPropsUpdate];
|
|
328
|
+
} else if (_controller.isBeingPresented) {
|
|
329
|
+
_pendingPropsUpdate = YES;
|
|
338
330
|
} else if (_initialDetentIndex >= 0) {
|
|
339
331
|
_pendingLayoutUpdate = NO;
|
|
340
332
|
}
|
|
@@ -556,7 +548,7 @@ using namespace facebook::react;
|
|
|
556
548
|
if (_isSheetUpdatePending)
|
|
557
549
|
return;
|
|
558
550
|
|
|
559
|
-
if (
|
|
551
|
+
if (_controller.isBeingPresented) {
|
|
560
552
|
_pendingSizeChange = YES;
|
|
561
553
|
return;
|
|
562
554
|
}
|
|
@@ -593,6 +585,11 @@ using namespace facebook::react;
|
|
|
593
585
|
[_containerView setupKeyboardObserverWithViewController:_controller];
|
|
594
586
|
[TrueSheetLifecycleEvents emitDidPresent:_eventEmitter index:index position:position detent:detent];
|
|
595
587
|
|
|
588
|
+
if (_pendingPropsUpdate) {
|
|
589
|
+
_pendingPropsUpdate = NO;
|
|
590
|
+
[self applySheetPropsUpdate];
|
|
591
|
+
}
|
|
592
|
+
|
|
596
593
|
if (_pendingSizeChange) {
|
|
597
594
|
_pendingSizeChange = NO;
|
|
598
595
|
[self setupSheetDetentsForSizeChange];
|
|
@@ -696,6 +693,32 @@ using namespace facebook::react;
|
|
|
696
693
|
|
|
697
694
|
#pragma mark - Private Helpers
|
|
698
695
|
|
|
696
|
+
- (void)applySheetPropsUpdate {
|
|
697
|
+
BOOL pendingLayoutUpdate = _pendingLayoutUpdate;
|
|
698
|
+
_pendingLayoutUpdate = NO;
|
|
699
|
+
|
|
700
|
+
if (_pendingDetents) {
|
|
701
|
+
_controller.detents = _pendingDetents;
|
|
702
|
+
_pendingDetents = nil;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
UIView *presenterView = _controller.presentingViewController.view;
|
|
706
|
+
[_controller setupAnchorViewInView:presenterView];
|
|
707
|
+
|
|
708
|
+
[_controller setupSheetSizing];
|
|
709
|
+
|
|
710
|
+
[_controller.sheetPresentationController animateChanges:^{
|
|
711
|
+
[self->_controller setupSheetProps];
|
|
712
|
+
if (pendingLayoutUpdate) {
|
|
713
|
+
[self->_controller setupSheetDetentsForDetentsChange];
|
|
714
|
+
} else {
|
|
715
|
+
[self->_controller setupSheetDetents];
|
|
716
|
+
}
|
|
717
|
+
[self->_controller applyActiveDetent];
|
|
718
|
+
}];
|
|
719
|
+
[_controller setupDraggable];
|
|
720
|
+
}
|
|
721
|
+
|
|
699
722
|
- (UIViewController *)findPresentingViewController {
|
|
700
723
|
if (!self.window)
|
|
701
724
|
return nil;
|
|
@@ -68,6 +68,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
68
68
|
@property (nonatomic, assign) BOOL pageSizing;
|
|
69
69
|
@property (nonatomic, assign) NSInteger anchor;
|
|
70
70
|
@property (nonatomic, assign) NSInteger insetAdjustment;
|
|
71
|
+
@property (nonatomic, assign) BOOL dismissible;
|
|
71
72
|
@property (nonatomic, assign) BOOL isPresented;
|
|
72
73
|
@property (nonatomic, assign) NSInteger activeDetentIndex;
|
|
73
74
|
@property (nonatomic, readonly) BOOL isTopmostPresentedController;
|
|
@@ -37,6 +37,7 @@ using namespace facebook::react;
|
|
|
37
37
|
UIView *_transitionFakeView;
|
|
38
38
|
BOOL _isDragging;
|
|
39
39
|
BOOL _isTransitioning;
|
|
40
|
+
BOOL _isTransitionSnapping;
|
|
40
41
|
BOOL _isTrackingPositionFromLayout;
|
|
41
42
|
BOOL _isWillDismissEmitted;
|
|
42
43
|
|
|
@@ -58,6 +59,7 @@ using namespace facebook::react;
|
|
|
58
59
|
_headerHeight = @(0);
|
|
59
60
|
_grabber = YES;
|
|
60
61
|
_draggable = YES;
|
|
62
|
+
_dismissible = YES;
|
|
61
63
|
_dimmed = YES;
|
|
62
64
|
_dimmedDetentIndex = @(0);
|
|
63
65
|
_pageSizing = YES;
|
|
@@ -188,10 +190,6 @@ using namespace facebook::react;
|
|
|
188
190
|
[super viewWillAppear:animated];
|
|
189
191
|
|
|
190
192
|
if (!_isPresented) {
|
|
191
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
192
|
-
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
193
|
-
});
|
|
194
|
-
|
|
195
193
|
UIViewController *presenter = self.presentingViewController;
|
|
196
194
|
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
197
195
|
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
@@ -219,6 +217,8 @@ using namespace facebook::react;
|
|
|
219
217
|
|
|
220
218
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
221
219
|
NSInteger index = [self currentDetentIndex];
|
|
220
|
+
[self learnOffsetForDetentIndex:index];
|
|
221
|
+
|
|
222
222
|
CGFloat detent = [self detentValueForIndex:index];
|
|
223
223
|
[self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
|
|
224
224
|
[self.delegate viewControllerDidFocus];
|
|
@@ -290,7 +290,7 @@ using namespace facebook::react;
|
|
|
290
290
|
_pendingContentSizeChange = NO;
|
|
291
291
|
_pendingDetentsChange = NO;
|
|
292
292
|
realtime = NO;
|
|
293
|
-
[self
|
|
293
|
+
[self learnOffsetForDetentIndex:self.currentDetentIndex];
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:realtime debug:@"layout"];
|
|
@@ -312,7 +312,7 @@ using namespace facebook::react;
|
|
|
312
312
|
_pendingDetentIndex = -1;
|
|
313
313
|
|
|
314
314
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
315
|
-
[self
|
|
315
|
+
[self learnOffsetForDetentIndex:pendingIndex];
|
|
316
316
|
CGFloat detent = [self detentValueForIndex:pendingIndex];
|
|
317
317
|
[self.delegate viewControllerDidChangeDetent:pendingIndex position:self.currentPosition detent:detent];
|
|
318
318
|
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:@"pending detent change"];
|
|
@@ -389,7 +389,7 @@ using namespace facebook::react;
|
|
|
389
389
|
case UIGestureRecognizerStateCancelled: {
|
|
390
390
|
if (!_isTransitioning) {
|
|
391
391
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
392
|
-
[self
|
|
392
|
+
[self learnOffsetForDetentIndex:self.currentDetentIndex];
|
|
393
393
|
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO debug:@"drag end"];
|
|
394
394
|
});
|
|
395
395
|
}
|
|
@@ -412,7 +412,6 @@ using namespace facebook::react;
|
|
|
412
412
|
CGRect presentedFrame = CGRectMake(0, self.currentPosition, 0, 0);
|
|
413
413
|
|
|
414
414
|
_transitionFakeView.frame = self.isBeingDismissed ? presentedFrame : dismissedFrame;
|
|
415
|
-
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
416
415
|
|
|
417
416
|
__weak __typeof(self) weakSelf = self;
|
|
418
417
|
|
|
@@ -439,6 +438,17 @@ using namespace facebook::react;
|
|
|
439
438
|
strongSelf->_transitioningTimer = nil;
|
|
440
439
|
[strongSelf->_transitionFakeView removeFromSuperview];
|
|
441
440
|
strongSelf->_isTransitioning = NO;
|
|
441
|
+
strongSelf->_isTransitionSnapping = NO;
|
|
442
|
+
|
|
443
|
+
// Emit settled position after detent snap.
|
|
444
|
+
// Uses dispatch_after because presentedView frame isn't final until UIKit
|
|
445
|
+
// completes its layout pass after the transition animation.
|
|
446
|
+
if (strongSelf->_isPresented) {
|
|
447
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
448
|
+
CGFloat position = strongSelf.currentPosition;
|
|
449
|
+
[strongSelf emitChangePositionDelegateWithPosition:position realtime:NO debug:@"transition end"];
|
|
450
|
+
});
|
|
451
|
+
}
|
|
442
452
|
}];
|
|
443
453
|
}
|
|
444
454
|
|
|
@@ -455,7 +465,12 @@ using namespace facebook::react;
|
|
|
455
465
|
|
|
456
466
|
} else {
|
|
457
467
|
CGFloat position = fmax(self.currentPosition, layerPosition);
|
|
458
|
-
|
|
468
|
+
// Detect drag → snap transition jump; stay non-realtime for the rest of the animation
|
|
469
|
+
if (!_isTransitionSnapping && _isPresented && _lastPosition > 0 && fabs(_lastPosition - position) > 20) {
|
|
470
|
+
_isTransitionSnapping = YES;
|
|
471
|
+
}
|
|
472
|
+
BOOL realtime = !_isTransitionSnapping;
|
|
473
|
+
[self emitChangePositionDelegateWithPosition:position realtime:realtime debug:@"transition in"];
|
|
459
474
|
}
|
|
460
475
|
}
|
|
461
476
|
}
|
|
@@ -475,16 +490,13 @@ using namespace facebook::react;
|
|
|
475
490
|
|
|
476
491
|
CGFloat index = [self interpolatedIndexForPosition:position];
|
|
477
492
|
CGFloat detent = [self interpolatedDetentForPosition:position];
|
|
493
|
+
|
|
478
494
|
[self.delegate viewControllerDidChangePosition:index position:position detent:detent realtime:realtime];
|
|
479
495
|
}
|
|
480
496
|
}
|
|
481
497
|
|
|
482
|
-
- (void)
|
|
483
|
-
[_detentCalculator
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
- (CGFloat)estimatedPositionForIndex:(NSInteger)index {
|
|
487
|
-
return [_detentCalculator estimatedPositionForIndex:index];
|
|
498
|
+
- (void)learnOffsetForDetentIndex:(NSInteger)index {
|
|
499
|
+
[_detentCalculator learnOffsetForDetentIndex:index];
|
|
488
500
|
}
|
|
489
501
|
|
|
490
502
|
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
@@ -525,7 +537,7 @@ using namespace facebook::react;
|
|
|
525
537
|
}
|
|
526
538
|
|
|
527
539
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
528
|
-
[_detentCalculator
|
|
540
|
+
[_detentCalculator clearResolvedHeights];
|
|
529
541
|
|
|
530
542
|
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
531
543
|
|
|
@@ -571,7 +583,7 @@ using namespace facebook::react;
|
|
|
571
583
|
|
|
572
584
|
if (value == -1) {
|
|
573
585
|
if (@available(iOS 16.0, *)) {
|
|
574
|
-
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight];
|
|
586
|
+
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight atIndex:index];
|
|
575
587
|
} else {
|
|
576
588
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
577
589
|
}
|
|
@@ -585,7 +597,7 @@ using namespace facebook::react;
|
|
|
585
597
|
if (@available(iOS 16.0, *)) {
|
|
586
598
|
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
587
599
|
CGFloat sheetHeight = value * self.screenHeight;
|
|
588
|
-
return [self customDetentWithIdentifier:detentId height:sheetHeight];
|
|
600
|
+
return [self customDetentWithIdentifier:detentId height:sheetHeight atIndex:index];
|
|
589
601
|
} else if (value >= 0.5) {
|
|
590
602
|
return [UISheetPresentationControllerDetent largeDetent];
|
|
591
603
|
} else {
|
|
@@ -594,17 +606,27 @@ using namespace facebook::react;
|
|
|
594
606
|
}
|
|
595
607
|
|
|
596
608
|
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
597
|
-
height:(CGFloat)height
|
|
609
|
+
height:(CGFloat)height
|
|
610
|
+
atIndex:(NSInteger)index API_AVAILABLE(ios(16.0)) {
|
|
598
611
|
CGFloat bottomAdjustment = [self detentBottomAdjustmentForHeight:height];
|
|
599
612
|
return [UISheetPresentationControllerDetent
|
|
600
613
|
customDetentWithIdentifier:identifier
|
|
601
614
|
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
602
615
|
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
616
|
+
self->_detentCalculator.maxDetentHeight = maxDetentValue;
|
|
617
|
+
|
|
603
618
|
CGFloat maxValue = self.maxContentHeight
|
|
604
619
|
? fmin(maxDetentValue, [self.maxContentHeight floatValue])
|
|
605
620
|
: maxDetentValue;
|
|
606
621
|
CGFloat adjustedHeight = height - bottomAdjustment;
|
|
607
|
-
|
|
622
|
+
CGFloat resolved = fmin(adjustedHeight, maxValue);
|
|
623
|
+
|
|
624
|
+
NSMutableArray *heights = self->_detentCalculator.resolvedDetentHeights;
|
|
625
|
+
if (heights && index >= 0 && index < (NSInteger)heights.count) {
|
|
626
|
+
heights[index] = @(resolved);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return resolved;
|
|
608
630
|
}];
|
|
609
631
|
}
|
|
610
632
|
|
|
@@ -802,6 +824,10 @@ using namespace facebook::react;
|
|
|
802
824
|
|
|
803
825
|
#pragma mark - UISheetPresentationControllerDelegate
|
|
804
826
|
|
|
827
|
+
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController {
|
|
828
|
+
return self.dismissible;
|
|
829
|
+
}
|
|
830
|
+
|
|
805
831
|
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
|
|
806
832
|
(UISheetPresentationController *)sheetPresentationController {
|
|
807
833
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -30,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
30
30
|
@interface TrueSheetDetentCalculator : NSObject
|
|
31
31
|
|
|
32
32
|
@property (nonatomic, weak, nullable) id<TrueSheetDetentCalculatorDelegate> delegate;
|
|
33
|
+
@property (nonatomic, assign) CGFloat maxDetentHeight;
|
|
34
|
+
@property (nonatomic, strong, nullable) NSMutableArray<NSNumber *> *resolvedDetentHeights;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
Returns the detent value (0-1 fraction) for a given index.
|
|
@@ -38,16 +40,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
38
40
|
- (CGFloat)detentValueForIndex:(NSInteger)index;
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
|
-
|
|
42
|
-
Uses stored resolved positions if available, otherwise calculates from detent value.
|
|
43
|
-
*/
|
|
44
|
-
- (CGFloat)estimatedPositionForIndex:(NSInteger)index;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
Stores the current resolved position for a detent index.
|
|
43
|
+
Learns the offset between resolver height and actual presented height for a detent.
|
|
48
44
|
Called when the sheet settles at a detent.
|
|
49
45
|
*/
|
|
50
|
-
- (void)
|
|
46
|
+
- (void)learnOffsetForDetentIndex:(NSInteger)index;
|
|
51
47
|
|
|
52
48
|
/**
|
|
53
49
|
Finds the segment between detents for a given position.
|
|
@@ -71,10 +67,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
71
67
|
- (CGFloat)interpolatedDetentForPosition:(CGFloat)position;
|
|
72
68
|
|
|
73
69
|
/**
|
|
74
|
-
Clears all
|
|
70
|
+
Clears all resolved heights and learned offsets.
|
|
75
71
|
Called when detents configuration changes.
|
|
76
72
|
*/
|
|
77
|
-
- (void)
|
|
73
|
+
- (void)clearResolvedHeights;
|
|
78
74
|
|
|
79
75
|
/**
|
|
80
76
|
Sets the count of detents (initializes storage for resolved positions).
|
|
@@ -9,14 +9,7 @@
|
|
|
9
9
|
#import "TrueSheetDetentCalculator.h"
|
|
10
10
|
|
|
11
11
|
@implementation TrueSheetDetentCalculator {
|
|
12
|
-
NSMutableArray<NSNumber *> *
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
- (instancetype)init {
|
|
16
|
-
if (self = [super init]) {
|
|
17
|
-
_resolvedDetentPositions = [NSMutableArray array];
|
|
18
|
-
}
|
|
19
|
-
return self;
|
|
12
|
+
NSMutableArray<NSNumber *> *_resolvedDetentOffsets;
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
#pragma mark - Public Methods
|
|
@@ -34,42 +27,57 @@
|
|
|
34
27
|
return 0;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
- (
|
|
38
|
-
if (index < 0 || index >= (NSInteger)
|
|
39
|
-
return
|
|
30
|
+
- (void)learnOffsetForDetentIndex:(NSInteger)index {
|
|
31
|
+
if (index < 0 || !_resolvedDetentHeights || index >= (NSInteger)_resolvedDetentHeights.count) {
|
|
32
|
+
return;
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
CGFloat
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
CGFloat actualHeight = self.delegate.screenHeight - self.delegate.currentPosition;
|
|
36
|
+
CGFloat resolverHeight = [_resolvedDetentHeights[index] doubleValue];
|
|
37
|
+
// Always update — system offset can change between detent transitions
|
|
38
|
+
if (resolverHeight > 0 && actualHeight > 0) {
|
|
39
|
+
_resolvedDetentOffsets[index] = @(actualHeight - resolverHeight);
|
|
45
40
|
}
|
|
41
|
+
}
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (pos > 0) {
|
|
55
|
-
CGFloat knownDetent = [self detentValueForIndex:i];
|
|
56
|
-
CGFloat expectedPos = screenHeight - (knownDetent * screenHeight);
|
|
57
|
-
CGFloat offset = pos - expectedPos;
|
|
58
|
-
return basePosition + offset;
|
|
43
|
+
- (CGFloat)resolvedHeightForIndex:(NSInteger)index {
|
|
44
|
+
if (_resolvedDetentHeights && index >= 0 && index < (NSInteger)_resolvedDetentHeights.count) {
|
|
45
|
+
CGFloat h = [_resolvedDetentHeights[index] doubleValue];
|
|
46
|
+
if (h > 0) {
|
|
47
|
+
// Use per-detent offset if learned, otherwise find any known offset
|
|
48
|
+
CGFloat offset = [self offsetForIndex:index];
|
|
49
|
+
return h + offset;
|
|
59
50
|
}
|
|
60
51
|
}
|
|
61
52
|
|
|
62
|
-
|
|
53
|
+
CGFloat detentValue = [self detentValueForIndex:index];
|
|
54
|
+
if (_maxDetentHeight > 0) {
|
|
55
|
+
return detentValue * _maxDetentHeight;
|
|
56
|
+
}
|
|
57
|
+
return detentValue * self.delegate.screenHeight;
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
- (
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
- (CGFloat)offsetForIndex:(NSInteger)index {
|
|
61
|
+
// Use this detent's own offset if available
|
|
62
|
+
if (index >= 0 && index < (NSInteger)_resolvedDetentOffsets.count) {
|
|
63
|
+
CGFloat offset = [_resolvedDetentOffsets[index] doubleValue];
|
|
64
|
+
if (offset != 0)
|
|
65
|
+
return offset;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fall back to any known offset
|
|
69
|
+
for (NSInteger i = 0; i < (NSInteger)_resolvedDetentOffsets.count; i++) {
|
|
70
|
+
CGFloat offset = [_resolvedDetentOffsets[i] doubleValue];
|
|
71
|
+
if (offset != 0)
|
|
72
|
+
return offset;
|
|
68
73
|
}
|
|
74
|
+
|
|
75
|
+
return 0;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
72
|
-
|
|
79
|
+
NSArray<NSNumber *> *detents = self.delegate.detents;
|
|
80
|
+
NSInteger count = detents.count;
|
|
73
81
|
if (count == 0) {
|
|
74
82
|
*outIndex = -1;
|
|
75
83
|
*outProgress = 0;
|
|
@@ -77,27 +85,27 @@
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
CGFloat screenHeight = self.delegate.screenHeight;
|
|
80
|
-
CGFloat
|
|
88
|
+
CGFloat sheetHeight = screenHeight - position;
|
|
89
|
+
CGFloat firstHeight = [self resolvedHeightForIndex:0];
|
|
81
90
|
|
|
82
|
-
//
|
|
83
|
-
if (
|
|
84
|
-
CGFloat range = screenHeight - firstPos;
|
|
91
|
+
// Below first detent - interpolating toward closed
|
|
92
|
+
if (sheetHeight < firstHeight) {
|
|
85
93
|
*outIndex = -1;
|
|
86
|
-
*outProgress =
|
|
94
|
+
*outProgress = firstHeight > 0 ? (firstHeight - sheetHeight) / firstHeight : 0;
|
|
87
95
|
return NO;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
// Single detent
|
|
98
|
+
// Single detent
|
|
91
99
|
if (count == 1) {
|
|
92
100
|
*outIndex = 0;
|
|
93
101
|
*outProgress = 0;
|
|
94
102
|
return NO;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
CGFloat
|
|
105
|
+
CGFloat lastHeight = [self resolvedHeightForIndex:count - 1];
|
|
98
106
|
|
|
99
|
-
//
|
|
100
|
-
if (
|
|
107
|
+
// Above last detent
|
|
108
|
+
if (sheetHeight > lastHeight) {
|
|
101
109
|
*outIndex = count - 1;
|
|
102
110
|
*outProgress = 0;
|
|
103
111
|
return NO;
|
|
@@ -105,13 +113,13 @@
|
|
|
105
113
|
|
|
106
114
|
// Between detents
|
|
107
115
|
for (NSInteger i = 0; i < count - 1; i++) {
|
|
108
|
-
CGFloat
|
|
109
|
-
CGFloat
|
|
116
|
+
CGFloat h = [self resolvedHeightForIndex:i];
|
|
117
|
+
CGFloat nextH = [self resolvedHeightForIndex:i + 1];
|
|
110
118
|
|
|
111
|
-
if (
|
|
112
|
-
CGFloat range =
|
|
119
|
+
if (sheetHeight >= h && sheetHeight <= nextH) {
|
|
120
|
+
CGFloat range = nextH - h;
|
|
113
121
|
*outIndex = i;
|
|
114
|
-
*outProgress = range > 0 ? (
|
|
122
|
+
*outProgress = range > 0 ? (sheetHeight - h) / range : 0;
|
|
115
123
|
return YES;
|
|
116
124
|
}
|
|
117
125
|
}
|
|
@@ -154,14 +162,17 @@
|
|
|
154
162
|
return detent + progress * (nextDetent - detent);
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
- (void)
|
|
158
|
-
[
|
|
165
|
+
- (void)clearResolvedHeights {
|
|
166
|
+
[_resolvedDetentHeights removeAllObjects];
|
|
167
|
+
[_resolvedDetentOffsets removeAllObjects];
|
|
159
168
|
}
|
|
160
169
|
|
|
161
170
|
- (void)setDetentCount:(NSInteger)count {
|
|
162
|
-
[
|
|
171
|
+
_resolvedDetentHeights = [NSMutableArray arrayWithCapacity:count];
|
|
172
|
+
_resolvedDetentOffsets = [NSMutableArray arrayWithCapacity:count];
|
|
163
173
|
for (NSInteger i = 0; i < count; i++) {
|
|
164
|
-
[
|
|
174
|
+
[_resolvedDetentHeights addObject:@(0)];
|
|
175
|
+
[_resolvedDetentOffsets addObject:@(0)];
|
|
165
176
|
}
|
|
166
177
|
}
|
|
167
178
|
|
package/package.json
CHANGED