@@ -75,8 +75,8 @@ run through the NativeScript UI dispatcher, so UIKit calls are safe and use the
7575same globals and iOS SDK types as NativeScript.
7676
7777``` tsx
78- import NativeScript , {defineUIKitView } from " @nativescript/react-native" ;
79- import type {UIKitViewRef } from " @nativescript/react-native" ;
78+ import NativeScript , { defineUIKitView } from " @nativescript/react-native" ;
79+ import type { UIKitViewRef } from " @nativescript/react-native" ;
8080
8181NativeScript .init ();
8282
@@ -100,15 +100,17 @@ export const NativeBadge = defineUIKitView<BadgeProps, UIView>({
100100 },
101101 update(view , props ) {
102102 view .backgroundColor =
103- props .tone === " green" ? UIColor .systemGreenColor : UIColor .systemBlueColor ;
103+ props .tone === " green"
104+ ? UIColor .systemGreenColor
105+ : UIColor .systemBlueColor ;
104106 view .layer .cornerRadius = 12 ;
105107 view .clipsToBounds = true ;
106108 const label = view .viewWithTag (1 ) as UILabel ;
107109 label .text = props .title ;
108110 },
109111});
110112
111- <NativeBadge title = " UIKit from JS" tone = " blue" style = { {height: 48 }} />;
113+ <NativeBadge title = " UIKit from JS" tone = " blue" style = { { height: 48 }} />;
112114```
113115
114116Forward a ref when you need imperative access:
@@ -142,11 +144,11 @@ The first argument to `create` is also the current props object, so existing
142144
143145``` tsx
144146export const NativeSwitch = NativeScript .defineUIKitView <
145- {value : boolean ; onValueChange ?: (value : boolean ) => void },
147+ { value : boolean ; onValueChange ?: (value : boolean ) => void },
146148 UISwitch
147149> ({
148150 name: " NativeSwitch" ,
149- layout: {sizing: " intrinsic" },
151+ layout: { sizing: " intrinsic" },
150152 create(ctx ) {
151153 const view = UISwitch .new ();
152154 ctx .targetAction (view , UIControlEvents .ValueChanged , () => {
@@ -182,7 +184,7 @@ property setters still win first, and unsupported names fall back to JS state:
182184``` ts
183185NativeScript .runOnUI (() => {
184186 const view = UIView .new ();
185- view .ownerState = {selected: false };
187+ view .ownerState = { selected: false };
186188 view .tag = 42 ; // still calls UIKit's native tag setter
187189});
188190```
@@ -206,7 +208,7 @@ const delegate = NativeScript.createDelegate<UIScrollViewDelegate>(
206208 });
207209 },
208210 },
209- {retainer },
211+ { retainer },
210212);
211213
212214scrollView .delegate = delegate ;
@@ -233,7 +235,7 @@ const dataSource = NativeScript.createDelegate(
233235 return NSURL .fileURLWithPath (path );
234236 },
235237 },
236- {owner: ctx },
238+ { owner: ctx },
237239);
238240```
239241
@@ -255,11 +257,11 @@ Use `defaultSize`, `minSize`, and `maxSize` when a native view can report zero
255257or needs bounds during the first layout pass.
256258
257259``` tsx
258- const NativeTitle = NativeScript .defineUIKitView <{text: string }, UILabel >({
260+ const NativeTitle = NativeScript .defineUIKitView <{ text: string }, UILabel >({
259261 name: " NativeTitle" ,
260262 layout: {
261263 sizing: " intrinsic" ,
262- defaultSize: {width: 1 , height: 1 },
264+ defaultSize: { width: 1 , height: 1 },
263265 },
264266 create() {
265267 return UILabel .new ();
@@ -290,7 +292,7 @@ export const BlurCard = NativeScript.defineUIKitContainer({
290292 },
291293});
292294
293- <BlurCard style = { {padding: 16 }} >
295+ <BlurCard style = { { padding: 16 }} >
294296 <Text >React Native child content</Text >
295297</BlurCard >;
296298```
@@ -310,11 +312,78 @@ export const NativePageHost = NativeScript.defineUIViewController({
310312});
311313```
312314
315+ ### Building app-specific native UI
316+
317+ This package is intentionally low-level. It installs NativeScript's Native API
318+ inside React Native and gives you lifecycle helpers; it does not ship opinionated
319+ wrappers for tabs, maps, cameras, pickers, or other app components. Build those
320+ as local components in your app or library:
321+
322+ - Use ` defineUIKitView() ` for one native ` UIView ` .
323+ - Use ` defineUIKitContainer() ` when React Native children should mount inside a
324+ native ` UIView ` .
325+ - Use ` defineUIViewController() ` when UIKit expects view-controller containment,
326+ such as tabs, navigation controllers, split views, document browsers, preview
327+ controllers, and presentation flows.
328+ - Use ` ctx.delegate() ` , ` ctx.targetAction() ` , ` ctx.retain() ` , and
329+ ` ctx.dispose() ` for native callbacks and weakly-held helper objects.
330+ - Use ` NativeScript.isClassAvailable() ` before touching SDK-new APIs.
331+
332+ For example, build native tabs with ` UITabBarController ` instead of measuring a
333+ standalone ` UITabBar ` as a leaf RN view:
334+
335+ ``` tsx
336+ type NativeTabsProps = {
337+ selectedIndex: number ;
338+ onSelectedIndexChange? : (index : number ) => void ;
339+ };
340+
341+ export const NativeTabs = NativeScript .defineUIViewController <
342+ NativeTabsProps ,
343+ UITabBarController
344+ > ({
345+ name: " NativeTabs" ,
346+ createController(ctx ) {
347+ const controller = UITabBarController .new ();
348+ const viewControllers = TAB_ITEMS .map ((item , index ) => {
349+ const child = UIViewController .new ();
350+ child .view .backgroundColor = UIColor .systemBackgroundColor ;
351+ child .tabBarItem = UITabBarItem .alloc ().initWithTitleImageSelectedImage (
352+ item .title ,
353+ UIImage .systemImageNamed (item .symbol ),
354+ UIImage .systemImageNamed (item .selectedSymbol ),
355+ );
356+ child .tabBarItem .tag = index ;
357+ return child ;
358+ });
359+
360+ controller .viewControllers = NSArray .arrayWithArray (viewControllers );
361+ ctx .delegate (controller , UITabBarControllerDelegate , {
362+ tabBarControllerDidSelectViewController(tabBarController ) {
363+ ctx .emit (" onSelectedIndexChange" , tabBarController .selectedIndex );
364+ },
365+ });
366+ return controller ;
367+ },
368+ update(controller , props ) {
369+ controller .selectedIndex = props .selectedIndex ;
370+ },
371+ });
372+
373+ <NativeTabs
374+ selectedIndex = { selectedIndex }
375+ onSelectedIndexChange = { setSelectedIndex }
376+ style = { { flex: 1 }}
377+ />;
378+ ```
379+
313380For modal UIKit controllers, find the top visible presenter and guard against
314381double presentation:
315382
316383``` ts
317- function topVisibleViewController(root = UIApplication .sharedApplication .keyWindow ?.rootViewController ) {
384+ function topVisibleViewController(
385+ root = UIApplication .sharedApplication .keyWindow ?.rootViewController ,
386+ ) {
318387 let current = root ;
319388 while (current ?.presentedViewController ) {
320389 current = current .presentedViewController ;
@@ -344,12 +413,13 @@ device availability can differ for frameworks such as VisionKit, QuickLook, and
344413PassKit.
345414
346415``` ts
347- if (NativeScript .loadFramework (" VisionKit" ) &&
348- NativeScript .isClassAvailable (" VNDocumentCameraViewController" )) {
349- const CameraController =
350- NativeScript .getClass <typeof VNDocumentCameraViewController >(
351- " VNDocumentCameraViewController" ,
352- );
416+ if (
417+ NativeScript .loadFramework (" VisionKit" ) &&
418+ NativeScript .isClassAvailable (" VNDocumentCameraViewController" )
419+ ) {
420+ const CameraController = NativeScript .getClass <
421+ typeof VNDocumentCameraViewController
422+ > (" VNDocumentCameraViewController" );
353423 const controller = CameraController ?.new ();
354424}
355425```
@@ -466,11 +536,11 @@ Expo development build, EAS Build, or `npx expo run:ios`.
4665364 . Initialize NativeScript in app code before using native APIs:
467537
468538 ``` tsx
469- import NativeScript , {defineUIKitView } from " @nativescript/react-native" ;
539+ import NativeScript , { defineUIKitView } from " @nativescript/react-native" ;
470540
471541 NativeScript .init ();
472542
473- const NativeBadge = defineUIKitView <{title: string }, UIView >({
543+ const NativeBadge = defineUIKitView <{ title: string }, UIView >({
474544 name: " NativeBadge" ,
475545 create() {
476546 const view = UIView .alloc ().initWithFrame (CGRectZero );
0 commit comments