Customization
How to layer your own RN UI on top of CameraView to build guide, button, and result screens.
Overview
<CameraView> renders the camera preview and the SDK detection overlay (nose/face markers, progress ring) natively. On top of it, you build the app UI — such as guide messages, buttons, progress, and result screens — yourself by overlaying ordinary RN views with absolute positioning. The UI is driven by the onDetectionStatus / onDetectionProgress / onDetectionFinished events and the camera commands.
The detection markers (nose/face) are drawn by the SDK itself. In addition, the detection rect (nose/face normalized box) is exposed to JS via onDetectionResult, so you can also build custom guides in RN that follow the marker positions (example below).
Basic Pattern
Lay <CameraView> over the screen and overlay an overlay container on top of it. Use pointerEvents="box-none" so that touches pass through to the camera area as well.
import { View, StyleSheet } from 'react-native';
<View style={{ flex: 1 }}>
<CameraView
camera={camera}
species="DOG"
purpose="PET_PROFILE_REGISTRATION"
captureSessionId={captureSessionId}
style={StyleSheet.absoluteFill}
onDetectionStatus={setStatus}
onDetectionProgress={setProgress}
onDetectionFinished={onFinished}
/>
{/* App UI overlay */}
<SafeAreaView style={StyleSheet.absoluteFill} pointerEvents="box-none">
{/* ...guide / buttons / progress... */}
</SafeAreaView>
</View>Example: Top Buttons (Switch / Close)
<View style={styles.topRow} pointerEvents="box-none">
<Pressable onPress={() => camera.switchCamera()}>
<Text style={styles.btn}>Switch</Text>
</Pressable>
<Pressable onPress={onClose}>
<Text style={styles.btn}>Close</Text>
</Pressable>
</View>Example: Status-Based Guide Messages
Receive onDetectionStatus as state and render user guidance text.
const [status, setStatus] = useState<DetectionStatus | null>(null);
function guideMessage(s: DetectionStatus | null): string {
if (!s) return 'Preparing camera...';
switch (s.type) {
case 'noObject': return 'Center your pet on the screen';
case 'processing': return 'Recognizing...';
case 'detected': return 'Great! Hold still';
case 'finished': return 'Capture complete!';
case 'failed': return `Try again: ${s.reason}`;
}
}
<Text style={styles.guideCapsule}>{guideMessage(status)}</Text>Example: Progress
const [progress, setProgress] = useState(0); // onDetectionProgress (0~100)
<View style={styles.progressTrack}>
<View style={[styles.progressFill, { width: `${progress}%` }]} />
</View>Example: Result Screen and Retake
When you receive completion via onDetectionFinished, show the result/upload UI, and restart with camera.startDetection() on "Retake".
const [result, setResult] = useState<CameraResult | null>(null);
const onFinished = useCallback((r: CameraResult) => {
setResult(r);
upload(r); // upload the file:// URIs to the app server
}, []);
const retake = useCallback(() => {
setResult(null);
setStatus(null);
setProgress(0);
camera.startDetection();
}, [camera]);
{result && (
<View style={styles.resultSheet}>
<Text>Capture complete — {result.fingerprintImages.length} fingerprint images</Text>
<Pressable onPress={retake}><Text>Retake</Text></Pressable>
</View>
)}Example: Detection Rect Tracking Overlay
With onDetectionResult, you receive the nose/face box (normalized 0–1, top-left origin), and you can draw a custom marker that follows that position yourself. Obtain the view size with onLayout to convert the normalized coordinates to pixels.
import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import type { DetectionResult } from '@petnow/react-native-camera-ui';
const [rect, setRect] = useState<DetectionResult['nose']>(null);
const [size, setSize] = useState({ w: 0, h: 0 });
<View
style={{ flex: 1 }}
onLayout={(e) =>
setSize({ w: e.nativeEvent.layout.width, h: e.nativeEvent.layout.height })
}
>
<CameraView
camera={camera}
species="DOG"
purpose="PET_PROFILE_REGISTRATION"
captureSessionId={captureSessionId}
style={StyleSheet.absoluteFill}
onDetectionResult={(r: DetectionResult) => setRect(r.nose ?? r.face)}
/>
{rect && (
<View
pointerEvents="none"
style={{
position: 'absolute',
left: rect.x * size.w,
top: rect.y * size.h,
width: rect.width * size.w,
height: rect.height * size.h,
borderWidth: 2,
borderColor: '#FF592C',
borderRadius: 8,
}}
/>
)}
</View>nose/faceare each{ x, y, width, height }(normalized 0–1) ornull. They can benullon frames with no detection, so guard for it.- Since it is drawn separately from the SDK's built-in markers, you can omit this overlay if the built-in markers are sufficient.
Tips
- Use
pointerEvents="box-none"on the overlay container and areas that don't need taps, and keep default behavior only on interactive elements like buttons. - If you need to pause (e.g., showing a sheet/popup), you can call
camera.pauseDetection()and then resume withcamera.resumeDetection()while preserving progress. - Feel free to change the guide text and failure-reason mapping to match your app's tone.
Next Steps
- Basic Usage — hook, event, and command reference