# Unif Camera — 全文文档聚合 > 单文件聚合版。每段都带源路径与标题,方便整体粘贴给 LLM。 ## 目录 - CameraApi - 类型 - useCamera - 核心概念 - 安装 - 快速上手 - 录像 - 拍照 - 水印 - 介绍 - 从 v1.x 升级 - AI Skill - 测试(Mock) - 常见问题 ## CameraApi *Source: `docs/api/camera-api.md` · Slug: `/api/camera-api`* # CameraApi 相机控制对象,由 [`useCamera()`](/docs/api/use-camera) 返回,提供**打开 / 关闭相机**两个方法。这是一套 Promise 式弹窗相机 API:`open()` 弹出全屏相机,用户拍完确认(或取消)后 Promise resolve 出 [`CameraResult`](/docs/api/types#cameraresult)。 --- ## 引用 / 签名 {#signature} ```ts import { useCamera } from '@unif/react-native-camera'; import type { CameraApi } from '@unif/react-native-camera'; ``` ```ts const [api, holder] = useCamera(); // api 的类型为 CameraApi ``` **TypeScript 签名:** ```ts type CameraApi = { open: (config: OpenConfig) => Promise; close: () => void; }; ``` --- ## `open(config)` {#open} ```ts open(config: OpenConfig): Promise ``` 弹出相机全屏模态。用户在模态内完成拍摄 → 预览确认 / 取消后,Promise resolve 为 [`CameraResult`](/docs/api/types#cameraresult)。**取消不会 reject**——而是 resolve 出 `code: 0`。 **参数:** | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `config` | [`OpenConfig`](#openconfig) | ✅ | 相机配置——拍摄模式、数据保留策略、水印 | **示例:** ```tsx const res = await api.open({ cameraMode: [ { mode: 'single', quality: 0.9 }, { mode: 'continuous' }, ], dataRetainedMode: 'clear', }); if (res.code === 200) { // res.data 是 CustomPhotoFile[] 文件列表 } ``` --- ## `close()` {#close} ```ts close(): void ``` 强制关闭相机模态(若当前处于打开状态)。内部等价于以 `code: 0`(`message: 'cancelled'`)settle 当前 `open()` 的 Promise。 **通常无需手动调用**——用户拍摄完成或取消后 `open()` 会自动 resolve 并关闭相机。仅在需要从外部强制收起相机时使用(例如路由拦截、用户登出)。 **示例:** ```tsx // 组件卸载时兜底关闭相机 useEffect(() => { return () => { api.close(); }; }, [api]); ``` --- ## OpenConfig {#openconfig} `open()` 的配置对象。完整字段类型表见 [类型 → OpenConfig](/docs/api/types#openconfig),这里说明各字段的**运行时行为**: | 字段 | 类型 | 必填 | 默认 | 说明 | | --- | --- | --- | --- | --- | | `cameraMode` | [`CameraMode[]`](/docs/api/types#cameramode) | ✅ | — | 拍摄模式数组,至少一项 | | `dataRetainedMode` | `'clear' \| 'retain'` | ✅ | — | 切换模式时是否保留已拍文件 | | `watermark` | [`WatermarkType`](/docs/api/types#watermarktype) | — | 不加水印 | 文字水印配置 | | `photoQualityPrioritization` | `'speed' \| 'balanced' \| 'quality'` | — | SDK 默认 `'balanced'` | 照片质量优先级(全局) | | `photoHDR` | `boolean` | — | 由相机 negotiate | 是否启用照片 HDR | | `videoBitRate` | `number` | — | 编码器自适应 | 录像目标码率(bps) | ### `cameraMode` {#cameramode-behavior} - **数组的首项决定初始状态**:初始前/后摄取首项的 `type`(缺省 `back`),初始闪光取首项的 `flashMode`(缺省 `off`);其余项的 `type` / `flashMode` 不影响初始化。 - **多项时底部出现模式 tab**:相机底部按数组顺序渲染「单拍 / 连拍 / 视频」切换 pill,用户拍摄过程中可自由切换。仅一项时不显示 tab。 - 每项的 `mode`、`quality` 等字段见 [`CameraMode`](/docs/api/types#cameramode)。 ### `dataRetainedMode` {#dataretainedmode-behavior} 控制用户**切换拍摄模式**时已拍文件的去留,并影响单拍的自动预览时机: | 值 | 行为 | | --- | --- | | `'clear'` | 切模式时先 `confirm()` 二次确认,确认后清空已拍照片;且「**单拍 + clear**」每拍一张后直接进入确认预览页 | | `'retain'` | 切模式时不清空,已拍文件累积合并进最终结果 | ### `watermark` {#watermark-behavior} 传入则**取景画面实时显示水印戳记**(WYSIWYG),**每次快门后逐张烧入**(保存时返回的已是烧好的成片)。仅对照片(`image/jpeg`)生效,录像无水印。详见 [水印指南](/docs/guides/watermark)。 ### 拍摄质量(`photoQualityPrioritization` / `photoHDR` / `videoBitRate`) {#quality-behavior} 三个**可选**字段,用于按需覆盖底层 vision-camera 的拍摄质量取舍。**核心约定:缺省(不传)时库不写入任何偏好,完全走 SDK 默认协商**——只有显式传值才生效。 | 字段 | 缺省(不传) | 传值时 | | --- | --- | --- | | `photoQualityPrioritization` | 不写入该选项 → SDK 默认 `'balanced'` | `'balanced'` / `'quality'` 任何设备直传;`'speed'` 在不支持的设备**自动安全降级**为 `'balanced'`(不报错、不中断拍摄) | | `photoHDR` | 不下发 `photoHDR` 约束 → 由相机 negotiate 自行决定 | 传 `true` / `false` 作为约束下发(显式开 / 显式关) | | `videoBitRate` | 不写入 → 编码器按分辨率自适应 | 作为 `targetBitRate`(bps)下发;编码器会参考但可能因系统压力 / 画面运动 / 文件大小约束略有出入 | :::note 与分辨率无关 照片 / 录像分辨率已固定为 UHD(照片 4:3 ≈12MP、16:9 4K;录像随画幅走 UHD),**不可配置**,也不随这三个字段变化。这三字段只调质量取舍 / HDR / 码率。 ::: --- ## 平台兼容性 {#platforms} | 平台 | 支持 | | --- | --- | | iOS | ✅ | | Android | ✅ | | Web | ❌ | --- ## 相关 {#related} - [useCamera](/docs/api/use-camera) — 获取 `CameraApi` 实例的 hook - [类型](/docs/api/types) — `OpenConfig` / `CameraResult` / `CustomPhotoFile` 完整字段表 - [拍照](/docs/guides/taking-photos) — 拍照场景完整指南 - [录像](/docs/guides/recording-video) — 录像场景完整指南 ## 类型 *Source: `docs/api/types.md` · Slug: `/api/types`* # 类型 `@unif/react-native-camera` 所有公开类型的完整定义,逐字段(prop · 类型 · 默认 · 说明)列出。类型从 `@unif/react-native-camera` 直接导出。 --- ## 引用 {#import} ```ts import type { OpenConfig, CameraMode, WatermarkType, CameraResult, CustomPhotoFile, CameraApi, } from '@unif/react-native-camera'; ``` --- ## OpenConfig {#openconfig} 传入 [`api.open(config)`](/docs/api/camera-api#open) 的配置对象。 | 字段 | 类型 | 必填 | 默认 | 说明 | | --- | --- | --- | --- | --- | | `cameraMode` | [`CameraMode[]`](#cameramode) | ✅ | — | 拍摄模式数组,至少一项;多项时底部出现模式 tab | | `dataRetainedMode` | `'clear' \| 'retain'` | ✅ | — | 切换模式时是否保留已拍照片 | | `watermark` | [`WatermarkType`](#watermarktype) | — | 不加水印 | 文字水印配置;传入则取景显示戳记 + 保存时烧入成片 | | `photoQualityPrioritization` | `'speed' \| 'balanced' \| 'quality'` | — | **走 SDK 默认 `'balanced'`** | 照片质量优先级(全局)。缺省不传该字段、由 vision-camera 用默认 `'balanced'`;`'speed'` 在不支持的设备会被**安全降级**为 `'balanced'`(不报错);`'quality'`/`'balanced'` 任何设备直传 | | `photoHDR` | `boolean` | — | **由相机 negotiate 决定** | 是否启用照片 HDR(多帧融合,更宽动态范围)。缺省不下发该约束、不强制开关;传 `boolean` 才作为约束下发 | | `videoBitRate` | `number` | — | **编码器自适应** | 录像目标码率(bps,全局,作用于 video 模式)。缺省不传、由编码器按分辨率自适应;仅在需要明确控制时传(如 4K 约 20–40 Mbps) | 各字段的运行时行为见 [CameraApi → OpenConfig](/docs/api/camera-api#openconfig)。 :::note 拍摄质量三字段缺省即「不替你做取舍」 `photoQualityPrioritization` / `photoHDR` / `videoBitRate` 都是**可选**字段,**缺省(不传)时库不写入任何偏好**,完全交给 vision-camera SDK 的默认协商。只有你**显式传值**时才会覆盖默认。照片/录像分辨率是另一回事——已固定为 UHD(4:3 ≈12MP、16:9 4K),不可配置、不随这三字段变化。 ::: --- ## CameraMode {#cameramode} `OpenConfig.cameraMode` 数组中每一项的类型,描述一种拍摄模式及其初始参数。 | 字段 | 类型 | 必填 | 默认 | 说明 | | --- | --- | --- | --- | --- | | `mode` | `'single' \| 'continuous' \| 'video'` | ✅ | — | 拍摄模式:单拍 / 连拍 / 视频 | | `type` | `'back' \| 'front'` | — | `'back'` | 初始前/后摄。**仅数组首项生效**(决定相机打开时的初始镜头) | | `flashMode` | `'auto' \| 'on' \| 'off'` | — | `'off'` | 初始闪光。**仅数组首项生效**作初始值;闪光开关之后由相机内 UI 控制 | | `quality` | `number` | — | `0.9` | JPEG 压缩率 `0~1`。质量优先级见 [`OpenConfig.photoQualityPrioritization`](#openconfig)(缺省走 SDK 默认 `'balanced'`) | | `recTime` | `number` | — | — | 录制时长上限(秒)。已接线 vision-camera `maxDuration`:到点原生自动停止、视频自动入已拍列表(缺省不设=不自动停) | :::note `type` / `flashMode` / `recTime` 的现状 - `type`、`flashMode` 沿用自原版 4.x 的 API,仅**数组首项**被读取,作相机打开时的初始镜头 / 初始闪光;其余项的这两个字段被忽略。 - `recTime` 已接线到 vision-camera `maxDuration`(2.21 起):到点原生自动停止,视频与手动停止走同一路径入列。缺省不传则不自动停。 ::: --- ## WatermarkType {#watermarktype} `OpenConfig.watermark` 的类型——给取景画面和成片烧入文字水印。用法见 [水印指南](/docs/guides/watermark)。 | 字段 | 类型 | 必填 | 默认 | 说明 | | --- | --- | --- | --- | --- | | `content` | `string[]` | ✅ | — | 水印文字,每个字符串一行;数量不限 | | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | — | `'top-right'` | 水印位置(文字对齐随位置自适应) | --- ## CameraResult {#cameraresult} [`api.open()`](/docs/api/camera-api#open) 返回的 `Promise` resolve 值。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `code` | `0 \| 200 \| 403 \| 404 \| 500 \| 503` | 状态码,见下表 | | `data` | [`CustomPhotoFile[]`](#customphotofile) | 拍摄的文件列表(仅 `code === 200` 时非空有效) | | `message` | `string` | 描述信息 | **状态码(`CameraResultCode`):** | code | 含义 | 何时返回 | | --- | --- | --- | | `200` | 成功 | 用户完成拍摄并确认,`data` 含文件列表 | | `0` | 取消 | 用户取消、点返回或调用 `api.close()`(`data` 为空) | | `403` | 无权限 | 相机权限被拒 | | `404` | 无设备 | 没有可用摄像设备 | | `500` | 配置非法 | `cameraMode` 为空数组 / 无效项(拍照运行时失败不再返回此码,见下) | | `503` | 录像失败 | 保留码,当前不触发(录像失败改走相机内重试,见下) | :::info 拍照 / 录像运行时失败:相机内重试,不返回 code 自 2.21 起,快门拍摄失败、录像启动 / 停止失败**不再 resolve 关相机**,而是在相机内弹顶部错误条提示重试、不丢已拍(对齐 1.x「失败停留」)。故 `500` 仅余「配置非法」、`503` 当前无触发路径,二者保留作 API 兼容。 ::: :::warning 判成功务必 `=== 200` 只有 `200` 是成功。`0` 是取消,此时 `data` 为空——别把取消当成功。 ::: --- ## CustomPhotoFile {#customphotofile} `CameraResult.data` 数组中每个文件的类型。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `string` | 唯一 id(`时间戳-序号`,避免同毫秒撞 id) | | `cameraType` | `'back' \| 'front'` | 拍摄时的前/后摄 | | `cameraMode` | `'single' \| 'continuous' \| 'video'` | 模式(原版 1.x 字段名,= `mode`) | | `path` | `string` | 本地文件路径 | | `uri` | `string` | 文件 uri(`file://` 前缀) | | `width` | `number` | 宽(px) | | `height` | `number` | 高(px) | | `mime` | `'image/jpeg' \| 'video/mp4'` | MIME 类型 | | `mode` | `'single' \| 'continuous' \| 'video'` | 模式(2.x 字段名,= `cameraMode`) | | `duration?` | `number` | 时长(秒,仅 video 条目有,取录制实际时长) | :::info `cameraMode` 与 `mode` 的关系 `cameraMode` 与 `mode` 是**同一值的两个别名**,始终相等:`cameraMode` 是原版(1.x)字段名,`mode` 是 2.x 引入的字段名。两者同时存在以保证向后兼容,按习惯选用其一即可。 ::: --- ## CameraApi {#cameraapi} [`useCamera()`](/docs/api/use-camera) 返回的相机控制对象。逐方法说明见 [CameraApi](/docs/api/camera-api)。 ```ts type CameraApi = { open: (config: OpenConfig) => Promise; close: () => void; }; ``` --- ## 平台兼容性 {#platforms} 类型定义为纯 TypeScript(不含运行时代码),在所有平台均可导入。 | 平台 | 支持 | | --- | --- | | iOS | ✅ | | Android | ✅ | | Web | ✅(仅类型) | --- ## 相关 {#related} - [useCamera](/docs/api/use-camera) — 获取 `CameraApi` 实例的 hook - [CameraApi](/docs/api/camera-api) — `open()` / `close()` 方法与 `OpenConfig` 行为 - [拍照](/docs/guides/taking-photos) — 拍照场景配置示例 - [录像](/docs/guides/recording-video) — 录像场景配置示例 ## useCamera *Source: `docs/api/use-camera.md` · Slug: `/api/use-camera`* # useCamera `@unif/react-native-camera` 的**唯一入口 Hook**。无参调用,返回 `[api, holder]` 二元组:`api` 用来打开 / 关闭相机,`holder` 是相机全屏模态的宿主节点,必须渲染进 React 树。 --- ## 引用 / 签名 {#signature} ```ts import { useCamera } from '@unif/react-native-camera'; ``` ```ts const [api, holder] = useCamera(); ``` **TypeScript 签名:** ```ts function useCamera(): [CameraApi, React.ReactElement] ``` `useCamera()` **不接受任何参数**——所有拍摄配置都在调用 [`api.open(config)`](/docs/api/camera-api) 时传入。 --- ## 返回值 {#return} 返回一个二元组 `[CameraApi, React.ReactElement]`: | 返回值 | 类型 | 说明 | | --- | --- | --- | | `api` | [`CameraApi`](/docs/api/camera-api) | 相机控制对象,提供 `open()` / `close()` 方法 | | `holder` | `React.ReactElement` | 相机 UI(全屏模态)的宿主节点,**必须渲染进 React 树** | `holder` 本质是一个 `` 节点(内部已自带 `SafeAreaProvider` + `ThemeProvider`),相机未打开时不显示任何内容。`api.open()` 只是把这个模态切到可见态并保存 resolver——**没有 `holder` 挂载就没有挂载锚点,`api.open()` 会静默无效、相机不弹出**。 --- ## 示例 {#example} ```tsx import React from 'react'; import { View, TouchableOpacity, Text } from 'react-native'; import { useCamera } from '@unif/react-native-camera'; const PhotoScreen = () => { const [api, holder] = useCamera(); const handleOpen = async () => { const res = await api.open({ cameraMode: [{ mode: 'single', quality: 0.9 }], dataRetainedMode: 'clear', }); if (res.code === 200) { // res.data 是 CustomPhotoFile[] 文件列表 } }; return ( 打开相机 {holder} ); }; ``` --- ## 注意事项 {#notes} - **`holder` 必须渲染进 React 树。** 它的位置不影响视觉(相机打开时会全屏覆盖),但节点必须存在;缺少它时 `api.open()` 调用无效、相机无法显示。这是最高频的接入错误。 - **只有 `code === 200` 才是成功。** 用户取消走 `code: 0`,`data` 为空——不要把取消当成功。完整状态码见 [`CameraResult`](/docs/api/types#cameraresult)。 - **测试 mock 时 `holder` 为 `null`。** 使用官方 mock(`jest.mock('@unif/react-native-camera', () => require('@unif/react-native-camera/mock'))`)时,`useCamera()` 返回 `[api, null]`,渲染时仍可直接写 `{holder}`(React 忽略 `null`)。详见 [测试](/docs/testing)。 --- ## 平台兼容性 {#platforms} | 平台 | 支持 | | --- | --- | | iOS | ✅ | | Android | ✅ | | Web | ❌ | --- ## 相关 {#related} - [CameraApi](/docs/api/camera-api) — `open()` / `close()` 方法完整文档 - [类型](/docs/api/types) — `OpenConfig` / `CameraResult` / `CustomPhotoFile` 类型定义 - [快速上手](/docs/getting-started/quick-start) — 最小可运行示例 ## 核心概念 *Source: `docs/getting-started/concepts.md` · Slug: `/getting-started/concepts`* # 核心概念 在深入 API 前,先建立几个核心心智模型——它们解释了这个库「为什么这样设计」与使用时的关键约束。 --- ## 模型一:模态相机,不是内嵌取景器 `@unif/react-native-camera` 提供的是**模态化相机**,而不是嵌进页面布局的取景器组件。 调用 `api.open()` 会弹出一个**全屏模态界面**,用户在模态内完成所有操作(切换模式、拍照、预览、确认或取消),确认后模态关闭,结果通过 Promise 返回。 这与把 `` 内嵌到页面布局的模式完全不同: - **调用方不管相机 UI 的布局和层叠** —— 模态自己全屏覆盖。 - **拍摄流程是线性的** —— `await api.open(...)` 挂起,拍完才继续,逻辑简单。 - **适合「按需拍照」** —— 表单附件、巡检记录、工单照片;不适合持续取景的 AR / 扫码场景(扫码请用 `@unif/react-native-hms-scan`)。 模态内的取景控件由库内置、用户自行操作(无需调用方配置):双指 pinch 变焦(含 `0.5x` 超广角与 `0.5/1` 档位快捷跳档)、点击对焦、前后摄翻转、画幅切换(`4:3` / `16:9`,**默认 `16:9`**)、闪光(`auto`/`on`/`off` 轮换)、快门声开关。 --- ## 模型二:`useCamera()` → `[api, holder]`,holder 必须渲染 `useCamera()` 无参,返回二元组 `[api, holder]`: - **`api`** —— `CameraApi`,有两个方法:`open(config)` 弹出相机并返回 Promise;`close()` 主动收起(等价于用户取消,resolve `code: 0`)。 - **`holder`** —— 相机模态的 React 宿主节点(`React.ReactElement`)。 ```tsx const [api, holder] = useCamera(); return ( {/* 其他 UI */} {holder} {/* 必须在树中 */} ); ``` **`holder` 必须出现在 React 树中**,原因: - 相机模态是通过 React 组件树挂载的,不是 imperative 的原生调用。 - `holder` 是相机 UI 的挂载锚点;缺它,`api.open()` 找不到渲染目标,调用**静默无效**。 - `holder` 的位置(在父节点哪一层)不影响视觉——相机打开时全屏覆盖——但**节点必须存在于组件树内**。 推荐在页面组件或根 App 里一次性放置 `{holder}`,无需每次调用前重新挂载。 --- ## 模型三:`api.open(config)` 返回 Promise,有完整生命周期 `api.open(config)` 返回 `Promise`,生命周期如下: ``` api.open(config) │ ▼ [打开] 全屏模态弹出,进入拍摄界面 │ ▼ [拍摄] 用户拍照 / 录像(单拍 / 连拍 / 视频) │ ▼ [确认] 预览页:确认使用 / 重拍 / 取消 │ ▼ [关闭] 模态收起 │ ▼ Promise.resolve(CameraResult) ``` 关键特性: - **挂起调用方** —— `await api.open(...)` 会等整个流程(确认或取消)完成才继续。 - **取消也 resolve** —— 用户取消时 `code` 为 `0`,**不会 reject**。 - **水印在每次快门后逐张烧入** —— 若传 `watermark`,相机在**每次快门后**对该张照片逐张烧入(`image/jpeg`,串行)(期间 footer 提示「正在生成水印图片…」)。Promise resolve 时水印已烧入成片。 ### `config`:`cameraMode` 与 `dataRetainedMode` `api.open(config)` 的入参是 `OpenConfig`: - **`cameraMode: CameraMode[]`** —— 拍摄模式数组,至少一项。每项的 `mode` 为 `'single'`(单拍) / `'continuous'`(连拍) / `'video'`(录像);可选 `quality`(0~1 JPEG 压缩系数,默认 `0.9`)。传多项时,相机底部出现模式 tab 供用户切换。 - **`dataRetainedMode: 'clear' | 'retain'`** —— 用户切换模式时:`'clear'` 清除已拍文件,`'retain'` 保留。 - **`watermark?`** —— 可选,见模型五。 > `CameraMode` 还有 `type`(初始前/后摄)、`flashMode`、`recTime` 等兼容字段。完整字段见 [API → 类型](/docs/api/types)。 --- ## 模型四:用 `CameraResult.code` 判定结果 `api.open()` resolve 出 `CameraResult = { code, data, message }`。**只有 `code === 200` 才是成功**: | code | 含义 | 处理 | | --- | --- | --- | | `200` | 用户确认保存(成功) | 取 `res.data`(`CustomPhotoFile[]`) | | `0` | 取消 / 关闭 | 静默,`data` 为空 | | `403` | 无相机权限 | 引导用户去系统设置开权限 | | `404` | 无可用摄像设备 | 提示设备不支持 | | `500` | 配置非法(`cameraMode` 为空) | 兜底提示,排查 `config` | | `503` | 保留码,当前不触发 | —— | > **拍照 / 录像运行时失败不再返回 code 关相机**:快门拍摄失败、录像启动 / 停止失败 → 相机内顶部错误条提示「请重试」,不关闭相机、不结束 `open()`,用户可重拍、已拍不丢。故 `500` 仅余「配置非法」、`503` 当前无触发路径。 成功时 `res.data` 的每一项是 `CustomPhotoFile`,含 `uri` / `path` / `width` / `height` / `mime`(`'image/jpeg'` 或 `'video/mp4'`)等字段。 :::danger 别把 `0` 当成功 `0` 是「取消」,此时 `data` 为空。判定务必用 `code === 200`。各 code 的处理范式见[常见问题](/docs/troubleshooting)。 ::: --- ## 模型五:Skia 水印(仅照片) 传入 `watermark` 后,相机在用户确认时用 **Skia** 把多行文字**离屏合成、烧进成片**: ```ts watermark: { content: ['Unif · 巡检', '上海浦东', '2024-01-01 10:00'], // 每项一行 position: 'top-right', // 六选一,默认 'top-right' } ``` 关键约束: - **仅对照片(`image/jpeg`)生效** —— 水印烧图把结果编码为 JPEG;**录像(`video/mp4`)没有水印**。 - **是可视标记,不是防篡改手段** —— 水印只是叠加在像素上的文字,不提供任何加密 / 防伪 / 签名保证。 - **烧图失败不阻断保存** —— 读写或解码异常时返回原图,拍摄照常成功。 > 水印用法详解见[指南 → 水印](/docs/guides/watermark)。 --- ## 术语表 | 术语 | 一句话定义 | 详情 | | --- | --- | --- | | `useCamera()` | 唯一公开 Hook,返回 `[api, holder]` | 模型二 | | holder | 相机模态的 React 宿主节点,必须渲染进树 | 模型二 | | `CameraApi` | `api` 对象,含 `open(config)` / `close()` | [API → useCamera](/docs/api/use-camera) | | `OpenConfig` | `api.open()` 入参:`cameraMode[]` + `dataRetainedMode`(+ 可选 `watermark`) | [API → 类型](/docs/api/types) | | `CameraMode` | 单个拍摄模式配置(`mode` + 可选 `quality`/`type`/`flashMode`/`recTime`) | [API → 类型](/docs/api/types) | | `dataRetainedMode` | 切模式时是否保留已拍文件:`'clear'` / `'retain'` | 模型三 | | `CameraResult` | `api.open()` 的 resolve 值:`code` + `data` + `message` | 模型四 | | `CustomPhotoFile` | 单个文件描述对象,含 `uri` / `path` / `width` / `height` / `mime` 等 | [API → 类型](/docs/api/types) | --- ## 下一步 - [指南 → 拍照](/docs/guides/taking-photos) —— 单拍 / 连拍配置详解 - [API 参考 → useCamera](/docs/api/use-camera) —— 完整 hook API - [API 参考 → 类型](/docs/api/types) —— 所有类型定义 ## 安装 *Source: `docs/getting-started/installation.md` · Slug: `/getting-started/installation`* # 安装 装齐 `@unif/react-native-camera` 的全部同伴包,配置原生权限,完成编译。**peerDeps 缺一即崩** —— 本页以 `package.json` 的 `peerDependencies` 为准逐项列出。 ## 环境要求 | 要求 | 版本 | | --- | --- | | React Native | **0.85+**(仅新架构 Fabric + TurboModules) | | React | 19+ | | iOS | 15.1+ | | Android | API 24+(Android 7.0) | :::note 为什么最低 iOS 是 15.1 本库是纯 JS 库,最低 iOS 由 peerDependencies 的原生库决定,取各 peer 最低 iOS 的**最高**值。React Native 0.85 core 的 `min_ios_version_supported` 为 `15.1`(RN 0.80+ 抬升),`react-native-vision-camera` / `react-native-nitro-modules` / `react-native-nitro-image` / `react-native-video` / `@dr.pogodin/react-native-fs` / `@sbaiahmed1/react-native-blur` 的 podspec 都继承这个值;`@shopify/react-native-skia` 写死 14.0、`react-native-reanimated` / `react-native-worklets` 为 13.4,均更低。故整体最低为 **iOS 15.1**。 ::: :::danger 仅支持新架构 本库依赖 Nitro Modules / vision-camera 5.x,**仅支持新架构**。旧架构(Bridge 模式)不受支持。安装前确认 `app.json` 或 `android/gradle.properties` 已启用新架构。 ::: --- ## 1. 安装依赖 {#安装依赖} 以下同伴包**全部必装,缺一即崩**(以 `package.json` 的 `peerDependencies` 为准): :::danger 完整 peer 清单 ```sh yarn add @unif/react-native-camera \ react-native-vision-camera react-native-vision-camera-worklets \ react-native-nitro-modules react-native-nitro-image \ @shopify/react-native-skia @dr.pogodin/react-native-fs react-native-video \ react-native-reanimated react-native-worklets react-native-reanimated-carousel \ react-native-gesture-handler react-native-safe-area-context react-native-svg \ @sbaiahmed1/react-native-blur @unif/react-native-design ``` ::: 各包的作用与版本约束: | 包 | 版本约束 | 作用 | | --- | --- | --- | | `react-native-vision-camera` | `^5.0.0` | 底层相机引擎 | | `react-native-vision-camera-worklets` | `^5.0.0` | vision-camera 5.x 内部懒 `require`,**必装**(见下) | | `react-native-nitro-modules` | `*` | vision-camera 5.x 的 Nitro 运行时 | | `react-native-nitro-image` | `*` | Nitro 图像桥 | | `@shopify/react-native-skia` | `>=2` | 水印离屏合成 | | `@dr.pogodin/react-native-fs` | `>=2` | 文件读写(**fork,非 `react-native-fs`**,见下) | | `react-native-video` | `>=7.0.0-beta.0` | 录像预览播放 | | `react-native-reanimated` | `>=4.0.0` | 取景器 / 预览动画 | | `react-native-worklets` | `*` | reanimated 4 / vision-camera 的 worklet 运行时 | | `react-native-reanimated-carousel` | `>=5.0.0-beta.0` | 预览页轮播 | | `react-native-gesture-handler` | `>=2.21.0` | pinch 变焦 / 对焦手势 | | `react-native-safe-area-context` | `>=5.0.0` | 安全区适配 | | `react-native-svg` | `>=15` | 矢量绘制(design `Icon` 等) | | `@sbaiahmed1/react-native-blur` | `>=4` | 界面毛玻璃 | | `@unif/react-native-design` | `>=0.8.1` | 图标(`Icon`)、按钮、字号/字重与颜色 token、缩放工具 `r()` | :::note 关于 `react-native-webview` `package.json` 的 `peerDependencies` 中还列有 `react-native-webview`(`*`),这是**早期版本遗留保留**的声明,当前源码已不直接引用它。新接入无需为本库单独安装;若项目其他依赖已带它,保持原样即可。 :::
为什么 react-native-vision-camera-worklets 必装? vision-camera 5.x 把 Frame Processor / 多线程能力拆到了同伴包 `react-native-vision-camera-worklets`,并在内部通过懒 `require` 引用它。**即使本库不使用任何 Frame Processor**,消费端打包器(Metro 等)在静态解析阶段仍会解析 vision-camera 内部那处 `require`——缺失该包会直接报错: - 打包期:`Unable to resolve module react-native-vision-camera-worklets` - 运行时:`Cannot use Frame Processors - react-native-vision-camera-worklets is not installed` 因此它是**必装的同伴包**,版本与 `react-native-vision-camera` 对齐(同为 `^5.x`)。vision-camera 自身未将其声明为 peer(视作可选),本库已在 `peerDependencies` 中显式声明,以提醒消费者一并安装。
为什么文件系统用 @dr.pogodin/react-native-fs 而非 react-native-fs? 本库依赖的是 **fork** —— `@dr.pogodin/react-native-fs`(水印烧图时读写临时文件用它)。它与社区原版 `react-native-fs` **是两个包**,装错或两者并存都会导致原生符号冲突。 ```sh # ❌ Incorrect:装成非 fork 的包,会冲突 yarn add react-native-fs # ✅ Correct:装这个 fork yarn add @dr.pogodin/react-native-fs ``` 若 `package.json` 里已混进 `react-native-fs`,先卸掉它再装 fork。
--- ## 2. 配置权限 {#权限配置} ### iOS(Info.plist) 在 `ios//Info.plist` 中添加以下三个权限 key: | Key | 说明 | | --- | --- | | `NSCameraUsageDescription` | 使用摄像头拍照 / 录像时展示给用户的说明文字 | | `NSMicrophoneUsageDescription` | 录制视频时需要麦克风权限,展示给用户的说明文字 | | `NSPhotoLibraryAddUsageDescription` | 保存照片 / 视频到相册时展示给用户的说明文字 | ```xml title="ios//Info.plist" NSCameraUsageDescription 需要访问摄像头以拍摄照片和视频 NSMicrophoneUsageDescription 录制视频时需要使用麦克风 NSPhotoLibraryAddUsageDescription 需要访问相册以保存拍摄的照片和视频 ``` ### Android(AndroidManifest.xml) 在 `android/app/src/main/AndroidManifest.xml` 的 `` 节点下添加: | 权限 | 说明 | | --- | --- | | `android.permission.CAMERA` | 拍照 / 录像所需的摄像头权限 | | `android.permission.RECORD_AUDIO` | 录制视频时的麦克风权限 | | `android.permission.READ_MEDIA_IMAGES` | Android 13+ 读取相册图片权限 | ```xml title="android/app/src/main/AndroidManifest.xml" ``` --- ## 3. 原生编译 ### iOS:pod install 安装或升级依赖后,**必须重新执行 pod install**: ```sh cd ios && bundle exec pod install ``` :::warning vision-camera / Skia / fs / video 升级后必跑 pod install `react-native-vision-camera`、`@shopify/react-native-skia`、`@dr.pogodin/react-native-fs`、`react-native-video`(7.x)均含原生代码,每次升级这些包后都需重新 `pod install`,否则运行时或编译期会报原生符号缺失。 ::: 完成后用 Xcode 或 `npx react-native run-ios` 重新编译运行。 ### Android Android 端无需额外配置,Gradle 自动同步。直接 `npx react-native run-android` 即可。 --- ## 4. 弹窗 / Toast 无需额外挂载 Host 相机的**二次确认弹窗 / Toast 是内部自洽的** —— 由相机 Modal 子树内的本地弹窗系统(`CameraDialogHost`)渲染,**不依赖** `@unif/react-native-design` 的全局 `ConfirmHost` / `ToastHost`。因此接入本库时: - **无需为相机在 App 根挂 `` / ``** —— 切模式 / 放弃拍摄的确认弹窗、保存提示 Toast 都直接显示在相机之上,开箱即用。 - 相机内部用 `ThemeProvider`(强制深色 token)+ `useColors`,模态内 UI 不依赖宿主的主题 Provider。 :::note 为什么相机要用本地弹窗 相机是全屏 RN ``。design 的 `ConfirmHost` / `ToastHost` 挂在消费者 App 根节点,而 App 根的弹窗 / Toast **无法叠加到已经 present 的相机 Modal 之上**(会被相机盖住)。所以相机内部改用挂在相机 Modal 子树里的高 `zIndex` 浮层渲染确认弹窗 / Toast,确保正常显示。 > 这是本库自身的设计;若你在**相机之外**使用 design 的命令式 `confirm` / `toast`,仍需按 design 文档在 App 根挂 `ConfirmHost` / `ToastHost`。 ::: --- ## 下一步 - [快速上手](/docs/getting-started/quick-start) —— 5 分钟跑通第一次拍照 - [核心概念](/docs/getting-started/concepts) —— 理解模态相机的心智模型 - [API 参考 → useCamera](/docs/api/use-camera) —— 完整 API 文档 ## 快速上手 *Source: `docs/getting-started/quick-start.md` · Slug: `/getting-started/quick-start`* # 快速上手 5 分钟跑通第一次拍照:取 `[api, holder]` → 渲染 `holder` → `await api.open(config)` → 按 `code === 200` 取文件。 :::warning 必须真机运行 相机依赖真实摄像头硬件,**模拟器 / Web 跑不起来**(属预期行为)。请在真机上验证。先完成[安装](/docs/getting-started/installation)(peerDeps + 权限键 + `pod install`)再运行本例。 ::: --- ## 最小可跑示例 ```tsx import React from 'react'; import { View, Button } from 'react-native'; import { useCamera, type CameraResult } from '@unif/react-native-camera'; export default function PhotoScreen() { const [api, holder] = useCamera(); // ① 取 api + holder const onShoot = async () => { const res: CameraResult = await api.open({ // ③ 弹出相机,await 结果 cameraMode: [{ mode: 'single', quality: 0.9 }], dataRetainedMode: 'clear', }); if (res.code === 200) { // ④ 200 才是成功 // res.data 是 CustomPhotoFile[],每项含 .uri / .path / .width / .height / .mime console.log(res.data[0]?.uri); } // 其余 code:0 取消 / 403 无权限 / 404 无设备 / 500 配置非法(拍摄失败走相机内重试) }; return (