{"id":50719993,"url":"https://github.com/swiftcrew/react-native-smart-capture","last_synced_at":"2026-06-09T23:01:11.002Z","repository":{"id":356315328,"uuid":"1232034255","full_name":"SwiftCrew/react-native-smart-capture","owner":"SwiftCrew","description":"A complete React Native solution for capturing and selecting profile images. Features front/back camera with zoom, gallery picker, interactive pan/pinch crop preview with circle or rectangle shapes, and public permission APIs. Parent app controls the entire permission and source selection UX. Works on iOS and Android.","archived":false,"fork":false,"pushed_at":"2026-05-07T15:19:30.000Z","size":273,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T15:33:38.779Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SwiftCrew.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T14:22:20.000Z","updated_at":"2026-05-07T15:08:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SwiftCrew/react-native-smart-capture","commit_stats":null,"previous_names":["swiftcrew/react-native-smart-capture"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/SwiftCrew/react-native-smart-capture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwiftCrew%2Freact-native-smart-capture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwiftCrew%2Freact-native-smart-capture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwiftCrew%2Freact-native-smart-capture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwiftCrew%2Freact-native-smart-capture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SwiftCrew","download_url":"https://codeload.github.com/SwiftCrew/react-native-smart-capture/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SwiftCrew%2Freact-native-smart-capture/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34129072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T23:01:10.064Z","updated_at":"2026-06-09T23:01:10.964Z","avatar_url":"https://github.com/SwiftCrew.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-native-smart-capture\n\n[![npm version](https://img.shields.io/npm/v/react-native-smart-capture.svg)](https://www.npmjs.com/package/react-native-smart-capture)\n[![license](https://img.shields.io/npm/l/react-native-smart-capture.svg)](https://github.com/SwiftCrew/react-native-smart-capture/blob/main/LICENSE)\n[![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android-lightgrey.svg)](https://reactnative.dev/)\n[![CI](https://github.com/SwiftCrew/react-native-smart-capture/actions/workflows/ci.yml/badge.svg)](https://github.com/SwiftCrew/react-native-smart-capture/actions/workflows/ci.yml)\n\n\u003e **Camera + Gallery + Crop — one SDK, zero hassle.**\n\nA production-ready React Native SDK for capturing and selecting profile images with interactive crop preview and built-in permission helpers.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/SwiftCrew/react-native-smart-capture/main/react-native-smart-capture.gif\" width=\"300\" alt=\"Demo\" /\u003e\n\u003c/p\u003e\n\n---\n\n## Why Use This SDK?\n\nBuilding a profile image picker from scratch requires handling:\n\n- Camera permissions (iOS + Android differences)\n- Photo library permissions (iOS limited access, Android scoped storage)\n- Camera integration with front/back switching and zoom\n- Gallery image selection\n- Image cropping with gesture support\n- Memory management and cleanup\n- Modal presentation issues on iOS\n\n**This SDK handles all of that** with a clean, customizable API.\n\n---\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| **Camera Capture** | Front/back camera with 1x / 2x zoom toggle |\n| **Gallery Selection** | Native picker with iOS limited-access support |\n| **Interactive Crop** | Pan \u0026 pinch gestures, circle or rectangle shapes |\n| **Permission Helpers** | Check, request, and deep-link to settings |\n| **Customizable UI** | Override strings, colors, and fonts |\n| **Cross-Platform** | iOS and Android support |\n| **Memory Safe** | Proper cleanup and mounted refs |\n| **TypeScript** | Full type definitions included |\n\n---\n\n## Installation\n\n```bash\n# Using yarn\nyarn add react-native-smart-capture\n\n# Using npm\nnpm install react-native-smart-capture\n```\n\n### Peer Dependencies\n\nInstall the required peer dependencies:\n\n```bash\nyarn add react-native-vision-camera \\\n         react-native-image-picker \\\n         react-native-permissions \\\n         react-native-reanimated \\\n         react-native-gesture-handler \\\n         react-native-svg \\\n         @react-native-community/image-editor\n```\n\n\u003e **Note:** `react-native-vision-camera` and `react-native-svg` are optional if you only use gallery selection.\n\n#### Compatible peer-dependency versions\n\nThis SDK targets stable APIs of its peers. Please install versions in these ranges:\n\n| Peer | Compatible range | Notes |\n|---|---|---|\n| `react-native-vision-camera` | `\u003e=3.0.0 \u003c5.0.0` | **VisionCamera 5.x** rewrote the photo-capture API and is **not yet supported**. Use `react-native-vision-camera@^4`. |\n| `react-native-image-picker` | `\u003e=7.0.0 \u003c9.0.0` | Tested up to v8. |\n| `@react-native-community/image-editor` | `\u003e=4.0.0 \u003c5.0.0` | |\n| `react-native-permissions` | `\u003e=3.0.0 \u003c6.0.0` | Tested up to v5. |\n| `react-native-reanimated` | `\u003e=3.0.0` | If on **Reanimated 4+**, also install `react-native-worklets` (see below). |\n| `react-native-gesture-handler` | `\u003e=2.0.0` | |\n| `react-native-svg` | `\u003e=13.0.0` | Optional. |\n\n#### If you use Reanimated 4\n\nReanimated 4 split out its worklets runtime into a separate package. You **must** install it:\n\n```bash\nyarn add react-native-worklets\ncd ios \u0026\u0026 pod install\n```\n\nIf you skip this you'll see `Unable to find a specification for 'RNWorklets'` during `pod install`.\n\n---\n\n## Permissions (camera \u0026 photo library)\n\nThis SDK uses **`react-native-permissions`** for runtime checks and requests (`ensureAllPermissions`, etc.).\nYou still need to declare permissions in **native config** so the OS can show prompts and App Store / Play review can validate them.\n\n### Runtime (JavaScript)\n\nCall `ensureAllPermissions()` (or the individual `check*` / `request*` helpers) **before** opening the camera or gallery flow.\nIf the user denies or blocks access, use `openSettings()` to deep-link them to the system Settings app.\n\n### iOS — `Info.plist`\n\nAdd usage descriptions — these strings are shown in the system permission dialog:\n\n| Capability | Key | When you need it |\n|---|---|---|\n| **Camera** | `NSCameraUsageDescription` | **Take Photo** / `openProfileImageCapture` |\n| **Photo library (read)** | `NSPhotoLibraryUsageDescription` | **Choose from gallery** / `openProfileImageGallery` |\n| **Save to library (optional)** | `NSPhotoLibraryAddUsageDescription` | Only if you save/export to the camera roll |\n| **Microphone** | `NSMicrophoneUsageDescription` | Often required by the camera framework even if you only capture still photos |\n\nExample:\n\n```xml\n\u003ckey\u003eNSCameraUsageDescription\u003c/key\u003e\n\u003cstring\u003eWe need camera access to capture your profile photo.\u003c/string\u003e\n\n\u003ckey\u003eNSPhotoLibraryUsageDescription\u003c/key\u003e\n\u003cstring\u003eWe need photo library access to choose your profile photo.\u003c/string\u003e\n\n\u003ckey\u003eNSPhotoLibraryAddUsageDescription\u003c/key\u003e\n\u003cstring\u003eWe need permission to save your profile photo.\u003c/string\u003e\n\n\u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n\u003cstring\u003eRequired by the camera framework for photo capture.\u003c/string\u003e\n```\n\n### iOS — `Podfile`\n\nRegister the permission pods you use (camera + photo library). At the **top** of `ios/Podfile`:\n\n```ruby\ndef node_require(script)\n  require Pod::Executable.execute_command('node', ['-p',\n    \"require.resolve('#{script}', {paths: [process.argv[1]]})\",\n    __dir__\n  ]).strip\nend\n\nnode_require('react-native-permissions/scripts/setup.rb')\n\nsetup_permissions([\n  'Camera',\n  'PhotoLibrary',\n  'PhotoLibraryAddOnly',\n])\n```\n\nThen:\n\n```bash\ncd ios \u0026\u0026 pod install\n```\n\n\u003e **Limited photo library (iOS):** if the user chooses \"Select Photos…\", the SDK treats limited access as **granted** for the gallery flow, consistent with `react-native-permissions`.\n\n### Android — `AndroidManifest.xml`\n\n| Permission | When |\n|---|---|\n| `android.permission.CAMERA` | Camera capture |\n| `android.permission.READ_MEDIA_IMAGES` | **Android 13+ (API 33+)** — read images from the gallery |\n| `android.permission.READ_EXTERNAL_STORAGE` with `android:maxSdkVersion=\"32\"` | **Android 12 and below** — read images from the gallery |\n\nExample:\n\n```xml\n\u003cuses-permission android:name=\"android.permission.CAMERA\" /\u003e\n\u003cuses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" /\u003e\n\u003cuses-permission\n  android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n  android:maxSdkVersion=\"32\" /\u003e\n```\n\nOn **Android 6+**, dangerous permissions are requested at **runtime** — `ensureAllPermissions()` handles that via `react-native-permissions`.\n\n### Minimal checklist before shipping\n\n- [ ] iOS: all required `NS*UsageDescription` keys present with real copy (not empty strings).\n- [ ] iOS: `setup_permissions([...])` in `Podfile` includes **Camera** + **PhotoLibrary** (and **PhotoLibraryAddOnly** if you save to the library).\n- [ ] Android: `CAMERA` + gallery read permissions for both API 33+ and API ≤ 32 as shown above.\n- [ ] JS: call `ensureAllPermissions()` before `openProfileImageCapture` / `openProfileImageGallery`.\n\n### Gallery-only apps (optional)\n\nIf your product **never** opens the in-app camera, you may omit `NSCameraUsageDescription` and `CAMERA` from the manifest — but you still need **photo library** entries for `openProfileImageGallery`.\n\n---\n\n## Setup\n\n### 1. Babel Configuration\n\nAdd the Reanimated plugin (must be last):\n\n```js\n// babel.config.js\nmodule.exports = {\n  presets: ['module:@react-native/babel-preset'],\n  plugins: ['react-native-reanimated/plugin'],\n};\n```\n\n### 2. App Root Setup\n\nWrap your app with `GestureHandlerRootView`:\n\n```tsx\nimport { GestureHandlerRootView } from 'react-native-gesture-handler';\n\nexport default function App() {\n  return (\n    \u003cGestureHandlerRootView style={{ flex: 1 }}\u003e\n      \u003cYourApp /\u003e\n    \u003c/GestureHandlerRootView\u003e\n  );\n}\n```\n\nFor best results, also import `react-native-gesture-handler` at the very top of your entry file (`index.js`):\n\n```js\nimport 'react-native-gesture-handler';\nimport { AppRegistry } from 'react-native';\n// ...\n```\n\n### 3. iOS native setup\n\nSee **[Permissions → iOS — Info.plist](#ios--infoplist)** and **[Permissions → iOS — Podfile](#ios--podfile)** above.\n\n### 4. Android native setup\n\nSee **[Permissions → Android — AndroidManifest.xml](#android--androidmanifestxml)** above.\n\n---\n\n## Quick Start\n\n### 1. Mount the Host Component\n\nAdd `ProfileImagePickerHost` once near your app root:\n\n```tsx\nimport { ProfileImagePickerHost } from 'react-native-smart-capture';\n\nfunction AppRoot() {\n  return (\n    \u003c\u003e\n      \u003cApp /\u003e\n      \u003cProfileImagePickerHost /\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### 2. Open the Picker\n\n```tsx\nimport {\n  ensureAllPermissions,\n  openProfileImageCapture,\n  openProfileImageGallery,\n} from 'react-native-smart-capture';\n\nconst handleCapturePhoto = async () =\u003e {\n  const { allGranted } = await ensureAllPermissions();\n\n  if (!allGranted) {\n    Alert.alert('Permission Required', 'Please grant camera and photo permissions.');\n    return;\n  }\n\n  openProfileImageCapture({\n    onSuccess: (image) =\u003e {\n      console.log('Captured image:', image.uri);\n    },\n    onCancel: () =\u003e {\n      console.log('User cancelled');\n    },\n    onError: (error) =\u003e {\n      console.error('Error:', error);\n    },\n    options: {\n      cropShape: 'circle', // or 'rectangle'\n      enableBase64: false,\n      compression: 0.85,\n      maxOutputSize: 1024,\n    },\n  });\n};\n\nconst handleSelectFromGallery = async () =\u003e {\n  const { allGranted } = await ensureAllPermissions();\n  if (!allGranted) return;\n\n  openProfileImageGallery({\n    onSuccess: (image) =\u003e console.log(image.uri),\n    options: { cropShape: 'rectangle' },\n  });\n};\n```\n\n---\n\n## API Reference\n\n### Permission Helpers\n\n```tsx\nimport {\n  checkCameraPermission,\n  requestCameraPermission,\n  checkGalleryPermission,\n  requestGalleryPermission,\n  ensureAllPermissions,\n  openSettings,\n} from 'react-native-smart-capture';\n```\n\n| Function | Returns | Description |\n|----------|---------|-------------|\n| `checkCameraPermission()` | `Promise\u003cPermissionStatus\u003e` | Check camera permission without prompting |\n| `requestCameraPermission()` | `Promise\u003cPermissionStatus\u003e` | Request camera permission |\n| `checkGalleryPermission()` | `Promise\u003cPermissionStatus\u003e` | Check gallery permission without prompting |\n| `requestGalleryPermission()` | `Promise\u003cPermissionStatus\u003e` | Request gallery permission |\n| `ensureAllPermissions()` | `Promise\u003cPermissionResult\u003e` | Check \u0026 request both permissions |\n| `openSettings()` | `Promise\u003cvoid\u003e` | Open app settings for manual permission grant |\n\n**PermissionStatus:** `'granted' | 'denied' | 'blocked' | 'unavailable'`\n\n**PermissionResult:**\n\n```ts\n{\n  camera: PermissionStatus;\n  gallery: PermissionStatus;\n  allGranted: boolean;\n}\n```\n\n---\n\n### Imperative API\n\n#### `openProfileImageCapture(args)`\n\nOpens the camera for photo capture.\n\n```ts\nopenProfileImageCapture({\n  onSuccess: (image: ProfileImageResult) =\u003e void,\n  onCancel?: () =\u003e void,\n  onError?: (error: Error) =\u003e void,\n  options?: ProfileImagePickerOptions,\n});\n```\n\n#### `openProfileImageGallery(args)`\n\nOpens the gallery for image selection.\n\n```ts\nopenProfileImageGallery({\n  onSuccess: (image: ProfileImageResult) =\u003e void,\n  onCancel?: () =\u003e void,\n  onError?: (error: Error) =\u003e void,\n  options?: ProfileImagePickerOptions,\n});\n```\n\n#### `openProfileImagePicker(args)`\n\nAlias for `openProfileImageCapture` (backward compatibility).\n\n---\n\n### Component API\n\n```tsx\nimport { ProfileImagePicker } from 'react-native-smart-capture';\n\n\u003cProfileImagePicker\n  visible={isVisible}\n  source=\"camera\" // or 'gallery'\n  onClose={() =\u003e setIsVisible(false)}\n  onImageSelected={(image) =\u003e handleImage(image)}\n  onError={(error) =\u003e console.error(error)}\n  options={{\n    cropShape: 'circle',\n    compression: 0.85,\n  }}\n/\u003e\n```\n\n---\n\n### Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `cropShape` | `'circle' \\| 'rectangle'` | `'circle'` | Shape of the crop overlay |\n| `enableBase64` | `boolean` | `false` | Include base64 in result |\n| `compression` | `number` | `0.85` | JPEG quality (0–1) |\n| `maxOutputSize` | `number` | `1024` | Max output dimension in pixels |\n| `ui` | `SmartCaptureUIConfig` | – | Customize strings, colors, fonts |\n\n---\n\n### Result Type\n\n```ts\ntype ProfileImageResult = {\n  uri: string;           // Local file URI\n  base64?: string;       // Base64 string (if enableBase64: true)\n  fileName?: string;     // Derived filename\n  type?: string;         // Always 'image/jpeg'\n  width?: number;        // Output width in pixels\n  height?: number;       // Output height in pixels\n};\n```\n\n---\n\n### UI Customization\n\n```ts\nopenProfileImageCapture({\n  onSuccess: handleSuccess,\n  options: {\n    ui: {\n      strings: {\n        previewTitle: 'Crop Your Photo',\n        previewCancel: 'Back',\n        previewUsePhoto: 'Save',\n        cameraPreparing: 'Loading camera...',\n        cameraCancel: 'Close',\n        cameraFlip: 'Switch',\n      },\n      colors: {\n        primary: '#007AFF',\n        background: '#000000',\n        overlay: 'rgba(0,0,0,0.6)',\n      },\n      fonts: {\n        regular: 'System',\n        medium: 'System',\n        bold: 'System',\n      },\n    },\n  },\n});\n```\n\n**Available String Keys:**\n\n| Key | Default | Description |\n|-----|---------|-------------|\n| `cameraPreparing` | \"Preparing camera...\" | Camera loading state |\n| `cameraCancel` | \"Cancel\" | Camera cancel button |\n| `cameraFlip` | \"Flip\" | Camera flip button |\n| `previewTitle` | \"Preview Image\" | Preview screen title |\n| `previewCancel` | \"Cancel\" | Preview cancel button |\n| `previewUsePhoto` | \"Use Photo\" | Preview confirm button |\n| `previewLoading` | \"Loading image...\" | Image loading state |\n| `previewTryAgain` | \"Try again\" | Error retry button |\n\n---\n\n## Example App\n\nA complete example app is included in the `example/` directory.\n\n```bash\n# Clone the repo\ngit clone https://github.com/SwiftCrew/react-native-smart-capture.git\ncd react-native-smart-capture\n\n# Install dependencies\nyarn install\ncd example \u0026\u0026 yarn install\n\n# iOS\ncd ios \u0026\u0026 pod install \u0026\u0026 cd ..\nyarn ios\n\n# Android\nyarn android\n```\n\n---\n\n## Use Cases\n\n- User profile photo upload\n- Avatar selection in social apps\n- KYC / identity verification\n- Document photo capture\n- Product image upload\n\n---\n\n## Troubleshooting\n\n**`TypeError: undefined is not an object (evaluating 'photo.path')` when capturing**\n\nYou probably have `react-native-vision-camera@5.x` installed, which changed its photo-capture API. Pin to v4:\n\n```bash\nyarn add react-native-vision-camera@^4\ncd ios \u0026\u0026 pod install\n```\n\n**`Unable to find a specification for 'RNWorklets' depended upon by 'RNReanimated'`**\n\nYou're on Reanimated 4 without its companion package. Install `react-native-worklets`:\n\n```bash\nyarn add react-native-worklets\ncd ios \u0026\u0026 pod install\n```\n\n**iOS — `pod install` fails with `Unicode Normalization not appropriate for ASCII-8BIT`**\n\nSet UTF-8 in your shell before running pod install:\n\n```bash\nexport LANG=en_US.UTF-8\nexport LC_ALL=en_US.UTF-8\ncd ios \u0026\u0026 pod install\n```\n\n**Permission dialog never appears on iOS**\n\nMake sure the corresponding `NS*UsageDescription` key is present in `Info.plist` **and** the matching pod is registered in `setup_permissions([...])` in your `Podfile`. Run `pod install` after editing.\n\n**Permission dialog never appears on Android**\n\nMake sure `CAMERA` / `READ_MEDIA_IMAGES` / `READ_EXTERNAL_STORAGE` (with `maxSdkVersion=\"32\"`) are declared in `AndroidManifest.xml`, and that you call `ensureAllPermissions()` (or `requestCameraPermission()` / `requestGalleryPermission()`) at runtime.\n\n---\n\n## Contributing\n\nContributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a PR.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n---\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a list of changes.\n\n---\n\n## License\n\nMIT © [SwiftCrew](https://github.com/SwiftCrew)\n\n---\n\n## Support\n\n- [Report a bug](https://github.com/SwiftCrew/react-native-smart-capture/issues)\n- [Request a feature](https://github.com/SwiftCrew/react-native-smart-capture/issues)\n- Star the repo if you find it useful!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswiftcrew%2Freact-native-smart-capture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswiftcrew%2Freact-native-smart-capture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswiftcrew%2Freact-native-smart-capture/lists"}