expo-camera
Escaneo de códigos de barras, linterna y límites de rendimiento de vista previa - el manual de expo-camera para Expo SDK 57 en pantallas de escaneo, auditoría y captura de campo.
Busca en todas las páginas de la documentación
Escaneo de códigos de barras, linterna y límites de rendimiento de vista previa - el manual de expo-camera para Expo SDK 57 en pantallas de escaneo, auditoría y captura de campo.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
npx expo install expo-camera// app.config.ts
export default {
plugins: [
[
"expo-camera",
{
cameraPermission:
"Permitir que FieldPro fotografíe las etiquetas del equipo para órdenes de trabajo.",
microphonePermission:
"Permitir que FieldPro grabe audio con videos de inspección.",
recordAudioAndroid: true,
barcodeScannerEnabled: true,
},
],
],
};import { CameraView, useCameraPermissions } from "expo-camera";
import { useCallback, useRef, useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
export function QrScanner({ onScan }: { onScan: (data: string) => void }) {
const [permission, requestPermission] = useCameraPermissions();
const [torch, setTorch] = useState(false);
const last = useRef(0);
const handleBarcode = useCallback(
({ data }: { data: string }) => {
const now = Date.now();
if (now - last.current < 1200) return;
last.current = now;
onScan(data);
},
[onScan]
);
if (!permission?.granted) {
return (
<View style={styles.gate}>
<Text>Escanea las etiquetas de activos para abrir la orden de trabajo correcta.</Text>
<Pressable onPress={requestPermission}>
<Text style={styles.link}>Permitir cámara</Text>
</Pressable>
</View>
);
}
return (
<View style={styles.root}>
<CameraView
style={styles.camera}
facing="back"
enableTorch={torch}
barcodeScannerSettings={{ barcodeTypes: ["qr", "code128"] }}
onBarcodeScanned={handleBarcode}
/>
<Pressable style={styles.torch} onPress={() => setTorch((t) => !t)}>
<Text style={styles.torchText}>{torch ? "Linterna encendida" : "Linterna apagada"}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
root: { flex: 1 },
camera: { flex: 1 },
gate: { flex: 1, padding: 24, gap: 12, justifyContent: "center" },
link: { color: "#2563eb" },
torch: { position: "absolute", top: 56, right: 16, padding: 10, backgroundColor: "rgba(0,0,0,0.5)", borderRadius: 8 },
torchText: { color: "#fff", fontWeight: "600" },
});Cuándo usarlo:
Cuándo evitarlo:
expo-image-picker launchCameraAsync es menos código.expo-image-picker en su lugar.Flujos de captura: Para reserva de biblioteca, puertas de permisos y previsualizaciones seguras para la memoria, consulta expo-camera y expo-image-picker.
Pantalla de escaneo con manejo de códigos de barras sin rebote, alternancia de linterna, montaje consciente del enfoque y captura fija reducida.
npx expo install expo-camera expo-image-manipulator// src/scan/InspectionCapture.tsx
import { CameraView, useCameraPermissions } from "expo-camera";
import * as ImageManipulator from "expo-image-manipulator";
import { useIsFocused } from "@react-navigation/native";
import { useCallback, useRef, useState } from "react";
import { Image } from "expo-image";
import { Pressable, StyleSheet, Text, View } from "react-native";
type Props = {
onScan: (payload: string) => void;
onPhoto: (uri: string) => void;
};
export function InspectionCapture({ onScan, onPhoto }: Props) {
const isFocused = useIsFocused();
const cameraRef = useRef<CameraView>(null);
const [permission, requestPermission] = useCameraPermissions();
const [ready, setReady] = useState(false);
const [torch, setTorch] = useState(false);
const [preview, setPreview] = useState<string | null>(null);
const lastScan = useRef(0);
const onBarcodeScanned = useCallback(
({ data }: { data: string }) => {
if (!isFocused) return;
const now = Date.now();
if (now - lastScan.current < 1500) return;
lastScan.current = now;
onScan(data);
},
[isFocused, onScan]
);
async function captureLabel() {
if (!ready) return;
const photo = await cameraRef.current?.takePictureAsync({ quality: 0.8 });
if (!photo?.uri) return;
const resized = await ImageManipulator.manipulateAsync(
photo.uri,
[{ resize: { width: 1600 } }],
{ compress: 0.75, format: ImageManipulator.SaveFormat.JPEG }
);
setPreview(resized.uri);
onPhoto(resized.uri);
}
if (!permission?.granted) {
return (
<View style={styles.gate}>
<Text>Fotografía las placas de serie y escanea los códigos QR en el equipo.</Text>
<Pressable onPress={requestPermission}>
<Text style={styles.link}>Continuar</Text>
</Pressable>
</View>
);
}
if (preview) {
return (
<View style={styles.preview}>
<Image source={{ uri: preview }} contentFit="contain" style={styles.previewImage} />
<Pressable onPress={() => setPreview(null)}>
<Text>Reintentar</Text>
</Pressable>
</View>
);
}
if (!isFocused) {
return <View style={styles.placeholder} />;
}
return (
<View style={styles.root}>
<CameraView
ref={cameraRef}
style={styles.camera}
facing="back"
enableTorch={torch}
onCameraReady={() => setReady(true)}
barcodeScannerSettings={{ barcodeTypes: ["qr"] }}
onBarcodeScanned={onBarcodeScanned}
/>
<View style={styles.toolbar}>
<Pressable onPress={() => setTorch((t) => !t)}>
<Text style={styles.tool}>{torch ? "Linterna encendida" : "Linterna"}</Text>
</Pressable>
<Pressable onPress={captureLabel} disabled={!ready}>
<Text style={styles.tool}>Capturar etiqueta</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
root: { flex: 1 },
camera: { flex: 1 },
gate: { flex: 1, padding: 24, gap: 12, justifyContent: "center" },
link: { color: "#2563eb", fontWeight: "600" },
placeholder: { flex: 1, backgroundColor: "#111" },
preview: { flex: 1, padding: 16, gap: 12 },
previewImage: { flex: 1, borderRadius: 8 },
toolbar: {
position: "absolute",
bottom: 40,
left: 16,
right: 16,
flexDirection: "row",
justifyContent: "space-between",
},
tool: { color: "#fff", fontWeight: "600", padding: 12, backgroundColor: "rgba(0,0,0,0.55)", borderRadius: 8 },
});Lo que demuestra esto:
useIsFocused libera la cámara cuando la pestaña o modal está oculta.onCameraReady autoriza takePictureAsync - llamarlo temprano produce errores o fotogramas en blanco.enableTorch activa la luz para el contraste de códigos de barras en pasillos oscuros.onBarcodeScanned sin rebote evita navegaciones duplicadas.expo-image mantiene la memoria acotada.| Modo | API | Mejor para |
|---|---|---|
| Vista previa en línea | CameraView + onBarcodeScanned | Superposición personalizado, linterna, UX en la misma pantalla |
| Escáner del sistema | CameraView.launchScanner() | iOS 16+ / Hoja de ML Kit de Android |
| Listener moderno | CameraView.onModernBarcodeScanned | Eventos de escáner nativos sin vista previa en línea |
import { CameraView } from "expo-camera";
async function openSystemScanner() {
await CameraView.launchScanner({ barcodeTypes: ["qr"] });
}data con Zod antes de router.push - nunca confíes en URLs escaneadas ciegamente.onBarcodeScanned={scanned ? undefined : handler}.barcodeScannerEnabled: false en el complemento de configuración cuando envías aplicaciones solo de foto - binario más pequeño.<CameraView enableTorch={torchOn} facing="back" />useEffect - dejarla encendida agota la batería e irrita a los usuarios.quality moderada sobre resolución completa sin luz.Controladores de costo de vista previa:
1. Resolución y relación de aspecto (la propiedad de proporción en Android cambia el tipo de escala)
2. Múltiples instancias de CameraView montadas (prohibidas - solo una sesión activa)
3. Pestañas de fondo aún montadas (usa restricción de enfoque)
4. Escaneo de códigos de barras + animaciones de navegación de 60 fps en Android de gama baja| Técnica | Efecto |
|---|---|
| Desmontar al desenfocar | Libera el hardware de cámara para otras aplicaciones |
pausePreview() cuando la hoja cubre la cámara | Reduce GPU mientras la modal está abierta |
Reducir pictureSize cuando sea compatible | Búferes de análisis más pequeños para fotografías fijas |
Pantalla de solo escaneo sin pictureSize 4K | Bloqueo más rápido y menor calor |
// Pausa cuando una hoja de fondo cubre la vista previa
useEffect(() => {
if (!sheetOpen) return;
cameraRef.current?.pausePreview();
return () => {
cameraRef.current?.resumePreview();
};
}, [sheetOpen]);takePictureAsync mientras la vista previa está en pausa en Android - genera una excepción.const photo = await cameraRef.current?.takePictureAsync({
quality: 0.8,
skipProcessing: false,
});
const video = await cameraRef.current?.recordAsync({ maxDuration: 60 });recordAsync escribe en caché - carga mediante expo-file-system, no base64.scanned.<Image>.Platform.OS y límites de tamaño.| Alternativa | Usá cuando | No uses cuando |
|---|---|---|
CameraView | UI de escaneo personalizado, linterna, video | Cámara del sistema de un toque |
launchCameraAsync | UX de cámara nativa es suficiente | Visor de marca |
launchScanner() | QR rápido sin UI personalizado | Necesitas superposición de linterna en la misma pantalla |
| react-native-vision-camera | Procesadores de fotogramas, contadores de FPS | Captura Expo estándar |
npx expo install expo-cameraAgrega cadenas de permiso a través del complemento de configuración, luego reconstruye si los permisos nativos cambiaron.
useIsFocused() es falso.CameraView montado roban la sesión de cámara única.Versiones de pila: Esta página se escribió para React 19.2.3, React Native 0.86.0 y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026