Petnow LogoPetnow
React Native SDK

커스터마이징

CameraView 위에 자체 RN UI를 올려 가이드·버튼·결과 화면을 구성하는 방법.


이 가이드를 시작하기 전에 기본 사용법을 완료하세요.

개요

<CameraView>는 카메라 프리뷰와 SDK 탐지 오버레이(코/얼굴 마커·진행 링)를 네이티브로 렌더합니다. 그 위에 가이드 메시지, 버튼, 진행률, 결과 화면 같은 앱 UI는 일반 RN 뷰를 absolute로 겹쳐서 직접 구성합니다. UI는 onDetectionStatus / onDetectionProgress / onDetectionFinished 이벤트와 camera 커맨드로 구동합니다.

탐지 마커(코/얼굴)는 SDK가 직접 그립니다. 추가로 검출 rect(코/얼굴 normalized box)가 onDetectionResult로 JS에 노출되므로, 마커 위치를 따라다니는 커스텀 가이드도 RN에서 직접 만들 수 있습니다(아래 예시).

기본 패턴

<CameraView>를 화면에 깔고, 그 위에 오버레이 컨테이너를 겹칩니다. 터치가 카메라 영역으로도 지나가도록 pointerEvents="box-none"을 씁니다.

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

  {/* 앱 UI 오버레이 */}
  <SafeAreaView style={StyleSheet.absoluteFill} pointerEvents="box-none">
    {/* ...가이드 / 버튼 / 진행률... */}
  </SafeAreaView>
</View>

예제: 상단 버튼 (전환 / 닫기)

<View style={styles.topRow} pointerEvents="box-none">
  <Pressable onPress={() => camera.switchCamera()}>
    <Text style={styles.btn}>전환</Text>
  </Pressable>
  <Pressable onPress={onClose}>
    <Text style={styles.btn}>닫기</Text>
  </Pressable>
</View>

예제: 상태별 가이드 메시지

onDetectionStatus를 상태로 받아 사용자 안내 문구를 그립니다.

const [status, setStatus] = useState<DetectionStatus | null>(null);

function guideMessage(s: DetectionStatus | null): string {
  if (!s) return '카메라 준비 중...';
  switch (s.type) {
    case 'noObject':   return '반려동물을 화면 중앙에 맞춰주세요';
    case 'processing': return '인식 중...';
    case 'detected':   return '좋아요! 그대로 유지하세요';
    case 'finished':   return '촬영 완료!';
    case 'failed':     return `다시 시도: ${s.reason}`;
  }
}

<Text style={styles.guideCapsule}>{guideMessage(status)}</Text>

예제: 진행률

const [progress, setProgress] = useState(0); // onDetectionProgress (0~100)

<View style={styles.progressTrack}>
  <View style={[styles.progressFill, { width: `${progress}%` }]} />
</View>

예제: 결과 화면과 재촬영

onDetectionFinished로 완료를 받으면 결과/업로드 UI를 띄우고, "다시 찍기"에서 camera.startDetection()으로 재시작합니다.

const [result, setResult] = useState<CameraResult | null>(null);

const onFinished = useCallback((r: CameraResult) => {
  setResult(r);
  upload(r); // file:// URI들을 앱 서버로 업로드
}, []);

const retake = useCallback(() => {
  setResult(null);
  setStatus(null);
  setProgress(0);
  camera.startDetection();
}, [camera]);

{result && (
  <View style={styles.resultSheet}>
    <Text>촬영 완료 — 지문 {result.fingerprintImages.length}장</Text>
    <Pressable onPress={retake}><Text>다시 찍기</Text></Pressable>
  </View>
)}

예제: 검출 rect 추적 오버레이

onDetectionResult로 코/얼굴 박스(normalized 0–1, 좌상단 원점)를 받아, 그 위치를 따라다니는 커스텀 마커를 직접 그릴 수 있습니다. 뷰 크기는 onLayout으로 얻어 normalized 좌표를 픽셀로 변환합니다.

import { useState } from 'react';
import { View, StyleSheet } from 'react-native';
import type { DetectionResult } from '@petify/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/face는 각각 { x, y, width, height }(normalized 0–1) 또는 null입니다. 미검출 프레임에서는 null이 될 수 있으니 가드하세요.
  • SDK 내장 마커와 별개로 그려지므로, 내장 마커가 충분하면 이 오버레이는 생략해도 됩니다.

  • 오버레이 컨테이너와 탭이 필요 없는 영역에는 pointerEvents="box-none"을, 버튼 같은 인터랙션 요소에만 기본 동작을 두세요.
  • 일시정지가 필요하면(예: 시트/팝업 표시) camera.pauseDetection()camera.resumeDetection()으로 진행률을 보존하며 재개할 수 있습니다.
  • 가이드 문구·실패 사유 매핑은 앱 톤에 맞게 자유롭게 바꾸세요.

다음 단계

On this page