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 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
+ }