@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.20
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 +25 -4
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +302 -184
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +21 -110
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +116 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +119 -50
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +360 -163
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +54 -12
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
- package/ios/TrueSheetContainerView.h +27 -7
- package/ios/TrueSheetContainerView.mm +31 -11
- package/ios/TrueSheetContentView.h +43 -15
- package/ios/TrueSheetContentView.mm +211 -189
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +93 -17
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +212 -55
- package/ios/TrueSheetViewController.h +31 -3
- package/ios/TrueSheetViewController.mm +393 -94
- package/ios/core/TrueSheetBlurView.h +0 -2
- package/ios/core/TrueSheetBlurView.mm +3 -36
- package/ios/core/TrueSheetDetentCalculator.h +7 -1
- package/ios/core/TrueSheetDetentCalculator.mm +27 -4
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +173 -39
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +218 -243
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetBackground.js +15 -0
- package/lib/module/TrueSheetBackground.js.map +1 -0
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +20 -15
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/detentBackgrounds.js +21 -0
- package/lib/module/detentBackgrounds.js.map +1 -0
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +14 -10
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +18 -2
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/createTrueSheetRouter.js +0 -9
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +10 -7
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/web/constants.js +1 -1
- package/lib/module/web/constants.js.map +1 -1
- package/lib/module/web/layout.js +168 -0
- package/lib/module/web/layout.js.map +1 -0
- package/lib/module/web/vaul/constants.js +4 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/helpers.js +1 -0
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +44 -6
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +6 -8
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +6 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +135 -34
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
- package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
- package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
- package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +11 -7
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +3 -4
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +41 -5
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/web/constants.d.ts +1 -1
- package/lib/typescript/src/web/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/layout.d.ts +19 -0
- package/lib/typescript/src/web/layout.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +7 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
- package/package.json +17 -8
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +206 -48
- package/src/TrueSheet.types.ts +142 -35
- package/src/TrueSheet.web.tsx +270 -290
- package/src/TrueSheetBackground.tsx +17 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +25 -23
- package/src/detentBackgrounds.ts +24 -0
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +14 -10
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +17 -2
- package/src/mocks/navigationExpoRouter.ts +6 -5
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +5 -33
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +28 -30
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +6 -7
- package/src/navigation/screen/useSheetScreenState.ts +2 -0
- package/src/navigation/types.ts +75 -22
- package/src/web/constants.ts +1 -1
- package/src/web/layout.ts +196 -0
- package/src/web/vaul/constants.ts +4 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +5 -0
- package/src/web/vaul/index.tsx +67 -5
- package/src/web/vaul/use-snap-points.ts +6 -14
|
@@ -17,25 +17,20 @@
|
|
|
17
17
|
#import <react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h>
|
|
18
18
|
#import "TrueSheetContainerView.h"
|
|
19
19
|
#import "TrueSheetFooterView.h"
|
|
20
|
-
#import "TrueSheetView.h"
|
|
21
20
|
#import "TrueSheetViewController.h"
|
|
22
21
|
#import "utils/PlatformUtil.h"
|
|
23
22
|
#import "utils/UIView+FirstResponder.h"
|
|
24
23
|
|
|
25
24
|
using namespace facebook::react;
|
|
26
25
|
|
|
27
|
-
static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
28
|
-
|
|
29
26
|
@implementation TrueSheetContentView {
|
|
30
27
|
TrueSheetContentViewShadowNode::ConcreteState::Shared _state;
|
|
31
|
-
RCTScrollViewComponentView *
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
CGFloat _bottomInset;
|
|
35
|
-
CGFloat _originalIndicatorBottomInset;
|
|
28
|
+
RCTScrollViewComponentView *_detectedScrollView;
|
|
29
|
+
CGFloat _lastReportedNaturalHeight;
|
|
30
|
+
CGFloat _appliedKeyboardOffset;
|
|
36
31
|
BOOL _observingTextChanges;
|
|
37
|
-
|
|
38
|
-
BOOL
|
|
32
|
+
UIScrollViewContentInsetAdjustmentBehavior _originalInsetAdjustmentBehavior;
|
|
33
|
+
BOOL _appliedSafeAreaInsetAdjustment;
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
@@ -52,37 +47,60 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
52
47
|
|
|
53
48
|
- (void)dealloc {
|
|
54
49
|
[self stopObservingTextChanges];
|
|
55
|
-
[self unobserveScrollViewContentSize];
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState {
|
|
59
53
|
_state = std::static_pointer_cast<TrueSheetContentViewShadowNode::ConcreteState const>(state);
|
|
54
|
+
|
|
55
|
+
CGFloat naturalHeight = _state->getData().naturalHeight;
|
|
56
|
+
if (naturalHeight != _lastReportedNaturalHeight) {
|
|
57
|
+
_lastReportedNaturalHeight = naturalHeight;
|
|
58
|
+
[self.delegate contentViewDidChangeSize:CGSizeMake(self.frame.size.width, naturalHeight)];
|
|
59
|
+
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// for auto detents — otherwise content lays out naturally like a regular view.
|
|
65
|
-
- (void)setScrollableBounded:(BOOL)bounded {
|
|
66
|
-
if (_scrollableBounded == bounded) {
|
|
62
|
+
- (void)setScrollableHandle:(NSInteger)scrollableHandle {
|
|
63
|
+
if (_scrollableHandle == scrollableHandle) {
|
|
67
64
|
return;
|
|
68
65
|
}
|
|
69
|
-
|
|
66
|
+
_scrollableHandle = scrollableHandle;
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
// Release the previously resolved ScrollView — setupScrollable re-resolves
|
|
69
|
+
[self clearScrollable];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)setSafeAreaInsetAdjustment:(BOOL)safeAreaInsetAdjustment {
|
|
73
|
+
if (_safeAreaInsetAdjustment == safeAreaInsetAdjustment) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
_safeAreaInsetAdjustment = safeAreaInsetAdjustment;
|
|
77
|
+
[self applySafeAreaInsetAdjustment];
|
|
78
|
+
[self updateBottomInset];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
- (void)setFooterInsetAdjustment:(BOOL)footerInsetAdjustment {
|
|
82
|
+
if (_footerInsetAdjustment == footerInsetAdjustment) {
|
|
83
|
+
return;
|
|
75
84
|
}
|
|
85
|
+
_footerInsetAdjustment = footerInsetAdjustment;
|
|
86
|
+
[self updateBottomInset];
|
|
76
87
|
}
|
|
77
88
|
|
|
78
|
-
-
|
|
79
|
-
|
|
89
|
+
// Hands the bottom safe-area inset to UIKit — `automatic` only insets while
|
|
90
|
+
// the content can scroll, which the Android counterpart mirrors manually.
|
|
91
|
+
- (void)applySafeAreaInsetAdjustment {
|
|
92
|
+
if (!_detectedScrollView) {
|
|
80
93
|
return;
|
|
81
94
|
}
|
|
82
|
-
_hasAutoDetent = hasAutoDetent;
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
97
|
+
if (_safeAreaInsetAdjustment && !_appliedSafeAreaInsetAdjustment) {
|
|
98
|
+
_originalInsetAdjustmentBehavior = scrollView.contentInsetAdjustmentBehavior;
|
|
99
|
+
scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
|
|
100
|
+
_appliedSafeAreaInsetAdjustment = YES;
|
|
101
|
+
} else if (!_safeAreaInsetAdjustment && _appliedSafeAreaInsetAdjustment) {
|
|
102
|
+
scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
|
|
103
|
+
_appliedSafeAreaInsetAdjustment = NO;
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
|
|
@@ -111,58 +129,25 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
111
129
|
#pragma mark - Layout
|
|
112
130
|
|
|
113
131
|
- (CGFloat)naturalHeight {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
height
|
|
117
|
-
|
|
118
|
-
return MAX(0, height);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
- (void)reportSizeIfChanged {
|
|
122
|
-
// A deep ScrollView unmount doesn't pass through this view's mount hooks —
|
|
123
|
-
// detect the stale pin here so it (and its contentSize observation) is
|
|
124
|
-
// released instead of lingering until the next explicit setup or recycle.
|
|
125
|
-
if (_pinnedScrollView && ![_pinnedScrollView isDescendantOfView:self]) {
|
|
126
|
-
[self.delegate contentViewScrollViewDidChange];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// naturalHeight mixes frames from different views; mid-transaction they're
|
|
130
|
-
// momentarily inconsistent (parent updates before the ScrollView), which
|
|
131
|
-
// feeds back into the auto detent and oscillates. Coalesce to the next
|
|
132
|
-
// main-queue tick so frames are settled before measuring.
|
|
133
|
-
if (_pinnedScrollView) {
|
|
134
|
-
if (_isReportPending) {
|
|
135
|
-
return;
|
|
132
|
+
if (_state) {
|
|
133
|
+
CGFloat height = _state->getData().naturalHeight;
|
|
134
|
+
if (height > 0) {
|
|
135
|
+
return height;
|
|
136
136
|
}
|
|
137
|
-
_isReportPending = YES;
|
|
138
|
-
|
|
139
|
-
__weak __typeof(self) weakSelf = self;
|
|
140
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
141
|
-
__typeof(self) strongSelf = weakSelf;
|
|
142
|
-
if (!strongSelf) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
strongSelf->_isReportPending = NO;
|
|
146
|
-
[strongSelf reportSizeNow];
|
|
147
|
-
});
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
[self reportSizeNow];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
- (void)reportSizeNow {
|
|
155
|
-
CGSize newSize = CGSizeMake(self.frame.size.width, self.naturalHeight);
|
|
156
|
-
if (!CGSizeEqualToSize(newSize, _lastSize)) {
|
|
157
|
-
_lastSize = newSize;
|
|
158
|
-
[self.delegate contentViewDidChangeSize:newSize];
|
|
159
137
|
}
|
|
138
|
+
return self.frame.size.height;
|
|
160
139
|
}
|
|
161
140
|
|
|
162
141
|
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
|
163
142
|
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
|
|
164
143
|
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
|
|
165
|
-
|
|
144
|
+
|
|
145
|
+
// A deep ScrollView unmount doesn't pass through this view's mount hooks —
|
|
146
|
+
// detect the stale reference here so it's released instead of lingering until the
|
|
147
|
+
// next explicit setup or recycle.
|
|
148
|
+
if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
|
|
149
|
+
[self.delegate contentViewScrollViewDidChange];
|
|
150
|
+
}
|
|
166
151
|
}
|
|
167
152
|
|
|
168
153
|
#pragma mark - Child Mounting
|
|
@@ -178,46 +163,105 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
178
163
|
}
|
|
179
164
|
|
|
180
165
|
- (void)checkScrollViewChanged {
|
|
181
|
-
if (!
|
|
166
|
+
if (!_detectedScrollView || ![_detectedScrollView isDescendantOfView:self]) {
|
|
167
|
+
[self.delegate contentViewScrollViewDidChange];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// A handle that commits in the same transaction as the ScrollView's insertion
|
|
172
|
+
// resolves to nothing — prop updates process before mount instructions, and a
|
|
173
|
+
// deep insertion doesn't pass through this view's mount hooks. Retry once the
|
|
174
|
+
// whole transaction has mounted.
|
|
175
|
+
- (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
|
|
176
|
+
withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry {
|
|
177
|
+
if (_scrollableHandle > 0 && !_detectedScrollView) {
|
|
182
178
|
[self.delegate contentViewScrollViewDidChange];
|
|
183
179
|
}
|
|
180
|
+
|
|
181
|
+
// Layout has settled for the whole tree — the footer overlap (sheet height,
|
|
182
|
+
// scroll view frame) and the keyboard inset are current here
|
|
183
|
+
[self updateBottomInset];
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
#pragma mark - Scrollable
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
// The scroll indicator follows automatically — UIKit derives its insets from
|
|
189
|
+
// the content inset while `automaticallyAdjustsScrollIndicatorInsets` is set.
|
|
190
|
+
- (void)setBottomInset:(CGFloat)inset {
|
|
191
|
+
if (!_detectedScrollView)
|
|
190
192
|
return;
|
|
191
193
|
|
|
192
|
-
UIEdgeInsets contentInset =
|
|
193
|
-
contentInset.bottom
|
|
194
|
-
|
|
194
|
+
UIEdgeInsets contentInset = _detectedScrollView.scrollView.contentInset;
|
|
195
|
+
if (fabs(contentInset.bottom - inset) < 0.001) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
contentInset.bottom = inset;
|
|
200
|
+
_detectedScrollView.scrollView.contentInset = contentInset;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// How much of the absolute footer covers the scroll view — measured from the
|
|
204
|
+
// footer's layout position (pinned to the container's bottom edge; its
|
|
205
|
+
// keyboard rise is a transform) so a short scroll view ending above the footer
|
|
206
|
+
// gets no inset. UIKit stacks the safe area on top of contentInset while the
|
|
207
|
+
// adjustment behavior is automatic — take out the inset baked into the
|
|
208
|
+
// footer's height so the total lands on the footer's top edge. Stays constant
|
|
209
|
+
// across keyboard transitions: the footer drops its inset as it rises, and
|
|
210
|
+
// the baked value drops with it. A pinned footer adds only the overlap not
|
|
211
|
+
// already covered by the keyboard inset.
|
|
212
|
+
- (CGFloat)footerInsetAbove:(CGFloat)keyboardInset {
|
|
213
|
+
if (!_footerInsetAdjustment || !self.footerView || !_detectedScrollView) {
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
UIView *container = self.footerView.superview;
|
|
218
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
219
|
+
CGRect scrollFrame = [scrollView convertRect:scrollView.bounds toView:container];
|
|
220
|
+
CGFloat footerTop = container.bounds.size.height - self.footerView.bounds.size.height;
|
|
221
|
+
|
|
222
|
+
CGFloat inset = CGRectGetMaxY(scrollFrame) - footerTop;
|
|
223
|
+
if (_appliedSafeAreaInsetAdjustment) {
|
|
224
|
+
inset -= self.footerView.appliedBottomInset;
|
|
225
|
+
}
|
|
226
|
+
if (!_keyboardObserver.viewController.footerAvoidsKeyboard) {
|
|
227
|
+
inset -= keyboardInset;
|
|
228
|
+
}
|
|
229
|
+
return MAX(0, inset);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
- (CGFloat)keyboardInset {
|
|
233
|
+
CGFloat keyboardHeight = _keyboardObserver.currentHeight;
|
|
234
|
+
return keyboardHeight > 0 ? [self keyboardInsetWithHeight:keyboardHeight] : 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Safe to call mid keyboard animation: the target matches the animation's
|
|
238
|
+
// model value (the footer inset doesn't change with the keyboard), so the
|
|
239
|
+
// early-out in setBottomInset: leaves the animation alone.
|
|
240
|
+
- (void)updateBottomInset {
|
|
241
|
+
if (!_detectedScrollView) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
CGFloat keyboardInset = [self keyboardInset];
|
|
245
|
+
[self setBottomInset:[self footerInsetAbove:keyboardInset] + keyboardInset];
|
|
199
246
|
}
|
|
200
247
|
|
|
201
248
|
- (void)clearScrollable {
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
[self setBottomInset:0];
|
|
250
|
+
_appliedKeyboardOffset = 0;
|
|
251
|
+
if (_appliedSafeAreaInsetAdjustment) {
|
|
252
|
+
_detectedScrollView.scrollView.contentInsetAdjustmentBehavior = _originalInsetAdjustmentBehavior;
|
|
253
|
+
_appliedSafeAreaInsetAdjustment = NO;
|
|
204
254
|
}
|
|
205
|
-
|
|
206
|
-
[self setScrollableBounded:NO];
|
|
207
|
-
_pinnedScrollView = nil;
|
|
208
|
-
_bottomInset = 0;
|
|
209
|
-
_originalIndicatorBottomInset = 0;
|
|
210
|
-
[self reportSizeIfChanged];
|
|
255
|
+
_detectedScrollView = nil;
|
|
211
256
|
}
|
|
212
257
|
|
|
213
|
-
- (void)
|
|
214
|
-
// Check if
|
|
215
|
-
if (
|
|
258
|
+
- (void)setupScrollable {
|
|
259
|
+
// Check if the detected scroll view is still valid (still in view hierarchy)
|
|
260
|
+
if (_detectedScrollView && ![_detectedScrollView isDescendantOfView:self]) {
|
|
216
261
|
[self clearScrollable];
|
|
217
262
|
}
|
|
218
263
|
|
|
219
|
-
|
|
220
|
-
if (_pinnedScrollView && _bottomInset == bottomInset) {
|
|
264
|
+
if (_detectedScrollView) {
|
|
221
265
|
return;
|
|
222
266
|
}
|
|
223
267
|
|
|
@@ -226,104 +270,76 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
226
270
|
return;
|
|
227
271
|
}
|
|
228
272
|
|
|
229
|
-
|
|
230
|
-
if (!_pinnedScrollView) {
|
|
231
|
-
_originalIndicatorBottomInset = scrollView.scrollView.verticalScrollIndicatorInsets.bottom;
|
|
232
|
-
_pinnedScrollView = scrollView;
|
|
233
|
-
|
|
234
|
-
[self observeScrollViewContentSize];
|
|
235
|
-
[self setScrollableBounded:_hasAutoDetent];
|
|
236
|
-
[self reportSizeIfChanged];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
_bottomInset = bottomInset;
|
|
273
|
+
_detectedScrollView = scrollView;
|
|
240
274
|
|
|
241
|
-
[self
|
|
275
|
+
[self applySafeAreaInsetAdjustment];
|
|
242
276
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
if (keyboardHeight > 0) {
|
|
246
|
-
CGFloat inset = [self keyboardInsetWithHeight:keyboardHeight];
|
|
247
|
-
[self setScrollViewContentInset:inset indicatorInset:_originalIndicatorBottomInset + inset];
|
|
248
|
-
}
|
|
277
|
+
// Apply the footer inset, and the keyboard inset if it's currently showing
|
|
278
|
+
[self updateBottomInset];
|
|
249
279
|
}
|
|
250
280
|
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// A relative footer stays behind the keyboard below the content, so its
|
|
254
|
-
// height shields that much of the keyboard's overlap.
|
|
281
|
+
// Short or nested scroll views can end above the sheet's bottom edge.
|
|
282
|
+
// Their frame also accounts for space occupied by a relative footer.
|
|
255
283
|
- (CGFloat)keyboardInsetWithHeight:(CGFloat)height {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
return MAX(0, height - self.footerView.frame.size.height);
|
|
263
|
-
}
|
|
284
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
285
|
+
UIWindow *window = scrollView.window;
|
|
286
|
+
CGRect scrollFrame = [scrollView convertRect:scrollView.bounds toView:window];
|
|
287
|
+
CGFloat keyboardTop = CGRectGetMaxY(window.bounds) - height;
|
|
288
|
+
CGFloat inset = MAX(0, CGRectGetMaxY(scrollFrame) - keyboardTop);
|
|
264
289
|
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (_observedScrollView == scrollView) {
|
|
270
|
-
return;
|
|
290
|
+
// UIKit stacks the safe area on top of contentInset while the adjustment
|
|
291
|
+
// behavior is automatic — take it out so the total lands on the keyboard edge.
|
|
292
|
+
if (_appliedSafeAreaInsetAdjustment) {
|
|
293
|
+
inset = MAX(0, inset - scrollView.safeAreaInsets.bottom);
|
|
271
294
|
}
|
|
272
|
-
[self unobserveScrollViewContentSize];
|
|
273
|
-
_observedScrollView = scrollView;
|
|
274
|
-
[scrollView addObserver:self
|
|
275
|
-
forKeyPath:@"contentSize"
|
|
276
|
-
options:NSKeyValueObservingOptionNew
|
|
277
|
-
context:TrueSheetContentSizeContext];
|
|
278
|
-
}
|
|
279
295
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
// Track how much of keyboardOffset actually lands in the inset so the caret
|
|
297
|
+
// reveal can compensate — the offset shifts the inset, not the keyboard edge.
|
|
298
|
+
CGFloat adjustedInset = MAX(0, inset + self.keyboardOffset);
|
|
299
|
+
_appliedKeyboardOffset =
|
|
300
|
+
adjustedInset + [self footerInsetAbove:adjustedInset] - (inset + [self footerInsetAbove:inset]);
|
|
301
|
+
|
|
302
|
+
// Content that already fits above the keyboard (and footer) has nothing to
|
|
303
|
+
// reveal — the inset would only open blank scroll range below it (huge gap
|
|
304
|
+
// at the bottom).
|
|
305
|
+
if (scrollView.contentSize.height + [self footerInsetAbove:adjustedInset] <=
|
|
306
|
+
scrollView.bounds.size.height - adjustedInset) {
|
|
307
|
+
_appliedKeyboardOffset = 0;
|
|
308
|
+
return 0;
|
|
284
309
|
}
|
|
310
|
+
|
|
311
|
+
return adjustedInset;
|
|
285
312
|
}
|
|
286
313
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
314
|
+
// An absolute footer floats over the viewport's bottom edge — extend the
|
|
315
|
+
// caret target so it clears the footer, not just the keyboard. Not needed
|
|
316
|
+
// while the footer inset is applied (the visible rect already ends above it)
|
|
317
|
+
// or when the footer stays behind the keyboard.
|
|
318
|
+
- (CGFloat)footerOcclusion {
|
|
319
|
+
TrueSheetViewController *controller = _keyboardObserver.viewController;
|
|
320
|
+
if (self.footerView && controller.absoluteFooter && controller.footerAvoidsKeyboard &&
|
|
321
|
+
[self footerInsetAbove:0] <= 0) {
|
|
322
|
+
return [self.footerView keyboardOcclusionHeight];
|
|
294
323
|
}
|
|
295
|
-
|
|
324
|
+
return 0;
|
|
296
325
|
}
|
|
297
326
|
|
|
327
|
+
// Resolves the user-provided `scrollableHandle` within the content subtree —
|
|
328
|
+
// Fabric component views carry their React tag as `UIView.tag`.
|
|
298
329
|
- (RCTScrollViewComponentView *)findScrollView {
|
|
299
|
-
if (
|
|
300
|
-
return
|
|
330
|
+
if (_detectedScrollView) {
|
|
331
|
+
return _detectedScrollView;
|
|
301
332
|
}
|
|
302
333
|
|
|
303
|
-
if (
|
|
334
|
+
if (_scrollableHandle <= 0) {
|
|
304
335
|
return nil;
|
|
305
336
|
}
|
|
306
337
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
for (UIView *subview in self.subviews) {
|
|
311
|
-
scrollView = [self findScrollViewInSubviews:subview.subviews];
|
|
312
|
-
if (scrollView) {
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
338
|
+
UIView *view = [self viewWithTag:_scrollableHandle];
|
|
339
|
+
if ([view isKindOfClass:RCTScrollViewComponentView.class]) {
|
|
340
|
+
return (RCTScrollViewComponentView *)view;
|
|
316
341
|
}
|
|
317
342
|
|
|
318
|
-
return scrollView;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
- (RCTScrollViewComponentView *)findScrollViewInSubviews:(NSArray<UIView *> *)subviews {
|
|
322
|
-
for (UIView *subview in subviews) {
|
|
323
|
-
if ([subview isKindOfClass:RCTScrollViewComponentView.class] && ![subview isKindOfClass:TrueSheetView.class]) {
|
|
324
|
-
return (RCTScrollViewComponentView *)subview;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
343
|
return nil;
|
|
328
344
|
}
|
|
329
345
|
|
|
@@ -331,11 +347,11 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
331
347
|
|
|
332
348
|
- (void)applyScrollEdgeEffects:(nullable ScrollableOptions *)options {
|
|
333
349
|
#if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_0)
|
|
334
|
-
if (!
|
|
350
|
+
if (!_detectedScrollView)
|
|
335
351
|
return;
|
|
336
352
|
|
|
337
353
|
if (@available(iOS 26.0, *)) {
|
|
338
|
-
UIScrollView *scrollView =
|
|
354
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
339
355
|
auto topEffect = options ? options.topScrollEdgeEffect : TrueSheetViewTopScrollEdgeEffect::Hidden;
|
|
340
356
|
auto bottomEffect = options ? options.bottomScrollEdgeEffect : TrueSheetViewBottomScrollEdgeEffect::Hidden;
|
|
341
357
|
|
|
@@ -371,7 +387,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
371
387
|
#pragma mark - TrueSheetKeyboardObserverDelegate
|
|
372
388
|
|
|
373
389
|
- (void)keyboardWillShow:(CGFloat)height duration:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
|
|
374
|
-
if (!
|
|
390
|
+
if (!_detectedScrollView) {
|
|
375
391
|
return;
|
|
376
392
|
}
|
|
377
393
|
|
|
@@ -380,14 +396,17 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
380
396
|
TrueSheetViewController *sheetController = _keyboardObserver.viewController;
|
|
381
397
|
UIView *firstResponder = sheetController ? [sheetController.view findFirstResponder] : nil;
|
|
382
398
|
|
|
383
|
-
CGFloat
|
|
399
|
+
CGFloat keyboardInset = [self keyboardInsetWithHeight:height];
|
|
400
|
+
CGFloat inset = [self footerInsetAbove:keyboardInset] + keyboardInset;
|
|
384
401
|
[UIView animateWithDuration:duration
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
402
|
+
delay:0
|
|
403
|
+
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
404
|
+
animations:^{
|
|
405
|
+
[self setBottomInset:inset];
|
|
406
|
+
}
|
|
407
|
+
completion:^(BOOL finished) {
|
|
408
|
+
[self updateBottomInset];
|
|
409
|
+
}];
|
|
391
410
|
|
|
392
411
|
// Defer scroll until the next run loop so content insets are applied first
|
|
393
412
|
if (firstResponder) {
|
|
@@ -398,7 +417,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
398
417
|
}
|
|
399
418
|
|
|
400
419
|
- (void)focusedInputTextDidChange:(NSNotification *)notification {
|
|
401
|
-
if (!
|
|
420
|
+
if (!_detectedScrollView || !_keyboardObserver || _keyboardObserver.currentHeight <= 0) {
|
|
402
421
|
return;
|
|
403
422
|
}
|
|
404
423
|
|
|
@@ -409,12 +428,15 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
409
428
|
}
|
|
410
429
|
|
|
411
430
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
431
|
+
// Typing can grow the content (e.g. a multiline input) past the fits-above-
|
|
432
|
+
// the-keyboard threshold — keep the inset in sync before revealing the caret.
|
|
433
|
+
[self updateBottomInset];
|
|
412
434
|
[self scrollToFocusedCaretAnimated:YES];
|
|
413
435
|
});
|
|
414
436
|
}
|
|
415
437
|
|
|
416
438
|
- (void)scrollToFocusedCaretAnimated:(BOOL)animated {
|
|
417
|
-
if (!
|
|
439
|
+
if (!_detectedScrollView) {
|
|
418
440
|
return;
|
|
419
441
|
}
|
|
420
442
|
|
|
@@ -424,7 +446,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
424
446
|
return;
|
|
425
447
|
}
|
|
426
448
|
|
|
427
|
-
UIScrollView *scrollView =
|
|
449
|
+
UIScrollView *scrollView = _detectedScrollView.scrollView;
|
|
428
450
|
CGRect targetRect = [firstResponder convertRect:firstResponder.bounds toView:scrollView];
|
|
429
451
|
|
|
430
452
|
if ([firstResponder conformsToProtocol:@protocol(UITextInput)]) {
|
|
@@ -442,14 +464,14 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
442
464
|
}
|
|
443
465
|
}
|
|
444
466
|
|
|
445
|
-
targetRect.size.height += self.keyboardScrollOffset;
|
|
467
|
+
targetRect.size.height += self.keyboardScrollOffset + [self footerOcclusion] - _appliedKeyboardOffset;
|
|
446
468
|
[scrollView scrollRectToVisible:targetRect animated:animated];
|
|
447
469
|
}
|
|
448
470
|
|
|
449
471
|
- (void)keyboardWillHide:(NSTimeInterval)duration curve:(UIViewAnimationOptions)curve {
|
|
450
472
|
[self stopObservingTextChanges];
|
|
451
473
|
|
|
452
|
-
if (!
|
|
474
|
+
if (!_detectedScrollView) {
|
|
453
475
|
return;
|
|
454
476
|
}
|
|
455
477
|
|
|
@@ -457,8 +479,7 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
457
479
|
delay:0
|
|
458
480
|
options:curve | UIViewAnimationOptionBeginFromCurrentState
|
|
459
481
|
animations:^{
|
|
460
|
-
[self
|
|
461
|
-
indicatorInset:self->_originalIndicatorBottomInset];
|
|
482
|
+
[self setBottomInset:[self footerInsetAbove:0]];
|
|
462
483
|
}
|
|
463
484
|
completion:nil];
|
|
464
485
|
}
|
|
@@ -470,9 +491,10 @@ static void *TrueSheetContentSizeContext = &TrueSheetContentSizeContext;
|
|
|
470
491
|
[self stopObservingTextChanges];
|
|
471
492
|
[self clearScrollable];
|
|
472
493
|
_state.reset();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
494
|
+
_scrollableHandle = 0;
|
|
495
|
+
_safeAreaInsetAdjustment = NO;
|
|
496
|
+
_footerInsetAdjustment = NO;
|
|
497
|
+
_lastReportedNaturalHeight = 0;
|
|
476
498
|
}
|
|
477
499
|
|
|
478
500
|
@end
|
|
@@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
27
27
|
@property (nonatomic, weak, nullable) TrueSheetKeyboardObserver *keyboardObserver;
|
|
28
28
|
@property (nonatomic, weak, nullable) id<TrueSheetFooterViewDelegate> delegate;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Bottom inset currently baked into the footer's layout height — pairs with
|
|
32
|
+
* the measured frame so detent math can subtract exactly what's baked in.
|
|
33
|
+
*/
|
|
34
|
+
@property (nonatomic, readonly) CGFloat appliedBottomInset;
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
37
|
* Re-applies the footer's keyboard slide using the current keyboard height.
|
|
32
38
|
* No-op when the keyboard is hidden. Used to reflect live `keyboardOffset` changes.
|