native-view-ios-26 1.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 +350 -0
- package/components.ts +57 -0
- package/index.d.ts +70 -0
- package/index.js +53 -0
- package/ios/NativeSafeAreaScrollView.swift +150 -0
- package/ios/NativeSafeAreaScrollViewManager.swift +31 -0
- package/ios/NativeSafeAreaView.swift +117 -0
- package/ios/NativeSafeAreaViewManager.m +16 -0
- package/ios/NativeSafeAreaViewManager.swift +19 -0
- package/ios/NativeViewIOS26.m +14 -0
- package/ios/NativeViewIOS26.swift +120 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# native-view-ios-26
|
|
2
|
+
|
|
3
|
+
Native iOS Safe Area module for React Native với Swift bridge. Thư viện này cung cấp 2 native components với blur effect dưới status bar và API để truy xuất thông tin safe area insets.
|
|
4
|
+
|
|
5
|
+
## Tính năng
|
|
6
|
+
|
|
7
|
+
- ✅ `<NativeSafeAreaView>` - Native view với safe area và blur effect
|
|
8
|
+
- ✅ `<NativeSafeAreaScrollView>` - Native scroll view với safe area và blur effect
|
|
9
|
+
- ✅ Blur effect tự động dưới status bar (systemMaterial blur)
|
|
10
|
+
- ✅ Lấy safe area insets hiện tại (top, bottom, left, right)
|
|
11
|
+
- ✅ Lắng nghe thay đổi safe area khi xoay màn hình
|
|
12
|
+
- ✅ Hỗ trợ iOS 13+ và iOS 26
|
|
13
|
+
- ✅ Viết bằng Swift với hiệu suất cao
|
|
14
|
+
- ✅ TypeScript support đầy đủ
|
|
15
|
+
|
|
16
|
+
## Cài đặt
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install native-view-ios-26
|
|
20
|
+
# hoặc
|
|
21
|
+
yarn add native-view-ios-26
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### iOS
|
|
25
|
+
|
|
26
|
+
Sau khi cài đặt, chạy lệnh sau để cài đặt CocoaPods dependencies:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd ios && pod install && cd ..
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Sử dụng
|
|
33
|
+
|
|
34
|
+
### 1. Native Components với Blur Effect
|
|
35
|
+
|
|
36
|
+
#### NativeSafeAreaView
|
|
37
|
+
|
|
38
|
+
Component view cơ bản với safe area insets và blur effect dưới status bar:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
import { NativeSafeAreaView } from 'native-view-ios-26';
|
|
42
|
+
|
|
43
|
+
function App() {
|
|
44
|
+
return (
|
|
45
|
+
<NativeSafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
|
|
46
|
+
<Text>Nội dung tự động có padding safe area</Text>
|
|
47
|
+
<Text>Blur effect xuất hiện dưới status bar</Text>
|
|
48
|
+
</NativeSafeAreaView>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Props:**
|
|
54
|
+
- `showBlur?: boolean` - Hiển thị blur effect (mặc định: `true`)
|
|
55
|
+
- `style?: ViewStyle` - Style cho view
|
|
56
|
+
|
|
57
|
+
#### NativeSafeAreaScrollView
|
|
58
|
+
|
|
59
|
+
Component scroll view với safe area insets và blur effect dưới status bar:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import { NativeSafeAreaScrollView } from 'native-view-ios-26';
|
|
63
|
+
|
|
64
|
+
function App() {
|
|
65
|
+
return (
|
|
66
|
+
<NativeSafeAreaScrollView
|
|
67
|
+
style={{ flex: 1 }}
|
|
68
|
+
showBlur={true}
|
|
69
|
+
showsVerticalScrollIndicator={true}
|
|
70
|
+
>
|
|
71
|
+
<Text>Nội dung có thể scroll</Text>
|
|
72
|
+
<Text>Blur effect luôn cố định dưới status bar</Text>
|
|
73
|
+
{/* More content... */}
|
|
74
|
+
</NativeSafeAreaScrollView>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Props:**
|
|
80
|
+
- `showBlur?: boolean` - Hiển thị blur effect (mặc định: `true`)
|
|
81
|
+
- `scrollEnabled?: boolean` - Cho phép scroll (mặc định: `true`)
|
|
82
|
+
- `showsVerticalScrollIndicator?: boolean` - Hiển thị thanh scroll dọc (mặc định: `true`)
|
|
83
|
+
- `showsHorizontalScrollIndicator?: boolean` - Hiển thị thanh scroll ngang (mặc định: `false`)
|
|
84
|
+
- `style?: ViewStyle` - Style cho scroll view
|
|
85
|
+
|
|
86
|
+
### 2. Tắt Blur Effect
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Không hiển thị blur effect
|
|
90
|
+
<NativeSafeAreaView showBlur={false}>
|
|
91
|
+
<Text>Không có blur effect</Text>
|
|
92
|
+
</NativeSafeAreaView>
|
|
93
|
+
|
|
94
|
+
<NativeSafeAreaScrollView showBlur={false}>
|
|
95
|
+
<Text>Scroll view không có blur effect</Text>
|
|
96
|
+
</NativeSafeAreaScrollView>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Lấy Safe Area Insets programmatically
|
|
100
|
+
|
|
101
|
+
### 3. Lấy Safe Area Insets programmatically
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import NativeViewIOS26 from 'native-view-ios-26';
|
|
105
|
+
|
|
106
|
+
// Lấy safe area insets
|
|
107
|
+
const insets = await NativeViewIOS26.getSafeAreaInsets();
|
|
108
|
+
console.log('Safe Area Insets:', insets);
|
|
109
|
+
// Output: { top: 47, bottom: 34, left: 0, right: 0 }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 4. Lắng nghe thay đổi Safe Area
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
import { useEffect, useState } from 'react';
|
|
116
|
+
import NativeViewIOS26 from 'native-view-ios-26';
|
|
117
|
+
|
|
118
|
+
function App() {
|
|
119
|
+
const [insets, setInsets] = useState({ top: 0, bottom: 0, left: 0, right: 0 });
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
// Lấy giá trị ban đầu
|
|
123
|
+
NativeViewIOS26.getSafeAreaInsets().then(setInsets);
|
|
124
|
+
|
|
125
|
+
// Đăng ký listener
|
|
126
|
+
const subscription = NativeViewIOS26.addSafeAreaListener((newInsets) => {
|
|
127
|
+
console.log('Safe area changed:', newInsets);
|
|
128
|
+
setInsets(newInsets);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Cleanup
|
|
132
|
+
return () => {
|
|
133
|
+
subscription.remove();
|
|
134
|
+
};
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<View style={{
|
|
139
|
+
paddingTop: insets.top,
|
|
140
|
+
paddingBottom: insets.bottom,
|
|
141
|
+
paddingLeft: insets.left,
|
|
142
|
+
paddingRight: insets.right
|
|
143
|
+
}}>
|
|
144
|
+
<Text>Top: {insets.top}</Text>
|
|
145
|
+
<Text>Bottom: {insets.bottom}</Text>
|
|
146
|
+
<Text>Left: {insets.left}</Text>
|
|
147
|
+
<Text>Right: {insets.right}</Text>
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### Components
|
|
156
|
+
|
|
157
|
+
#### `<NativeSafeAreaView>`
|
|
158
|
+
|
|
159
|
+
Native view component với automatic safe area insets và blur effect.
|
|
160
|
+
|
|
161
|
+
**Props:**
|
|
162
|
+
```typescript
|
|
163
|
+
interface NativeSafeAreaViewProps {
|
|
164
|
+
showBlur?: boolean; // Default: true
|
|
165
|
+
style?: ViewStyle;
|
|
166
|
+
children?: React.ReactNode;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### `<NativeSafeAreaScrollView>`
|
|
171
|
+
|
|
172
|
+
Native scroll view component với automatic safe area insets và blur effect.
|
|
173
|
+
|
|
174
|
+
**Props:**
|
|
175
|
+
```typescript
|
|
176
|
+
interface NativeSafeAreaScrollViewProps {
|
|
177
|
+
showBlur?: boolean; // Default: true
|
|
178
|
+
scrollEnabled?: boolean; // Default: true
|
|
179
|
+
showsVerticalScrollIndicator?: boolean; // Default: true
|
|
180
|
+
showsHorizontalScrollIndicator?: boolean; // Default: false
|
|
181
|
+
style?: ViewStyle;
|
|
182
|
+
children?: React.ReactNode;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Methods
|
|
187
|
+
|
|
188
|
+
### `getSafeAreaInsets()`
|
|
189
|
+
|
|
190
|
+
Trả về Promise với safe area insets hiện tại.
|
|
191
|
+
|
|
192
|
+
**Returns:**
|
|
193
|
+
```typescript
|
|
194
|
+
Promise<{
|
|
195
|
+
top: number;
|
|
196
|
+
bottom: number;
|
|
197
|
+
left: number;
|
|
198
|
+
right: number;
|
|
199
|
+
}>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `addSafeAreaListener(callback)`
|
|
203
|
+
|
|
204
|
+
Đăng ký listener để nhận thông báo khi safe area thay đổi (ví dụ: khi xoay màn hình).
|
|
205
|
+
|
|
206
|
+
**Parameters:**
|
|
207
|
+
- `callback: (insets: SafeAreaInsets) => void` - Hàm được gọi khi safe area thay đổi
|
|
208
|
+
|
|
209
|
+
**Returns:**
|
|
210
|
+
```typescript
|
|
211
|
+
{
|
|
212
|
+
remove: () => void;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Yêu cầu hệ thống
|
|
217
|
+
|
|
218
|
+
- React Native >= 0.60
|
|
219
|
+
- iOS >= 13.0
|
|
220
|
+
- Swift 5.0+
|
|
221
|
+
|
|
222
|
+
## Lưu ý
|
|
223
|
+
|
|
224
|
+
- Thư viện này chỉ hoạt động trên iOS
|
|
225
|
+
- Blur effect sử dụng `UIVisualEffectView` với style `systemMaterial`
|
|
226
|
+
- Blur effect tự động điều chỉnh theo Dark Mode / Light Mode của hệ thống
|
|
227
|
+
- Safe area insets có thể thay đổi khi:
|
|
228
|
+
- Xoay màn hình
|
|
229
|
+
- Hiển thị/ẩn thanh trạng thái
|
|
230
|
+
- Thay đổi layout của ứng dụng
|
|
231
|
+
- `NativeSafeAreaScrollView` tự động set content insets phù hợp với safe area
|
|
232
|
+
|
|
233
|
+
## So sánh với SafeAreaView mặc định
|
|
234
|
+
|
|
235
|
+
| Feature | React Native SafeAreaView | NativeSafeAreaView | NativeSafeAreaScrollView |
|
|
236
|
+
|---------|--------------------------|-------------------|-------------------------|
|
|
237
|
+
| Safe Area Padding | ✅ | ✅ | ✅ |
|
|
238
|
+
| Blur Effect dưới Status Bar | ❌ | ✅ | ✅ |
|
|
239
|
+
| Hoạt động trong ScrollView | ❌ (bị lỗi trên iOS 26) | ✅ | ✅ (built-in scroll) |
|
|
240
|
+
| Auto Content Inset | ❌ | N/A | ✅ |
|
|
241
|
+
| Native Performance | ❌ (JS) | ✅ (Swift) | ✅ (Swift) |
|
|
242
|
+
|
|
243
|
+
## Ví dụ thực tế
|
|
244
|
+
|
|
245
|
+
### Màn hình với Header và Content
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
import { NativeSafeAreaView } from 'native-view-ios-26';
|
|
249
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
250
|
+
|
|
251
|
+
function HomeScreen() {
|
|
252
|
+
return (
|
|
253
|
+
<NativeSafeAreaView style={styles.container}>
|
|
254
|
+
<View style={styles.header}>
|
|
255
|
+
<Text style={styles.title}>My App</Text>
|
|
256
|
+
</View>
|
|
257
|
+
<View style={styles.content}>
|
|
258
|
+
<Text>Main content here</Text>
|
|
259
|
+
</View>
|
|
260
|
+
</NativeSafeAreaView>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const styles = StyleSheet.create({
|
|
265
|
+
container: {
|
|
266
|
+
flex: 1,
|
|
267
|
+
backgroundColor: '#fff',
|
|
268
|
+
},
|
|
269
|
+
header: {
|
|
270
|
+
padding: 16,
|
|
271
|
+
backgroundColor: '#f0f0f0',
|
|
272
|
+
},
|
|
273
|
+
title: {
|
|
274
|
+
fontSize: 24,
|
|
275
|
+
fontWeight: 'bold',
|
|
276
|
+
},
|
|
277
|
+
content: {
|
|
278
|
+
flex: 1,
|
|
279
|
+
padding: 16,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Scroll View với nhiều nội dung
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
import { NativeSafeAreaScrollView } from 'native-view-ios-26';
|
|
288
|
+
import { View, Text, Image, StyleSheet } from 'react-native';
|
|
289
|
+
|
|
290
|
+
function ArticleScreen() {
|
|
291
|
+
return (
|
|
292
|
+
<NativeSafeAreaScrollView
|
|
293
|
+
style={styles.container}
|
|
294
|
+
showBlur={true}
|
|
295
|
+
showsVerticalScrollIndicator={true}
|
|
296
|
+
>
|
|
297
|
+
<Image source={{ uri: 'https://...' }} style={styles.image} />
|
|
298
|
+
<View style={styles.content}>
|
|
299
|
+
<Text style={styles.title}>Article Title</Text>
|
|
300
|
+
<Text style={styles.body}>
|
|
301
|
+
Long article content that scrolls...
|
|
302
|
+
</Text>
|
|
303
|
+
</View>
|
|
304
|
+
</NativeSafeAreaScrollView>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const styles = StyleSheet.create({
|
|
309
|
+
container: {
|
|
310
|
+
flex: 1,
|
|
311
|
+
backgroundColor: '#fff',
|
|
312
|
+
},
|
|
313
|
+
image: {
|
|
314
|
+
width: '100%',
|
|
315
|
+
height: 300,
|
|
316
|
+
},
|
|
317
|
+
content: {
|
|
318
|
+
padding: 16,
|
|
319
|
+
},
|
|
320
|
+
title: {
|
|
321
|
+
fontSize: 28,
|
|
322
|
+
fontWeight: 'bold',
|
|
323
|
+
marginBottom: 16,
|
|
324
|
+
},
|
|
325
|
+
body: {
|
|
326
|
+
fontSize: 16,
|
|
327
|
+
lineHeight: 24,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Lưu ý
|
|
333
|
+
|
|
334
|
+
- Thư viện này chỉ hoạt động trên iOS
|
|
335
|
+
- Safe area insets có thể thay đổi khi:
|
|
336
|
+
- Xoay màn hình
|
|
337
|
+
- Hiển thị/ẩn thanh trạng thái
|
|
338
|
+
- Thay đổi layout của ứng dụng
|
|
339
|
+
|
|
340
|
+
## License
|
|
341
|
+
|
|
342
|
+
MIT
|
|
343
|
+
|
|
344
|
+
## Tác giả
|
|
345
|
+
|
|
346
|
+
Jason Phan
|
|
347
|
+
|
|
348
|
+
## Đóng góp
|
|
349
|
+
|
|
350
|
+
Mọi đóng góp đều được chào đón! Vui lòng tạo pull request hoặc issue trên GitHub.
|
package/components.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { requireNativeComponent, ViewProps, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// Native View Component
|
|
5
|
+
interface NativeSafeAreaViewProps extends ViewProps {
|
|
6
|
+
showBlur?: boolean;
|
|
7
|
+
style?: ViewStyle;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const RNNativeSafeAreaView = requireNativeComponent<NativeSafeAreaViewProps>('NativeSafeAreaView');
|
|
11
|
+
|
|
12
|
+
export const NativeSafeAreaView: React.FC<NativeSafeAreaViewProps> = ({
|
|
13
|
+
showBlur = true,
|
|
14
|
+
children,
|
|
15
|
+
style,
|
|
16
|
+
...props
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<RNNativeSafeAreaView showBlur={showBlur} style={style} {...props}>
|
|
20
|
+
{children}
|
|
21
|
+
</RNNativeSafeAreaView>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Native ScrollView Component
|
|
26
|
+
interface NativeSafeAreaScrollViewProps extends ViewProps {
|
|
27
|
+
showBlur?: boolean;
|
|
28
|
+
scrollEnabled?: boolean;
|
|
29
|
+
showsVerticalScrollIndicator?: boolean;
|
|
30
|
+
showsHorizontalScrollIndicator?: boolean;
|
|
31
|
+
style?: ViewStyle;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const RNNativeSafeAreaScrollView = requireNativeComponent<NativeSafeAreaScrollViewProps>('NativeSafeAreaScrollView');
|
|
35
|
+
|
|
36
|
+
export const NativeSafeAreaScrollView: React.FC<NativeSafeAreaScrollViewProps> = ({
|
|
37
|
+
showBlur = true,
|
|
38
|
+
scrollEnabled = true,
|
|
39
|
+
showsVerticalScrollIndicator = true,
|
|
40
|
+
showsHorizontalScrollIndicator = false,
|
|
41
|
+
children,
|
|
42
|
+
style,
|
|
43
|
+
...props
|
|
44
|
+
}) => {
|
|
45
|
+
return (
|
|
46
|
+
<RNNativeSafeAreaScrollView
|
|
47
|
+
showBlur={showBlur}
|
|
48
|
+
scrollEnabled={scrollEnabled}
|
|
49
|
+
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
50
|
+
showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}
|
|
51
|
+
style={style}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</RNNativeSafeAreaScrollView>
|
|
56
|
+
);
|
|
57
|
+
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
import { ViewProps, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface SafeAreaInsets {
|
|
5
|
+
top: number;
|
|
6
|
+
bottom: number;
|
|
7
|
+
left: number;
|
|
8
|
+
right: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface NativeViewIOS26 {
|
|
12
|
+
/**
|
|
13
|
+
* Get the current safe area insets for the main window
|
|
14
|
+
* @returns Promise resolving to safe area insets
|
|
15
|
+
*/
|
|
16
|
+
getSafeAreaInsets(): Promise<SafeAreaInsets>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Add listener for safe area changes
|
|
20
|
+
* @param callback Function to call when safe area changes
|
|
21
|
+
* @returns Subscription object with remove method
|
|
22
|
+
*/
|
|
23
|
+
addSafeAreaListener(callback: (insets: SafeAreaInsets) => void): {
|
|
24
|
+
remove: () => void;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const NativeViewIOS26: NativeViewIOS26;
|
|
29
|
+
|
|
30
|
+
export default NativeViewIOS26;
|
|
31
|
+
|
|
32
|
+
// Native Components
|
|
33
|
+
export interface NativeSafeAreaViewProps extends ViewProps {
|
|
34
|
+
/**
|
|
35
|
+
* Whether to show blur effect under status bar
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
showBlur?: boolean;
|
|
39
|
+
style?: ViewStyle;
|
|
40
|
+
children?: React.ReactNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface NativeSafeAreaScrollViewProps extends ViewProps {
|
|
44
|
+
/**
|
|
45
|
+
* Whether to show blur effect under status bar
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
showBlur?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether scrolling is enabled
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
scrollEnabled?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Whether to show vertical scroll indicator
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
showsVerticalScrollIndicator?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Whether to show horizontal scroll indicator
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
showsHorizontalScrollIndicator?: boolean;
|
|
64
|
+
style?: ViewStyle;
|
|
65
|
+
children?: React.ReactNode;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const NativeSafeAreaView: ComponentType<NativeSafeAreaViewProps>;
|
|
69
|
+
export const NativeSafeAreaScrollView: ComponentType<NativeSafeAreaScrollViewProps>;
|
|
70
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package 'native-view-ios-26' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
'- You rebuilt the app after installing the package\n' +
|
|
6
|
+
'- You are not using Expo Go\n' +
|
|
7
|
+
'- If you are using Pods, run `pod install` in the ios folder\n';
|
|
8
|
+
|
|
9
|
+
const NativeViewIOS26Module = NativeModules.NativeViewIOS26
|
|
10
|
+
? NativeModules.NativeViewIOS26
|
|
11
|
+
: new Proxy(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get() {
|
|
15
|
+
throw new Error(LINKING_ERROR);
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const eventEmitter = new NativeEventEmitter(NativeViewIOS26Module);
|
|
21
|
+
|
|
22
|
+
const NativeViewIOS26 = {
|
|
23
|
+
/**
|
|
24
|
+
* Get the current safe area insets for the main window
|
|
25
|
+
* @returns Promise resolving to safe area insets
|
|
26
|
+
*/
|
|
27
|
+
getSafeAreaInsets() {
|
|
28
|
+
return NativeViewIOS26Module.getSafeAreaInsets();
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Add listener for safe area changes
|
|
33
|
+
* @param callback Function to call when safe area changes
|
|
34
|
+
* @returns Subscription object with remove method
|
|
35
|
+
*/
|
|
36
|
+
addSafeAreaListener(callback) {
|
|
37
|
+
const subscription = eventEmitter.addListener(
|
|
38
|
+
'onSafeAreaInsetsChange',
|
|
39
|
+
callback
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
remove: () => {
|
|
44
|
+
subscription.remove();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default NativeViewIOS26;
|
|
51
|
+
|
|
52
|
+
// Export components
|
|
53
|
+
export { NativeSafeAreaView, NativeSafeAreaScrollView } from './components.ts';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
@available(iOS 13.0, *)
|
|
5
|
+
class NativeSafeAreaScrollView: UIScrollView {
|
|
6
|
+
|
|
7
|
+
private var blurEffectView: UIVisualEffectView?
|
|
8
|
+
private var showBlur: Bool = true
|
|
9
|
+
private var topBlurHeightConstraint: NSLayoutConstraint?
|
|
10
|
+
|
|
11
|
+
override init(frame: CGRect) {
|
|
12
|
+
super.init(frame: frame)
|
|
13
|
+
setupView()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
required init?(coder: NSCoder) {
|
|
17
|
+
super.init(coder: coder)
|
|
18
|
+
setupView()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private func setupView() {
|
|
22
|
+
backgroundColor = .clear
|
|
23
|
+
|
|
24
|
+
// Enable automatic content inset adjustment
|
|
25
|
+
if #available(iOS 11.0, *) {
|
|
26
|
+
contentInsetAdjustmentBehavior = .never
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Set up content insets for safe area
|
|
30
|
+
updateContentInsets()
|
|
31
|
+
setupBlurEffect()
|
|
32
|
+
|
|
33
|
+
// Listen for safe area changes
|
|
34
|
+
NotificationCenter.default.addObserver(
|
|
35
|
+
self,
|
|
36
|
+
selector: #selector(updateInsets),
|
|
37
|
+
name: UIDevice.orientationDidChangeNotification,
|
|
38
|
+
object: nil
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func setupBlurEffect() {
|
|
43
|
+
guard showBlur else { return }
|
|
44
|
+
|
|
45
|
+
// Create blur effect for status bar area
|
|
46
|
+
let blurEffect = UIBlurEffect(style: .systemMaterial)
|
|
47
|
+
let blurView = UIVisualEffectView(effect: blurEffect)
|
|
48
|
+
blurView.translatesAutoresizingMaskIntoConstraints = false
|
|
49
|
+
blurView.alpha = 0.95
|
|
50
|
+
blurView.isUserInteractionEnabled = false
|
|
51
|
+
|
|
52
|
+
// Add blur to the scroll view's parent or self
|
|
53
|
+
if let superview = self.superview {
|
|
54
|
+
superview.insertSubview(blurView, aboveSubview: self)
|
|
55
|
+
|
|
56
|
+
topBlurHeightConstraint = blurView.heightAnchor.constraint(equalToConstant: safeAreaInsets.top)
|
|
57
|
+
|
|
58
|
+
NSLayoutConstraint.activate([
|
|
59
|
+
blurView.topAnchor.constraint(equalTo: superview.topAnchor),
|
|
60
|
+
blurView.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
|
|
61
|
+
blurView.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
|
|
62
|
+
topBlurHeightConstraint!
|
|
63
|
+
])
|
|
64
|
+
} else {
|
|
65
|
+
// Fallback: add as subview
|
|
66
|
+
addSubview(blurView)
|
|
67
|
+
|
|
68
|
+
topBlurHeightConstraint = blurView.heightAnchor.constraint(equalToConstant: safeAreaInsets.top)
|
|
69
|
+
|
|
70
|
+
NSLayoutConstraint.activate([
|
|
71
|
+
blurView.topAnchor.constraint(equalTo: topAnchor),
|
|
72
|
+
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
73
|
+
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
74
|
+
topBlurHeightConstraint!
|
|
75
|
+
])
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
blurEffectView = blurView
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override func didMoveToSuperview() {
|
|
82
|
+
super.didMoveToSuperview()
|
|
83
|
+
|
|
84
|
+
// Re-setup blur if needed when added to view hierarchy
|
|
85
|
+
if superview != nil && showBlur && blurEffectView?.superview == nil {
|
|
86
|
+
blurEffectView?.removeFromSuperview()
|
|
87
|
+
blurEffectView = nil
|
|
88
|
+
setupBlurEffect()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private func updateContentInsets() {
|
|
93
|
+
if #available(iOS 11.0, *) {
|
|
94
|
+
let insets = safeAreaInsets
|
|
95
|
+
contentInset = UIEdgeInsets(
|
|
96
|
+
top: insets.top,
|
|
97
|
+
left: insets.left,
|
|
98
|
+
bottom: insets.bottom,
|
|
99
|
+
right: insets.right
|
|
100
|
+
)
|
|
101
|
+
scrollIndicatorInsets = contentInset
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc private func updateInsets() {
|
|
106
|
+
updateContentInsets()
|
|
107
|
+
|
|
108
|
+
// Update blur effect height
|
|
109
|
+
if let constraint = topBlurHeightConstraint {
|
|
110
|
+
constraint.constant = safeAreaInsets.top
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setNeedsLayout()
|
|
114
|
+
layoutIfNeeded()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override func safeAreaInsetsDidChange() {
|
|
118
|
+
super.safeAreaInsetsDidChange()
|
|
119
|
+
updateInsets()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func setShowBlur(_ show: Bool) {
|
|
123
|
+
guard showBlur != show else { return }
|
|
124
|
+
showBlur = show
|
|
125
|
+
|
|
126
|
+
if show {
|
|
127
|
+
setupBlurEffect()
|
|
128
|
+
} else {
|
|
129
|
+
blurEffectView?.removeFromSuperview()
|
|
130
|
+
blurEffectView = nil
|
|
131
|
+
topBlurHeightConstraint = nil
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func setScrollEnabled(_ enabled: Bool) {
|
|
136
|
+
isScrollEnabled = enabled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func setShowsVerticalScrollIndicator(_ shows: Bool) {
|
|
140
|
+
showsVerticalScrollIndicator = shows
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func setShowsHorizontalScrollIndicator(_ shows: Bool) {
|
|
144
|
+
showsHorizontalScrollIndicator = shows
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
deinit {
|
|
148
|
+
NotificationCenter.default.removeObserver(self)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
@objc(NativeSafeAreaScrollViewManager)
|
|
6
|
+
class NativeSafeAreaScrollViewManager: RCTViewManager {
|
|
7
|
+
|
|
8
|
+
override func view() -> UIView! {
|
|
9
|
+
return NativeSafeAreaScrollView()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@objc func setShowBlur(_ view: NativeSafeAreaScrollView, showBlur: Bool) {
|
|
17
|
+
view.setShowBlur(showBlur)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@objc func setScrollEnabled(_ view: NativeSafeAreaScrollView, scrollEnabled: Bool) {
|
|
21
|
+
view.setScrollEnabled(scrollEnabled)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@objc func setShowsVerticalScrollIndicator(_ view: NativeSafeAreaScrollView, shows: Bool) {
|
|
25
|
+
view.setShowsVerticalScrollIndicator(shows)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@objc func setShowsHorizontalScrollIndicator(_ view: NativeSafeAreaScrollView, shows: Bool) {
|
|
29
|
+
view.setShowsHorizontalScrollIndicator(shows)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
@available(iOS 13.0, *)
|
|
5
|
+
class NativeSafeAreaView: UIView {
|
|
6
|
+
|
|
7
|
+
private var blurEffectView: UIVisualEffectView?
|
|
8
|
+
private var contentView: UIView!
|
|
9
|
+
private var showBlur: Bool = true
|
|
10
|
+
|
|
11
|
+
override init(frame: CGRect) {
|
|
12
|
+
super.init(frame: frame)
|
|
13
|
+
setupView()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
required init?(coder: NSCoder) {
|
|
17
|
+
super.init(coder: coder)
|
|
18
|
+
setupView()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private func setupView() {
|
|
22
|
+
backgroundColor = .clear
|
|
23
|
+
|
|
24
|
+
// Create content view
|
|
25
|
+
contentView = UIView()
|
|
26
|
+
contentView.backgroundColor = .clear
|
|
27
|
+
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
28
|
+
addSubview(contentView)
|
|
29
|
+
|
|
30
|
+
setupBlurEffect()
|
|
31
|
+
updateConstraints()
|
|
32
|
+
|
|
33
|
+
// Listen for safe area changes
|
|
34
|
+
if #available(iOS 11.0, *) {
|
|
35
|
+
NotificationCenter.default.addObserver(
|
|
36
|
+
self,
|
|
37
|
+
selector: #selector(updateInsets),
|
|
38
|
+
name: UIDevice.orientationDidChangeNotification,
|
|
39
|
+
object: nil
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func setupBlurEffect() {
|
|
45
|
+
guard showBlur else { return }
|
|
46
|
+
|
|
47
|
+
// Create blur effect for status bar area
|
|
48
|
+
let blurEffect = UIBlurEffect(style: .systemMaterial)
|
|
49
|
+
let blurView = UIVisualEffectView(effect: blurEffect)
|
|
50
|
+
blurView.translatesAutoresizingMaskIntoConstraints = false
|
|
51
|
+
blurView.alpha = 0.95
|
|
52
|
+
|
|
53
|
+
insertSubview(blurView, at: 0)
|
|
54
|
+
blurEffectView = blurView
|
|
55
|
+
|
|
56
|
+
// Pin blur to top
|
|
57
|
+
NSLayoutConstraint.activate([
|
|
58
|
+
blurView.topAnchor.constraint(equalTo: topAnchor),
|
|
59
|
+
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
60
|
+
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
61
|
+
blurView.heightAnchor.constraint(equalToConstant: safeAreaInsets.top)
|
|
62
|
+
])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private func updateConstraints() {
|
|
66
|
+
NSLayoutConstraint.activate([
|
|
67
|
+
contentView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
|
|
68
|
+
contentView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
|
|
69
|
+
contentView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
|
|
70
|
+
contentView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
|
|
71
|
+
])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc private func updateInsets() {
|
|
75
|
+
setNeedsLayout()
|
|
76
|
+
layoutIfNeeded()
|
|
77
|
+
|
|
78
|
+
// Update blur effect height
|
|
79
|
+
if let blurView = blurEffectView {
|
|
80
|
+
blurView.constraints.forEach { constraint in
|
|
81
|
+
if constraint.firstAttribute == .height {
|
|
82
|
+
constraint.constant = safeAreaInsets.top
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override func safeAreaInsetsDidChange() {
|
|
89
|
+
super.safeAreaInsetsDidChange()
|
|
90
|
+
updateInsets()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func setShowBlur(_ show: Bool) {
|
|
94
|
+
guard showBlur != show else { return }
|
|
95
|
+
showBlur = show
|
|
96
|
+
|
|
97
|
+
if show {
|
|
98
|
+
setupBlurEffect()
|
|
99
|
+
} else {
|
|
100
|
+
blurEffectView?.removeFromSuperview()
|
|
101
|
+
blurEffectView = nil
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Override to add subviews to contentView instead
|
|
106
|
+
override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
|
|
107
|
+
contentView.insertSubview(subview, at: atIndex)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override func removeReactSubview(_ subview: UIView!) {
|
|
111
|
+
subview.removeFromSuperview()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
deinit {
|
|
115
|
+
NotificationCenter.default.removeObserver(self)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import <React/RCTViewManager.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(NativeSafeAreaViewManager, RCTViewManager)
|
|
4
|
+
|
|
5
|
+
RCT_EXPORT_VIEW_PROPERTY(showBlur, BOOL)
|
|
6
|
+
|
|
7
|
+
@end
|
|
8
|
+
|
|
9
|
+
@interface RCT_EXTERN_MODULE(NativeSafeAreaScrollViewManager, RCTViewManager)
|
|
10
|
+
|
|
11
|
+
RCT_EXPORT_VIEW_PROPERTY(showBlur, BOOL)
|
|
12
|
+
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
|
13
|
+
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
|
14
|
+
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
@objc(NativeSafeAreaViewManager)
|
|
6
|
+
class NativeSafeAreaViewManager: RCTViewManager {
|
|
7
|
+
|
|
8
|
+
override func view() -> UIView! {
|
|
9
|
+
return NativeSafeAreaView()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@objc func setShowBlur(_ view: NativeSafeAreaView, showBlur: Bool) {
|
|
17
|
+
view.setShowBlur(showBlur)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(NativeViewIOS26, RCTEventEmitter)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(getSafeAreaInsets:(RCTPromiseResolveBlock)resolve
|
|
7
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
+ (BOOL)requiresMainQueueSetup
|
|
10
|
+
{
|
|
11
|
+
return YES;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
@objc(NativeViewIOS26)
|
|
6
|
+
class NativeViewIOS26: RCTEventEmitter {
|
|
7
|
+
|
|
8
|
+
private var hasListeners = false
|
|
9
|
+
private var observers: [NSObjectProtocol] = []
|
|
10
|
+
|
|
11
|
+
override init() {
|
|
12
|
+
super.init()
|
|
13
|
+
setupObservers()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
deinit {
|
|
17
|
+
removeObservers()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - RCTEventEmitter
|
|
21
|
+
|
|
22
|
+
override func supportedEvents() -> [String]! {
|
|
23
|
+
return ["onSafeAreaInsetsChange"]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override func startObserving() {
|
|
27
|
+
hasListeners = true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override func stopObserving() {
|
|
31
|
+
hasListeners = false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Setup Observers
|
|
39
|
+
|
|
40
|
+
private func setupObservers() {
|
|
41
|
+
let notificationNames: [NSNotification.Name] = [
|
|
42
|
+
UIDevice.orientationDidChangeNotification,
|
|
43
|
+
UIApplication.didBecomeActiveNotification
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
for name in notificationNames {
|
|
47
|
+
let observer = NotificationCenter.default.addObserver(
|
|
48
|
+
forName: name,
|
|
49
|
+
object: nil,
|
|
50
|
+
queue: .main
|
|
51
|
+
) { [weak self] _ in
|
|
52
|
+
self?.notifySafeAreaChange()
|
|
53
|
+
}
|
|
54
|
+
observers.append(observer)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private func removeObservers() {
|
|
59
|
+
observers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
60
|
+
observers.removeAll()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// MARK: - Public Methods
|
|
64
|
+
|
|
65
|
+
@objc
|
|
66
|
+
func getSafeAreaInsets(
|
|
67
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
68
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
69
|
+
) {
|
|
70
|
+
DispatchQueue.main.async { [weak self] in
|
|
71
|
+
guard let self = self else {
|
|
72
|
+
reject("ERROR", "Module deallocated", nil)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if let insets = self.getCurrentSafeAreaInsets() {
|
|
77
|
+
resolve(insets)
|
|
78
|
+
} else {
|
|
79
|
+
reject("ERROR", "Failed to get safe area insets", nil)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// MARK: - Helper Methods
|
|
85
|
+
|
|
86
|
+
private func getCurrentSafeAreaInsets() -> [String: CGFloat]? {
|
|
87
|
+
guard let window = getKeyWindow() else {
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let safeAreaInsets = window.safeAreaInsets
|
|
92
|
+
|
|
93
|
+
return [
|
|
94
|
+
"top": safeAreaInsets.top,
|
|
95
|
+
"bottom": safeAreaInsets.bottom,
|
|
96
|
+
"left": safeAreaInsets.left,
|
|
97
|
+
"right": safeAreaInsets.right
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private func getKeyWindow() -> UIWindow? {
|
|
102
|
+
// iOS 13+ compatible way to get key window
|
|
103
|
+
if #available(iOS 13.0, *) {
|
|
104
|
+
return UIApplication.shared.connectedScenes
|
|
105
|
+
.compactMap { $0 as? UIWindowScene }
|
|
106
|
+
.flatMap { $0.windows }
|
|
107
|
+
.first { $0.isKeyWindow }
|
|
108
|
+
} else {
|
|
109
|
+
return UIApplication.shared.keyWindow
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func notifySafeAreaChange() {
|
|
114
|
+
guard hasListeners else { return }
|
|
115
|
+
|
|
116
|
+
if let insets = getCurrentSafeAreaInsets() {
|
|
117
|
+
sendEvent(withName: "onSafeAreaInsetsChange", body: insets)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "native-view-ios-26",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Native iOS Safe Area module for React Native with Swift bridge",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"react-native",
|
|
12
|
+
"ios",
|
|
13
|
+
"safe-area",
|
|
14
|
+
"insets",
|
|
15
|
+
"swift",
|
|
16
|
+
"native-module",
|
|
17
|
+
"blur-effect"
|
|
18
|
+
],
|
|
19
|
+
"author": "Jason Phan",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": "*",
|
|
23
|
+
"react-native": "*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"react": "^18.2.0",
|
|
27
|
+
"react-native": "^0.73.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/JasonP205/native-view-ios-26.git"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"ios",
|
|
35
|
+
"index.js",
|
|
36
|
+
"index.d.ts",
|
|
37
|
+
"components.ts",
|
|
38
|
+
"!**/__tests__",
|
|
39
|
+
"!**/__fixtures__",
|
|
40
|
+
"!**/__mocks__"
|
|
41
|
+
],
|
|
42
|
+
"react-native": "index.js"
|
|
43
|
+
}
|