@lodev09/react-native-true-sheet 3.0.0-beta.8 → 3.0.0
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/README.md +13 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +29 -33
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +48 -43
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +387 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +22 -4
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +67 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +44 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +94 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +37 -33
- package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
- package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
- package/android/src/main/res/values/styles.xml +13 -1
- package/ios/TrueSheetContainerView.mm +4 -0
- package/ios/TrueSheetContentView.h +2 -1
- package/ios/TrueSheetContentView.mm +91 -11
- package/ios/TrueSheetView.mm +65 -41
- package/ios/TrueSheetViewController.h +21 -10
- package/ios/TrueSheetViewController.mm +330 -165
- package/ios/core/TrueSheetBlurView.h +24 -0
- package/ios/{utils/ConversionUtil.mm → core/TrueSheetBlurView.mm} +65 -3
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +40 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/lib/module/TrueSheet.js +65 -12
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +13 -7
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +4 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +58 -6
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +14 -5
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +0 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +80 -10
- package/src/TrueSheet.types.ts +65 -6
- package/src/__mocks__/index.js +0 -5
- package/src/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/src/index.ts +0 -1
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.h +0 -26
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.h +0 -26
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnWillDismissEvent.h +0 -26
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/ConversionUtil.h +0 -24
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#import "TrueSheetViewController.h"
|
|
10
10
|
#import "TrueSheetContentView.h"
|
|
11
|
-
#import "
|
|
11
|
+
#import "core/TrueSheetBlurView.h"
|
|
12
12
|
#import "utils/GestureUtil.h"
|
|
13
13
|
#import "utils/WindowUtil.h"
|
|
14
14
|
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
|
|
22
22
|
@implementation TrueSheetViewController {
|
|
23
23
|
CGFloat _lastPosition;
|
|
24
|
-
CGFloat _lastTransitionPosition;
|
|
25
|
-
BOOL _isTransitioning;
|
|
26
24
|
BOOL _isDragging;
|
|
27
|
-
|
|
25
|
+
NSInteger _pendingDetentIndex;
|
|
28
26
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
// Reference to parent TrueSheetViewController (if presented from another sheet)
|
|
28
|
+
__weak TrueSheetViewController *_parentSheetController;
|
|
29
|
+
|
|
30
|
+
// Blur effect view
|
|
31
|
+
TrueSheetBlurView *_blurView;
|
|
32
|
+
|
|
33
|
+
// Resolved detent positions (Y coordinate when sheet rests at each detent)
|
|
34
|
+
NSMutableArray<NSNumber *> *_resolvedDetentPositions;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
#pragma mark - Initialization
|
|
@@ -39,32 +42,24 @@
|
|
|
39
42
|
_contentHeight = @(0);
|
|
40
43
|
_headerHeight = @(0);
|
|
41
44
|
_grabber = YES;
|
|
45
|
+
_draggable = YES;
|
|
42
46
|
_dimmed = YES;
|
|
43
47
|
_dimmedDetentIndex = @(0);
|
|
44
48
|
_pageSizing = YES;
|
|
45
49
|
_lastPosition = 0;
|
|
46
|
-
_lastTransitionPosition = 0;
|
|
47
|
-
_isTransitioning = NO;
|
|
48
50
|
_isDragging = NO;
|
|
49
|
-
_isTrackingPositionFromLayout = NO;
|
|
50
|
-
_layoutTransitioning = NO;
|
|
51
51
|
_isPresented = NO;
|
|
52
52
|
_activeDetentIndex = -1;
|
|
53
|
+
_pendingDetentIndex = -1;
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
_fakeTransitionView.userInteractionEnabled = NO;
|
|
55
|
+
_blurInteraction = YES;
|
|
56
|
+
_resolvedDetentPositions = [NSMutableArray array];
|
|
57
57
|
}
|
|
58
58
|
return self;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
- (void)dealloc {
|
|
62
62
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
63
|
-
|
|
64
|
-
if (_displayLink) {
|
|
65
|
-
[_displayLink invalidate];
|
|
66
|
-
_displayLink = nil;
|
|
67
|
-
}
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
#pragma mark - Computed Properties
|
|
@@ -77,7 +72,7 @@
|
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
- (BOOL)isActiveAndVisible {
|
|
80
|
-
return self.isViewLoaded && self.view.window != nil
|
|
75
|
+
return self.isViewLoaded && self.view.window != nil;
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
- (UIView *)presentedView {
|
|
@@ -89,21 +84,8 @@
|
|
|
89
84
|
return presentedView ? presentedView.frame.origin.y : 0.0;
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
- (CGFloat)
|
|
93
|
-
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
UIWindow *window = [WindowUtil keyWindow];
|
|
97
|
-
return window ? window.safeAreaInsets.bottom : 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
- (CGFloat)currentHeight {
|
|
101
|
-
return self.containerHeight - self.currentPosition - self.bottomInset;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
- (CGFloat)containerHeight {
|
|
105
|
-
UIView *sheetContainerView = self.sheetPresentationController.containerView;
|
|
106
|
-
return sheetContainerView ? sheetContainerView.frame.size.height : 0.0;
|
|
87
|
+
- (CGFloat)screenHeight {
|
|
88
|
+
return UIScreen.mainScreen.bounds.size.height;
|
|
107
89
|
}
|
|
108
90
|
|
|
109
91
|
- (NSInteger)currentDetentIndex {
|
|
@@ -146,10 +128,25 @@
|
|
|
146
128
|
|
|
147
129
|
// Only trigger on initial presentation, not repositioning
|
|
148
130
|
if (!_isPresented) {
|
|
149
|
-
if
|
|
150
|
-
|
|
131
|
+
// Capture parent sheet reference if presented from another TrueSheet
|
|
132
|
+
UIViewController *presenter = self.presentingViewController;
|
|
133
|
+
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
134
|
+
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
135
|
+
// Notify parent that it is about to lose focus
|
|
136
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
137
|
+
[_parentSheetController.delegate viewControllerWillBlur];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
|
|
142
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
143
|
+
NSInteger index = [self currentDetentIndex];
|
|
144
|
+
CGFloat position = self.currentPosition;
|
|
145
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
146
|
+
|
|
147
|
+
[self.delegate viewControllerWillPresentAtIndex:index position:position detent:detent];
|
|
148
|
+
});
|
|
151
149
|
}
|
|
152
|
-
[self setupTransitionPositionTracking];
|
|
153
150
|
}
|
|
154
151
|
}
|
|
155
152
|
|
|
@@ -157,8 +154,19 @@
|
|
|
157
154
|
[super viewDidAppear:animated];
|
|
158
155
|
|
|
159
156
|
if (!_isPresented) {
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
// Notify parent that it has lost focus (after the child sheet appeared)
|
|
158
|
+
if (_parentSheetController) {
|
|
159
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
160
|
+
[_parentSheetController.delegate viewControllerDidBlur];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
|
|
165
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
166
|
+
NSInteger index = [self currentDetentIndex];
|
|
167
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
168
|
+
[self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
|
|
169
|
+
});
|
|
162
170
|
}
|
|
163
171
|
[self setupGestureRecognizer];
|
|
164
172
|
_isPresented = YES;
|
|
@@ -168,12 +176,24 @@
|
|
|
168
176
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
169
177
|
[super viewWillDisappear:animated];
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
BOOL isActuallyDismissing = self.presentingViewController == nil || self.isBeingDismissed;
|
|
180
|
+
|
|
181
|
+
if (isActuallyDismissing) {
|
|
182
|
+
// Notify the parent sheet (if any) that it is about to regain focus
|
|
183
|
+
if (_parentSheetController) {
|
|
184
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
185
|
+
[_parentSheetController.delegate viewControllerWillFocus];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
190
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
191
|
+
});
|
|
174
192
|
|
|
175
|
-
|
|
176
|
-
|
|
193
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
|
|
194
|
+
[self.delegate viewControllerWillDismiss];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
177
197
|
}
|
|
178
198
|
|
|
179
199
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
@@ -182,11 +202,19 @@
|
|
|
182
202
|
// Only dispatch didDismiss when actually dismissing (not when another modal is presented on top)
|
|
183
203
|
BOOL isActuallyDismissing = self.presentingViewController == nil || self.isBeingDismissed;
|
|
184
204
|
|
|
185
|
-
if (isActuallyDismissing
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
if (isActuallyDismissing) {
|
|
206
|
+
// Notify the parent sheet (if any) that it regained focus
|
|
207
|
+
if (_parentSheetController) {
|
|
208
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
209
|
+
[_parentSheetController.delegate viewControllerDidFocus];
|
|
210
|
+
}
|
|
211
|
+
_parentSheetController = nil;
|
|
212
|
+
}
|
|
188
213
|
|
|
189
|
-
|
|
214
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidDismiss)]) {
|
|
215
|
+
[self.delegate viewControllerDidDismiss];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
190
218
|
|
|
191
219
|
if (isActuallyDismissing) {
|
|
192
220
|
_isPresented = NO;
|
|
@@ -201,16 +229,29 @@
|
|
|
201
229
|
[self.delegate viewControllerDidChangeSize:self.view.frame.size];
|
|
202
230
|
}
|
|
203
231
|
|
|
204
|
-
if (
|
|
205
|
-
|
|
232
|
+
// Check if there's an active presented controller that has settled (not being presented/dismissed)
|
|
233
|
+
UIViewController *presented = self.presentedViewController;
|
|
234
|
+
BOOL hasPresentedController = presented != nil && !presented.isBeingPresented && !presented.isBeingDismissed;
|
|
235
|
+
|
|
236
|
+
if (!_isDragging) {
|
|
237
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
238
|
+
// Update stored position for current detent (handles content size changes)
|
|
239
|
+
[self storeResolvedPositionForIndex:[self currentDetentIndex]];
|
|
240
|
+
|
|
241
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:hasPresentedController];
|
|
242
|
+
});
|
|
243
|
+
}
|
|
206
244
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
// Emit pending detent change after programmatic resize settles
|
|
246
|
+
if (_pendingDetentIndex >= 0) {
|
|
247
|
+
NSInteger pendingIndex = _pendingDetentIndex;
|
|
248
|
+
_pendingDetentIndex = -1;
|
|
210
249
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
250
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
251
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
252
|
+
CGFloat detent = [self detentValueForIndex:pendingIndex];
|
|
253
|
+
[self.delegate viewControllerDidChangeDetent:pendingIndex position:self.currentPosition detent:detent];
|
|
254
|
+
}
|
|
214
255
|
});
|
|
215
256
|
}
|
|
216
257
|
}
|
|
@@ -237,13 +278,28 @@
|
|
|
237
278
|
if (!presentedView)
|
|
238
279
|
return;
|
|
239
280
|
|
|
281
|
+
// Disable pan gestures if draggable is NO
|
|
282
|
+
if (!self.draggable) {
|
|
283
|
+
[GestureUtil setPanGesturesEnabled:NO forView:presentedView];
|
|
284
|
+
|
|
285
|
+
// Also disable ScrollView's pan gesture if present
|
|
286
|
+
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
287
|
+
if (contentView) {
|
|
288
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
289
|
+
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
290
|
+
[GestureUtil setPanGesturesEnabled:NO forView:scrollViewComponent.scrollView];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
240
296
|
// Attach to presented view's pan gesture (sheet's drag gesture from UIKit)
|
|
241
297
|
[GestureUtil attachPanGestureHandler:presentedView target:self selector:@selector(handlePanGesture:)];
|
|
242
298
|
|
|
243
299
|
// Also attach to ScrollView's pan gesture if present
|
|
244
300
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
245
301
|
if (contentView) {
|
|
246
|
-
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView];
|
|
302
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
247
303
|
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
248
304
|
[GestureUtil attachPanGestureHandler:scrollViewComponent.scrollView
|
|
249
305
|
target:self
|
|
@@ -252,11 +308,29 @@
|
|
|
252
308
|
}
|
|
253
309
|
}
|
|
254
310
|
|
|
311
|
+
- (void)updateDraggable {
|
|
312
|
+
UIView *presentedView = self.presentedView;
|
|
313
|
+
if (!presentedView)
|
|
314
|
+
return;
|
|
315
|
+
|
|
316
|
+
[GestureUtil setPanGesturesEnabled:self.draggable forView:presentedView];
|
|
317
|
+
|
|
318
|
+
// Also update ScrollView's pan gesture if present
|
|
319
|
+
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
320
|
+
if (contentView) {
|
|
321
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
322
|
+
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
323
|
+
[GestureUtil setPanGesturesEnabled:self.draggable forView:scrollViewComponent.scrollView];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
255
328
|
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
|
|
256
329
|
NSInteger index = [self currentDetentIndex];
|
|
330
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
257
331
|
|
|
258
|
-
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:)]) {
|
|
259
|
-
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition];
|
|
332
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:detent:)]) {
|
|
333
|
+
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition detent:detent];
|
|
260
334
|
}
|
|
261
335
|
|
|
262
336
|
switch (gesture.state) {
|
|
@@ -264,14 +338,19 @@
|
|
|
264
338
|
_isDragging = YES;
|
|
265
339
|
break;
|
|
266
340
|
case UIGestureRecognizerStateChanged:
|
|
267
|
-
|
|
268
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition transitioning:NO];
|
|
269
|
-
}
|
|
341
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES];
|
|
270
342
|
break;
|
|
271
343
|
case UIGestureRecognizerStateEnded:
|
|
272
|
-
case UIGestureRecognizerStateCancelled:
|
|
344
|
+
case UIGestureRecognizerStateCancelled: {
|
|
273
345
|
_isDragging = NO;
|
|
346
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
347
|
+
// Store resolved position when drag ends
|
|
348
|
+
[self storeResolvedPositionForIndex:[self currentDetentIndex]];
|
|
349
|
+
|
|
350
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
351
|
+
});
|
|
274
352
|
break;
|
|
353
|
+
}
|
|
275
354
|
default:
|
|
276
355
|
break;
|
|
277
356
|
}
|
|
@@ -279,90 +358,156 @@
|
|
|
279
358
|
|
|
280
359
|
#pragma mark - Position Tracking
|
|
281
360
|
|
|
282
|
-
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position
|
|
283
|
-
|
|
361
|
+
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime {
|
|
362
|
+
// Use epsilon comparison to avoid missing updates due to floating point precision
|
|
363
|
+
if (fabs(_lastPosition - position) > 0.01) {
|
|
284
364
|
_lastPosition = position;
|
|
285
365
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
366
|
+
CGFloat index = [self interpolatedIndexForPosition:position];
|
|
367
|
+
CGFloat detent = [self interpolatedDetentForPosition:position];
|
|
368
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangePosition:position:detent:realtime:)]) {
|
|
369
|
+
[self.delegate viewControllerDidChangePosition:index position:position detent:detent realtime:realtime];
|
|
289
370
|
}
|
|
290
371
|
}
|
|
291
372
|
}
|
|
292
373
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
374
|
+
/// Stores the current position for the given detent index
|
|
375
|
+
- (void)storeResolvedPositionForIndex:(NSInteger)index {
|
|
376
|
+
if (index >= 0 && index < (NSInteger)_resolvedDetentPositions.count) {
|
|
377
|
+
_resolvedDetentPositions[index] = @(self.currentPosition);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Returns the estimated Y position for a detent index, using stored positions when available
|
|
382
|
+
- (CGFloat)estimatedPositionForIndex:(NSInteger)index {
|
|
383
|
+
if (index < 0 || index >= (NSInteger)_resolvedDetentPositions.count)
|
|
384
|
+
return 0;
|
|
303
385
|
|
|
304
|
-
|
|
386
|
+
CGFloat storedPos = [_resolvedDetentPositions[index] doubleValue];
|
|
387
|
+
if (storedPos > 0) {
|
|
388
|
+
return storedPos;
|
|
389
|
+
}
|
|
305
390
|
|
|
306
|
-
|
|
307
|
-
|
|
391
|
+
// Estimate based on detent value and known offset from first resolved position
|
|
392
|
+
CGFloat detentValue = [self detentValueForIndex:index];
|
|
393
|
+
CGFloat basePosition = self.screenHeight - (detentValue * self.screenHeight);
|
|
308
394
|
|
|
309
|
-
|
|
310
|
-
|
|
395
|
+
// Find a resolved position to calculate offset
|
|
396
|
+
for (NSInteger i = 0; i < (NSInteger)_resolvedDetentPositions.count; i++) {
|
|
397
|
+
CGFloat pos = [_resolvedDetentPositions[i] doubleValue];
|
|
398
|
+
if (pos > 0) {
|
|
399
|
+
CGFloat knownDetent = [self detentValueForIndex:i];
|
|
400
|
+
CGFloat expectedPos = self.screenHeight - (knownDetent * self.screenHeight);
|
|
401
|
+
CGFloat offset = pos - expectedPos;
|
|
402
|
+
return basePosition + offset;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return basePosition;
|
|
407
|
+
}
|
|
311
408
|
|
|
312
|
-
|
|
313
|
-
|
|
409
|
+
/// Finds the segment containing the given position and returns the lower index and progress within that segment.
|
|
410
|
+
/// Returns YES if a segment was found, NO otherwise. When NO, `outIndex` contains the boundary index.
|
|
411
|
+
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
412
|
+
NSInteger count = _resolvedDetentPositions.count;
|
|
413
|
+
if (count == 0) {
|
|
414
|
+
*outIndex = -1;
|
|
415
|
+
*outProgress = 0;
|
|
416
|
+
return NO;
|
|
417
|
+
}
|
|
314
418
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
419
|
+
if (count == 1) {
|
|
420
|
+
*outIndex = 0;
|
|
421
|
+
*outProgress = 0;
|
|
422
|
+
return NO;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
CGFloat firstPos = [self estimatedPositionForIndex:0];
|
|
426
|
+
CGFloat lastPos = [self estimatedPositionForIndex:count - 1];
|
|
318
427
|
|
|
319
|
-
|
|
320
|
-
|
|
428
|
+
// Below first detent (position > firstPos means sheet is smaller)
|
|
429
|
+
if (position > firstPos) {
|
|
430
|
+
CGFloat range = self.screenHeight - firstPos;
|
|
431
|
+
*outIndex = -1;
|
|
432
|
+
*outProgress = range > 0 ? (position - firstPos) / range : 0;
|
|
433
|
+
return NO;
|
|
434
|
+
}
|
|
321
435
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
436
|
+
// Above last detent
|
|
437
|
+
if (position < lastPos) {
|
|
438
|
+
*outIndex = count - 1;
|
|
439
|
+
*outProgress = 0;
|
|
440
|
+
return NO;
|
|
441
|
+
}
|
|
325
442
|
|
|
326
|
-
|
|
443
|
+
// Find segment (positions decrease as index increases)
|
|
444
|
+
for (NSInteger i = 0; i < count - 1; i++) {
|
|
445
|
+
CGFloat pos = [self estimatedPositionForIndex:i];
|
|
446
|
+
CGFloat nextPos = [self estimatedPositionForIndex:i + 1];
|
|
327
447
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
448
|
+
if (position <= pos && position >= nextPos) {
|
|
449
|
+
CGFloat range = pos - nextPos;
|
|
450
|
+
*outIndex = i;
|
|
451
|
+
*outProgress = range > 0 ? (pos - position) / range : 0;
|
|
452
|
+
return YES;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
332
455
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
[self->_displayLink invalidate];
|
|
337
|
-
self->_displayLink = nil;
|
|
338
|
-
[self->_fakeTransitionView removeFromSuperview];
|
|
339
|
-
self->_isTransitioning = NO;
|
|
340
|
-
}];
|
|
456
|
+
*outIndex = count - 1;
|
|
457
|
+
*outProgress = 0;
|
|
458
|
+
return NO;
|
|
341
459
|
}
|
|
342
460
|
|
|
343
|
-
- (
|
|
344
|
-
|
|
461
|
+
- (CGFloat)interpolatedIndexForPosition:(CGFloat)position {
|
|
462
|
+
NSInteger index;
|
|
463
|
+
CGFloat progress;
|
|
464
|
+
BOOL found = [self findSegmentForPosition:position outIndex:&index outProgress:&progress];
|
|
345
465
|
|
|
346
|
-
if (
|
|
347
|
-
|
|
466
|
+
if (!found) {
|
|
467
|
+
if (index == -1) {
|
|
468
|
+
// Below first detent - return negative progress
|
|
469
|
+
return -progress;
|
|
470
|
+
}
|
|
471
|
+
// At or beyond boundary
|
|
472
|
+
return index;
|
|
473
|
+
}
|
|
348
474
|
|
|
349
|
-
//
|
|
350
|
-
|
|
475
|
+
// Within a segment - interpolate
|
|
476
|
+
return index + fmax(0, fmin(1, progress));
|
|
477
|
+
}
|
|
351
478
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
479
|
+
- (CGFloat)interpolatedDetentForPosition:(CGFloat)position {
|
|
480
|
+
NSInteger index;
|
|
481
|
+
CGFloat progress;
|
|
482
|
+
BOOL found = [self findSegmentForPosition:position outIndex:&index outProgress:&progress];
|
|
355
483
|
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
_lastTransitionPosition = position;
|
|
484
|
+
if (!found) {
|
|
485
|
+
if (index == -1) {
|
|
486
|
+
// Below first detent
|
|
487
|
+
CGFloat firstDetent = [self detentValueForIndex:0];
|
|
488
|
+
return fmax(0, firstDetent * (1 - progress));
|
|
362
489
|
}
|
|
490
|
+
// At or beyond boundary
|
|
491
|
+
return [self detentValueForIndex:index];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Within a segment - interpolate between detent values
|
|
495
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
496
|
+
CGFloat nextDetent = [self detentValueForIndex:index + 1];
|
|
497
|
+
return detent + progress * (nextDetent - detent);
|
|
498
|
+
}
|
|
363
499
|
|
|
364
|
-
|
|
500
|
+
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
501
|
+
if (index >= 0 && index < (NSInteger)_detents.count) {
|
|
502
|
+
CGFloat value = [_detents[index] doubleValue];
|
|
503
|
+
// For auto (-1), calculate actual fraction from content + header height
|
|
504
|
+
if (value == -1) {
|
|
505
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
506
|
+
return autoHeight / self.screenHeight;
|
|
507
|
+
}
|
|
508
|
+
return value;
|
|
365
509
|
}
|
|
510
|
+
return 0;
|
|
366
511
|
}
|
|
367
512
|
|
|
368
513
|
#pragma mark - Sheet Configuration
|
|
@@ -373,16 +518,18 @@
|
|
|
373
518
|
return;
|
|
374
519
|
|
|
375
520
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
521
|
+
[_resolvedDetentPositions removeAllObjects];
|
|
376
522
|
|
|
377
|
-
|
|
378
|
-
CGFloat totalHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue] - self.bottomInset;
|
|
523
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
379
524
|
|
|
380
525
|
for (NSInteger index = 0; index < self.detents.count; index++) {
|
|
381
526
|
id detent = self.detents[index];
|
|
382
527
|
UISheetPresentationControllerDetent *sheetDetent = [self detentForValue:detent
|
|
383
|
-
|
|
528
|
+
withAutoHeight:autoHeight
|
|
384
529
|
atIndex:index];
|
|
385
530
|
[detents addObject:sheetDetent];
|
|
531
|
+
// Initialize with placeholder - will be updated when sheet settles at each detent
|
|
532
|
+
[_resolvedDetentPositions addObject:@(0)];
|
|
386
533
|
}
|
|
387
534
|
|
|
388
535
|
sheet.detents = detents;
|
|
@@ -408,25 +555,19 @@
|
|
|
408
555
|
}
|
|
409
556
|
}
|
|
410
557
|
|
|
411
|
-
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
558
|
+
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
559
|
+
withAutoHeight:(CGFloat)autoHeight
|
|
560
|
+
atIndex:(NSInteger)index {
|
|
412
561
|
if (![detent isKindOfClass:[NSNumber class]]) {
|
|
413
562
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
414
563
|
}
|
|
415
564
|
|
|
416
|
-
CGFloat value = [detent
|
|
565
|
+
CGFloat value = [detent doubleValue];
|
|
417
566
|
|
|
418
567
|
// -1 represents "auto" (fit content height)
|
|
419
568
|
if (value == -1) {
|
|
420
569
|
if (@available(iOS 16.0, *)) {
|
|
421
|
-
|
|
422
|
-
return [UISheetPresentationControllerDetent
|
|
423
|
-
customDetentWithIdentifier:detentId
|
|
424
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
425
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
426
|
-
CGFloat maxValue =
|
|
427
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
428
|
-
return fmin(height, maxValue);
|
|
429
|
-
}];
|
|
570
|
+
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight];
|
|
430
571
|
} else {
|
|
431
572
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
432
573
|
}
|
|
@@ -438,21 +579,9 @@
|
|
|
438
579
|
}
|
|
439
580
|
|
|
440
581
|
if (@available(iOS 16.0, *)) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return [UISheetPresentationControllerDetent mediumDetent];
|
|
445
|
-
} else {
|
|
446
|
-
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
447
|
-
return [UISheetPresentationControllerDetent
|
|
448
|
-
customDetentWithIdentifier:detentId
|
|
449
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
450
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
451
|
-
CGFloat maxValue =
|
|
452
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
453
|
-
return fmin(value * maxDetentValue, maxValue);
|
|
454
|
-
}];
|
|
455
|
-
}
|
|
582
|
+
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
583
|
+
CGFloat sheetHeight = value * self.screenHeight;
|
|
584
|
+
return [self customDetentWithIdentifier:detentId height:sheetHeight];
|
|
456
585
|
} else if (value >= 0.5) {
|
|
457
586
|
return [UISheetPresentationControllerDetent largeDetent];
|
|
458
587
|
} else {
|
|
@@ -460,6 +589,18 @@
|
|
|
460
589
|
}
|
|
461
590
|
}
|
|
462
591
|
|
|
592
|
+
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
593
|
+
height:(CGFloat)height API_AVAILABLE(ios(16.0)) {
|
|
594
|
+
return [UISheetPresentationControllerDetent
|
|
595
|
+
customDetentWithIdentifier:identifier
|
|
596
|
+
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
597
|
+
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
598
|
+
CGFloat maxValue =
|
|
599
|
+
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
600
|
+
return fmin(height, maxValue);
|
|
601
|
+
}];
|
|
602
|
+
}
|
|
603
|
+
|
|
463
604
|
- (UISheetPresentationControllerDetentIdentifier)detentIdentifierForIndex:(NSInteger)index {
|
|
464
605
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
465
606
|
if (!sheet)
|
|
@@ -512,6 +653,16 @@
|
|
|
512
653
|
[self applyActiveDetent];
|
|
513
654
|
}
|
|
514
655
|
|
|
656
|
+
- (void)resizeToDetentIndex:(NSInteger)index {
|
|
657
|
+
if (index == _activeDetentIndex) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
_pendingDetentIndex = index;
|
|
662
|
+
_activeDetentIndex = index;
|
|
663
|
+
[self applyActiveDetent];
|
|
664
|
+
}
|
|
665
|
+
|
|
515
666
|
- (void)setupSheetProps {
|
|
516
667
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
517
668
|
if (!sheet) {
|
|
@@ -527,7 +678,7 @@
|
|
|
527
678
|
}
|
|
528
679
|
|
|
529
680
|
sheet.prefersEdgeAttachedInCompactHeight = YES;
|
|
530
|
-
sheet.prefersGrabberVisible = self.grabber;
|
|
681
|
+
sheet.prefersGrabberVisible = self.grabber && self.draggable;
|
|
531
682
|
|
|
532
683
|
if (self.cornerRadius) {
|
|
533
684
|
sheet.preferredCornerRadius = [self.cornerRadius floatValue];
|
|
@@ -539,16 +690,25 @@
|
|
|
539
690
|
|
|
540
691
|
// Setup or remove blur effect
|
|
541
692
|
if (self.blurTint && self.blurTint.length > 0) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
693
|
+
// Create blur view if needed
|
|
694
|
+
if (!_blurView) {
|
|
695
|
+
_blurView = [[TrueSheetBlurView alloc] init];
|
|
696
|
+
_blurView.frame = self.view.bounds;
|
|
697
|
+
_blurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
698
|
+
[self.view insertSubview:_blurView atIndex:0];
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Update blur properties and apply effect
|
|
702
|
+
_blurView.blurTint = self.blurTint;
|
|
703
|
+
_blurView.blurIntensity = self.blurIntensity;
|
|
704
|
+
_blurView.blurInteraction = self.blurInteraction;
|
|
705
|
+
[_blurView applyBlurEffect];
|
|
547
706
|
} else {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
707
|
+
// Remove blur effect
|
|
708
|
+
if (_blurView) {
|
|
709
|
+
[_blurView removeBlurEffect];
|
|
710
|
+
[_blurView removeFromSuperview];
|
|
711
|
+
_blurView = nil;
|
|
552
712
|
}
|
|
553
713
|
}
|
|
554
714
|
}
|
|
@@ -557,9 +717,14 @@
|
|
|
557
717
|
|
|
558
718
|
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
|
|
559
719
|
(UISheetPresentationController *)sheetPresentationController {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
720
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
721
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
722
|
+
NSInteger index = [self currentDetentIndex];
|
|
723
|
+
if (index >= 0) {
|
|
724
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
725
|
+
[self.delegate viewControllerDidChangeDetent:index position:self.currentPosition detent:detent];
|
|
726
|
+
}
|
|
727
|
+
});
|
|
563
728
|
}
|
|
564
729
|
}
|
|
565
730
|
|