@rn-tools/sheets 0.1.1 → 0.1.3

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 CHANGED
@@ -12,8 +12,24 @@ Uses SwiftUI's sheet API and Android's BottomSheetDialog component to render Rea
12
12
 
13
13
  ## Installation
14
14
 
15
- `yarn add @rntools/sheets`
15
+ `yarn add @rntools/sheets expo-build-properties`
16
+
17
+ Update your minimum iOS deployment target to 16 in `app.json`:
18
+
19
+ ```json
20
+ {
21
+ "plugins": [
22
+ [
23
+ "expo-build-properties",
24
+ {
25
+ "ios": {
26
+ "deploymentTarget": "16.4"
27
+ }
28
+ }
29
+ ]
30
+ }
16
31
 
32
+ ```
17
33
 
18
34
  As with most non-core expo modules this requires a new native build
19
35
 
@@ -28,7 +44,7 @@ export default function App() {
28
44
 
29
45
  return (
30
46
  <View className="flex-1">
31
- <Button title="Show sheet" onPress={() => setIsOpen(!isOpen)} />
47
+ <Button title="Show sheet" onPress={() => setIsOpen(true)} />
32
48
 
33
49
  <BottomSheet
34
50
  isOpen={isOpen}
@@ -89,9 +89,9 @@ class RNToolsSheetsView(context: Context, appContext: AppContext) : ExpoView(con
89
89
  try {
90
90
  Color.parseColor(it) // Convert hex string to Color
91
91
  } catch (e: IllegalArgumentException) {
92
- Color.TRANSPARENT
92
+ Color.WHITE
93
93
  }
94
- } ?: Color.TRANSPARENT
94
+ } ?: Color.WHITE
95
95
 
96
96
  val drawable = GradientDrawable().apply {
97
97
  setColor(backgroundColor)
@@ -1,6 +1,8 @@
1
1
  require 'json'
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+ new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
5
+
4
6
 
5
7
  Pod::Spec.new do |s|
6
8
  s.name = 'RNToolsSheets'
@@ -19,10 +21,12 @@ Pod::Spec.new do |s|
19
21
  s.static_framework = true
20
22
 
21
23
  s.dependency 'ExpoModulesCore'
24
+ s.public_header_files = 'Sources/RNTSurfaceTouchHandlerWrapper.h'
22
25
 
23
26
  # Swift/Objective-C compatibility
24
27
  s.pod_target_xcconfig = {
25
28
  'DEFINES_MODULE' => 'YES',
29
+ 'OTHER_SWIFT_FLAGS' => new_arch_enabled ? '-DRCT_NEW_ARCH_ENABLED' : ''
26
30
  }
27
31
 
28
32
  s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
@@ -1,4 +1,5 @@
1
1
  import ExpoModulesCore
2
+ import React
2
3
  import SwiftUI
3
4
 
4
5
  public class SheetProps: ObservableObject {
@@ -11,7 +12,6 @@ public class SheetProps: ObservableObject {
11
12
  @Published var grabberVisible: Bool = true
12
13
  @Published var backgroundColor: String? = nil
13
14
  @Published var cornerRadius: Float? = nil
14
-
15
15
  }
16
16
 
17
17
  struct SheetAppearance: Record {
@@ -20,7 +20,7 @@ struct SheetAppearance: Record {
20
20
 
21
21
  @Field
22
22
  var backgroundColor: String?
23
-
23
+
24
24
  @Field
25
25
  var cornerRadius: Float?
26
26
  }
@@ -32,50 +32,84 @@ public class RNToolsSheetsView: ExpoView {
32
32
 
33
33
  var touchHandler: RCTTouchHandler?
34
34
 
35
+ private lazy var sheetVC = SheetInternalViewController()
36
+
35
37
  lazy var hostingController = UIHostingController(
36
38
  rootView: ContentView(
37
- props: props, onDismiss: onDismiss, onStateChange: onStateChange))
39
+ props: props, sheetVC: sheetVC, onDismiss: onDismiss,
40
+ onStateChange: onStateChange))
38
41
 
39
42
  required init(appContext: AppContext? = nil) {
40
43
  super.init(appContext: appContext)
41
44
 
42
45
  if let bridge = appContext?.reactBridge {
43
- touchHandler = RCTTouchHandler(bridge: bridge)
46
+ sheetVC.bridge = bridge
44
47
  }
45
48
 
46
49
  hostingController.view.autoresizingMask = [
47
50
  .flexibleWidth, .flexibleHeight,
48
51
  ]
49
52
  hostingController.view.backgroundColor = UIColor.clear
50
- hostingController.rootView = ContentView(
51
- props: props, onDismiss: onDismiss, onStateChange: onStateChange)
53
+ }
54
+
55
+ public override func layoutSubviews() {
56
+ super.layoutSubviews()
57
+ hostingController.view.frame = bounds
58
+ }
52
59
 
60
+ public override func didMoveToSuperview() {
61
+ super.didMoveToSuperview()
62
+ let parentViewController = findParentViewControllerOrNil()
63
+
64
+ parentViewController.apply {
65
+ $0.addChild(hostingController)
66
+ }
67
+
68
+ hostingController.view.layer.removeAllAnimations()
69
+ hostingController.view.frame = bounds
53
70
  addSubview(hostingController.view)
71
+ parentViewController.apply {
72
+ hostingController.didMove(toParent: $0)
73
+ }
54
74
  }
55
75
 
56
76
  public override func reactSubviews() -> [UIView]! {
57
77
  return []
58
78
  }
59
79
 
60
- public override func insertReactSubview(_ subview: UIView!, at atIndex: Int)
61
- {
62
- super.insertReactSubview(subview, at: atIndex)
63
- props.children.insert(subview, at: atIndex)
64
- if atIndex == 0 {
65
- touchHandler?.attach(to: subview)
80
+ #if RCT_NEW_ARCH_ENABLED
81
+ public override func mountChildComponentView(
82
+ _ childComponentView: UIView,
83
+ index: Int
84
+ ) {
85
+ sheetVC.insertChild(childComponentView, at: index)
66
86
  }
67
- }
68
87
 
69
- public override func removeReactSubview(_ subview: UIView!) {
70
- super.removeReactSubview(subview)
71
- if let index = props.children.firstIndex(of: subview) {
72
- props.children.remove(at: index)
88
+ public override func unmountChildComponentView(
89
+ _ childComponentView: UIView,
90
+ index: Int
91
+ ) {
92
+ childComponentView.removeFromSuperview()
73
93
  }
74
- }
94
+
95
+ #else
96
+ public override func insertReactSubview(
97
+ _ subview: UIView!, at atIndex: Int
98
+ ) {
99
+ sheetVC.insertChild(subview, at: atIndex)
100
+ }
101
+
102
+ public override func removeReactSubview(_ subview: UIView!) {
103
+ sheetVC.removeChild(subview)
104
+ }
105
+
106
+ #endif
107
+
75
108
  }
76
109
 
77
110
  struct ContentView: View {
78
111
  @ObservedObject var props: SheetProps
112
+ var sheetVC: SheetInternalViewController
79
113
  var onDismiss: EventDispatcher
80
114
  var onStateChange: EventDispatcher
81
115
 
@@ -119,101 +153,146 @@ struct ContentView: View {
119
153
  onStateChange(["type": "HIDDEN"])
120
154
  }
121
155
  ) {
122
- VStack {
123
- ForEach(Array(props.children.enumerated()), id: \.offset) {
124
- index, child in
125
- RepresentableView(view: child)
126
- }
127
- }
128
- .background(
129
- GeometryReader { geometry in
130
- Color.clear
131
- .onChange(of: geometry.size.height) { newHeight in
132
- if abs(newHeight - lastHeight) > 2 {
133
- if !isDragging {
134
- isDragging = true
135
- onStateChange(["type": "DRAGGING"])
136
- }
156
+ SheetInternalVCRepresentable(controller: sheetVC)
157
+ .background(
158
+ GeometryReader { geometry in
159
+ Color.clear
160
+ .onChange(of: geometry.size.height) {
161
+ newHeight in
162
+ if abs(newHeight - lastHeight) > 2 {
163
+ if !isDragging {
164
+ isDragging = true
165
+ onStateChange(["type": "DRAGGING"])
166
+ }
137
167
 
138
- settleTimer?.invalidate()
139
- settleTimer = Timer.scheduledTimer(
140
- withTimeInterval: 0.15, repeats: false
141
- ) { _ in
142
- isDragging = false
143
- onStateChange(["type": "SETTLING"])
144
-
145
- DispatchQueue.main.asyncAfter(
146
- deadline: .now() + 0.15
147
- ) {
148
- let idx = upperSnapIndex(
149
- for: newHeight,
150
- snapPoints: props.snapPoints
151
- )
152
- onStateChange([
153
- "type": "OPEN",
154
- "payload": ["index": idx],
155
- ])
168
+ settleTimer?.invalidate()
169
+ settleTimer = Timer.scheduledTimer(
170
+ withTimeInterval: 0.15,
171
+ repeats: false
172
+ ) { _ in
173
+ isDragging = false
174
+ onStateChange(["type": "SETTLING"])
175
+
176
+ DispatchQueue.main.asyncAfter(
177
+ deadline: .now() + 0.15
178
+ ) {
179
+ let idx = upperSnapIndex(
180
+ for: newHeight,
181
+ snapPoints: props.snapPoints
182
+ )
183
+ onStateChange([
184
+ "type": "OPEN",
185
+ "payload": ["index": idx],
186
+ ])
187
+ }
156
188
  }
157
189
  }
158
- }
159
190
 
160
- lastHeight = newHeight
161
- }
191
+ lastHeight = newHeight
192
+ }
193
+ }
194
+ )
195
+ .presentationBackground(
196
+ props.backgroundColor != nil
197
+ ? Color(hex: props.backgroundColor!) : Color.white
198
+ )
199
+ .presentationDragIndicator(
200
+ props.grabberVisible ? .visible : .hidden
201
+ )
202
+ .presentationDetents(
203
+ Set(detents),
204
+ selection: $selectedDetent
205
+ )
206
+ .presentationCornerRadius(
207
+ props.cornerRadius.map { CGFloat($0) }
208
+ )
209
+ .onAppear {
210
+ selectedDetent = detent(for: props.openToIndex)
162
211
  }
163
- )
164
- .presentationBackground(props.backgroundColor != nil ? Color(hex: props.backgroundColor!) : Color.white)
165
- .presentationDragIndicator(
166
- props.grabberVisible ? .visible : .hidden
167
- )
168
- .presentationDetents(
169
- Set(detents),
170
- selection: $selectedDetent
171
- )
172
- .presentationCornerRadius(props.cornerRadius.map { CGFloat($0) })
173
- .onAppear {
174
- selectedDetent = detent(for: props.openToIndex)
175
- }
176
212
  }
177
213
  }
178
214
  }
179
215
 
180
- struct RepresentableView: UIViewRepresentable {
181
- var view: UIView
216
+ struct SheetInternalVCRepresentable: UIViewControllerRepresentable {
217
+ let controller: SheetInternalViewController
182
218
 
183
- func makeUIView(context: Context) -> UIView {
184
- let containerView = UIView()
185
- containerView.backgroundColor = .clear
219
+ func makeUIViewController(context: Context) -> SheetInternalViewController {
220
+ controller
221
+ }
222
+ func updateUIViewController(
223
+ _ uiViewController: SheetInternalViewController,
224
+ context: Context
225
+ ) {}
226
+ }
186
227
 
187
- view.translatesAutoresizingMaskIntoConstraints = false
188
- containerView.addSubview(view)
228
+ final class SheetInternalViewController: UIViewController {
229
+ var bridge: RCTBridge? {
230
+ didSet {
231
+ touchHandler = RCTTouchHandler(bridge: bridge)
232
+ self.touchHandler?.attach(to: self.view)
233
+ }
234
+ }
235
+ var surfaceTouchHandler = RNTSurfaceTouchHandlerWrapper()
236
+ var touchHandler: RCTTouchHandler?
189
237
 
190
- NSLayoutConstraint.activate([
191
- view.topAnchor.constraint(equalTo: containerView.topAnchor),
192
- view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
193
- view.trailingAnchor.constraint(
194
- equalTo: containerView.trailingAnchor),
195
- view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
196
- ])
238
+ init() {
239
+ super.init(nibName: nil, bundle: nil)
240
+ view.backgroundColor = .clear
241
+ }
242
+
243
+ override func loadView() {
244
+ self.view = UIView()
245
+
246
+ self.surfaceTouchHandler.attach(to: self.view)
247
+ }
248
+
249
+ @available(*, unavailable) required init?(coder: NSCoder) { fatalError() }
250
+
251
+ func insertChild(_ child: UIView, at index: Int) {
252
+ view.insertSubview(child, at: index)
197
253
 
198
- return containerView
199
254
  }
200
255
 
201
- func updateUIView(_ uiView: UIView, context: Context) {}
256
+ func removeChild(_ child: UIView) {
257
+ child.removeFromSuperview()
258
+ }
202
259
  }
203
260
 
261
+ extension Optional {
262
+ func apply(_ fn: (Wrapped) -> Void) {
263
+ if case let .some(val) = self {
264
+ fn(val)
265
+ }
266
+ }
267
+ }
268
+
269
+ extension UIView {
270
+ // Walks the responder chain to find the parent UIViewController
271
+ // or null if not in a heirarchy yet
272
+ func findParentViewControllerOrNil() -> UIViewController? {
273
+ var nextResponder: UIResponder? = next
274
+ while nextResponder != nil && nextResponder as? UIViewController == nil
275
+ {
276
+ nextResponder = nextResponder?.next
277
+ }
278
+
279
+ return nextResponder as? UIViewController
280
+ }
281
+ }
204
282
 
205
283
  extension Color {
206
284
  init(hex: String) {
207
285
  var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
208
286
  hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
209
-
287
+
210
288
  var rgb: UInt64 = 0
211
289
  Scanner(string: hexSanitized).scanHexInt64(&rgb)
212
-
290
+
213
291
  let red = Double((rgb & 0xFF0000) >> 16) / 255.0
214
292
  let green = Double((rgb & 0x00FF00) >> 8) / 255.0
215
293
  let blue = Double(rgb & 0x0000FF) / 255.0
216
-
294
+
217
295
  self.init(red: red, green: green, blue: blue)
218
296
  }
219
297
  }
298
+
@@ -0,0 +1,11 @@
1
+ // RNTSurfaceTouchHandlerWrapper.h (PUBLIC)
2
+ #import <Foundation/Foundation.h>
3
+ @class UIView;
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+ @interface RNTSurfaceTouchHandlerWrapper : NSObject
7
+ - (void)attachToView:(UIView *)view;
8
+ @end
9
+ NS_ASSUME_NONNULL_END
10
+
11
+
@@ -0,0 +1,21 @@
1
+ // RNTSurfaceTouchHandlerWrapper.mm (Objective‑C++, .mm extension!)
2
+ #import "RNTSurfaceTouchHandlerWrapper.h"
3
+ #import <React/RCTSurfaceTouchHandler.h>
4
+
5
+ @implementation RNTSurfaceTouchHandlerWrapper {
6
+ RCTSurfaceTouchHandler *_handler;
7
+ }
8
+
9
+ - (instancetype)init {
10
+ if (self = [super init]) {
11
+ _handler = [[RCTSurfaceTouchHandler alloc] init];
12
+ }
13
+ return self;
14
+ }
15
+
16
+ - (void)attachToView:(UIView *)view {
17
+ [_handler attachToView:view];
18
+ }
19
+ @end
20
+
21
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rn-tools/sheets",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "My new module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
@@ -20,7 +20,7 @@
20
20
  "sheets",
21
21
  "RNToolsSheets"
22
22
  ],
23
- "repository": "https://github.com/ajsmth/rn-tools",
23
+ "repository": "https://github.com/ajsmth/rn-tools/tree/main/packages/sheets",
24
24
  "bugs": {
25
25
  "url": "https://github.com/ajsmth/rn-tools/issues"
26
26
  },