Skip to content

Commit 3c9b299

Browse files
committed
docs(react-native): add native UI guidance
1 parent 3181b94 commit 3c9b299

1 file changed

Lines changed: 91 additions & 21 deletions

File tree

packages/react-native/README.md

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ run through the NativeScript UI dispatcher, so UIKit calls are safe and use the
7575
same 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

8181
NativeScript.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

114116
Forward 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
144146
export 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
183185
NativeScript.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

212214
scrollView.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
255257
or 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+
313380
For modal UIKit controllers, find the top visible presenter and guard against
314381
double 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
344413
PassKit.
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`.
466536
4. 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

Comments
 (0)