react-native-viewdrop-ios
v0.2.4
Published
ViewDrop is a module for React Native that will allow View to use a native iOS feature to transfer pictures, videos, files, and more through a simple Drag & Drop action.
Readme
react-native-viewdrop-ios
ViewDrop is a React Native module that turns any View into a native iOS drag-and-drop target. Drop images, videos, audio, documents, or any other file — one at a time or in batches.

Requirements
- React Native >= 0.71.0
- iOS >= 13.0
- Supports both Old Architecture and New Architecture (Fabric)
Installation
npm install react-native-viewdrop-ios
# or
yarn add react-native-viewdrop-iosFor New Architecture projects, no extra setup is needed — the library auto-detects Fabric.
For projects using use_modular_headers! in their Podfile (common with New Arch), ensure it's set globally or for this pod.
Supported platforms
| Platform | Status | |----------|--------| | iOS | ✅ Supported (Old Arch + Fabric) | | macOS | 🚧 In development | | Android / Web / tvOS / visionOS | ❌ Not planned |
Quick start
import { ViewDrop } from 'react-native-viewdrop-ios';
<ViewDrop
style={{ flex: 1 }}
onImageReceived={(base64) => console.log(base64)}
onDropItemDetected={() => console.log('drag started')}
>
<Text>Drop files here</Text>
</ViewDrop>Props
Event callbacks
| Prop | Type | Description |
|------|------|-------------|
| onDropItemDetected | () => void | Fires when a drag session enters the view. Use it to animate the drop zone. |
| onImageReceived | (image: string) => void | Fires when a single image is dropped. image is a base64 data-URI. Only active when isEnableMultiDropping is false. |
| onVideoReceived | ({ fileName, fullUrl }) => void | Fires when a single video is dropped. fullUrl is a temporary file path. Only active when isEnableMultiDropping is false. |
| onAudioReceived | ({ fileName, fullUrl }) => void | Fires when a single audio file is dropped. Only active when isEnableMultiDropping is false. |
| onFileReceived | ({ fileName, fileUrl, typeIdentifier }) => void | Fires for any other file type (PDF, ZIP, etc.) dropped as a single item. Only active when isEnableMultiDropping is false. |
| onFileItemsReceived | (data: Record<'image'\|'video'\|'audio'\|'file', FileInfo[]>) => void | Fires when isEnableMultiDropping is true. Contains all dropped files grouped by category. Works for single-file drops too. |
Filter props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| fileTypes | ('image' \| 'video' \| 'audio' \| 'file')[] | all | Accepted file categories. Uses Apple's UTType system under the hood. |
| whiteListExtensions | string[] | — | Only files whose extension is in this list are accepted. |
| blackListExtensions | string[] | — | Files whose extension is in this list are rejected. |
| isEnableMultiDropping | boolean | false | Routes all drops (including single-file) through onFileItemsReceived. |
| allowPartialDrop | boolean | false | Requires isEnableMultiDropping. Changes filter behaviour from session-level to per-file (see below). |
Image resize props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| imageResize | ImageResizeConfig | — | Downscale and/or recompress images before they are delivered to onImageReceived. Has no effect on videos, audio, or generic files. |
type ImageResizeConfig = {
maxWidth?: number; // max output width in pixels; 0 = unlimited (default: 0)
maxHeight?: number; // max output height in pixels; 0 = unlimited (default: 0)
quality?: number; // JPEG compression 0.0–1.0 (default: 1.0)
mode?: 'aspectFit' | 'aspectFill'; // scaling mode (default: 'aspectFit')
};- Only fields you specify are applied; omitted fields use their defaults.
- When both
maxWidthandmaxHeightare0(orimageResizeis not set) images are delivered at their original size. aspectFit(default) fits the image inside the bounding box, preserving aspect ratio.aspectFillfills the bounding box, cropping if necessary.- Output encoding: JPEG when
quality < 1.0, PNG otherwise.
Filtering in depth
Session-level vs per-file filtering
By default (allowPartialDrop = false) filtering is session-level: the entire drop is either accepted or rejected as a unit. If any single dragged item fails a filter, the whole session is rejected and iOS shows a red "forbidden" indicator.
When allowPartialDrop = true filtering becomes per-file: the drop session is always accepted visually, but individual files that fail the filters are silently removed from the result. This is useful when users drag a mixed batch of files.
allowPartialDrop = false (default) allowPartialDrop = true
───────────────────────────────── ─────────────────────────────────
[a.pdf, b.exe, c.docx] [a.pdf, b.exe, c.docx]
blackList = ['exe'] blackList = ['exe']
│ │
┌──────▼──────┐ ┌──────────▼──────────┐
│ any .exe? │ │ filter per file │
└──────┬──────┘ │ a.pdf → ✅ pass │
yes │ b.exe → ❌ skip │
│ │ c.docx → ✅ pass │
┌──────▼───────┐ └──────────┬──────────┘
│ FORBIDDEN │ │
│ (grey icon) │ onFileItemsReceived({ file: [a.pdf, c.docx] })
└──────────────┘fileTypes
Filters by Apple UTType category. If not specified, all types are accepted.
// Accept only images and generic documents
<ViewDrop fileTypes={['image', 'file']} ... />whiteListExtensions — allow-list
Only files whose extension is in the list pass. All other extensions are blocked.
// Accept only PDF, DOCX, and TXT files
<ViewDrop whiteListExtensions={['pdf', 'docx', 'txt']} ... />With allowPartialDrop: non-matching files are silently dropped from the result instead of rejecting the whole session.
blackListExtensions — block-list
Files whose extension matches the list are rejected. All other extensions pass.
// Block executables and shell scripts
<ViewDrop blackListExtensions={['exe', 'bat', 'sh', 'cmd']} ... />With allowPartialDrop: matching files are removed from the result; the rest are delivered normally.
Combining filters
All active filters are applied together with AND logic. A file must satisfy every specified constraint to pass:
- File category must match
fileTypes(if specified) - AND file extension must be in
whiteListExtensions(if specified) - AND file extension must not be in
blackListExtensions(if specified)
fileTypes + whiteListExtensions
fileTypes coarsely pre-filters by Apple UTType (image/video/audio/file). whiteListExtensions then narrows to specific extensions within that category.
fileTypes=['image'] + whiteListExtensions=['png','jpg']
────────────────────────────────────────────────────────
photo.png → image ✅ → png ✅ → PASS
photo.heic → image ✅ → heic ❌ → REJECT
doc.pdf → file ❌ → REJECTTypical use-cases:
- Accept only raster images, block HEIC/RAW:
fileTypes=['image'] + whiteListExtensions=['png','jpg','jpeg'] - Accept only specific document formats:
fileTypes=['file'] + whiteListExtensions=['pdf','docx','xlsx'] - Accept audio but only lossless:
fileTypes=['audio'] + whiteListExtensions=['flac','wav','aiff']
fileTypes + blackListExtensions
fileTypes accepts the whole category; blackListExtensions carves out unwanted extensions inside it.
fileTypes=['image'] + blackListExtensions=['heic','heif']
──────────────────────────────────────────────────────────
photo.png → image ✅ → not heic ✅ → PASS
photo.heic → image ✅ → heic ❌ → REJECT
video.mp4 → video ❌ → REJECTTypical use-cases:
- Accept all images except HEIC:
fileTypes=['image'] + blackListExtensions=['heic','heif'] - Accept all documents except archives:
fileTypes=['file'] + blackListExtensions=['zip','rar','7z']
All three together
fileTypes + whiteListExtensions + blackListExtensions can be combined, though whitelist and blacklist on the same extension set is unusual. A more realistic pattern is using fileTypes for category selection and one list for extension refinement.
Multi-file dropping
Enable with isEnableMultiDropping. All results — even a single-file drop — arrive in onFileItemsReceived grouped by category:
import { ViewDrop, MapKeysMultiItems, type FileInfo } from 'react-native-viewdrop-ios';
<ViewDrop
isEnableMultiDropping
onFileItemsReceived={(data) => {
// data.image → FileInfo[] (PNG, JPEG, HEIC, …)
// data.video → FileInfo[] (MP4, MOV, …)
// data.audio → FileInfo[] (MP3, AAC, …)
// data.file → FileInfo[] (PDF, ZIP, DOCX, …)
console.log(data);
}}
/>FileInfo shape:
type FileInfo = {
fileName: string; // e.g. "photo.png"
fileUrl: string; // absolute path to a temporary copy on disk (no "file://" prefix)
typeIdentifier: string; // UTType category: "image" | "video" | "audio" | "file"
};
fileUrlis a raw file-system path, not a URI — it does not include afile://scheme. To display a file in a React Native<Image>or pass it to a media player, prepend the scheme manually:<Image source={{ uri: `file://${fileUrl}` }} />The file is a temporary copy managed by iOS and may be deleted after the drop session ends. Copy it to a permanent location (e.g. the app's Documents directory) if you need it beyond the current screen.
Examples
Accept any file — single drop
<ViewDrop
onImageReceived={(base64) => setImage(base64)}
onVideoReceived={({ fullUrl }) => setVideo(fullUrl)}
onAudioReceived={({ fullUrl }) => setAudio(fullUrl)}
onFileReceived={({ fileName, fileUrl }) => console.log(fileName, fileUrl)}
onDropItemDetected={() => setHint('Drop!')}
/>Accept any file — multi drop
<ViewDrop
isEnableMultiDropping
onFileItemsReceived={(data) => {
data.image?.forEach((f) => console.log('image:', f.fileName));
data.video?.forEach((f) => console.log('video:', f.fileName));
data.file?.forEach((f) => console.log('file:', f.fileName));
}}
/>Allow only images (whitelist by type)
<ViewDrop
fileTypes={['image']}
onImageReceived={(base64) => setImage(base64)}
/>Allow only PNG and JPEG (whitelist by extension)
If any dragged file is not a PNG or JPEG the whole drop is rejected (red indicator).
<ViewDrop
whiteListExtensions={['png', 'jpg', 'jpeg']}
onImageReceived={(base64) => setImage(base64)}
/>Block executables — reject whole batch
// If the user drags even one .exe, the entire drop is rejected.
<ViewDrop
isEnableMultiDropping
blackListExtensions={['exe', 'bat', 'sh']}
onFileItemsReceived={(data) => console.log(data)}
/>Block executables — filter silently (allowPartialDrop)
// .exe files are removed; the rest arrive normally.
<ViewDrop
isEnableMultiDropping
allowPartialDrop
blackListExtensions={['exe', 'bat', 'sh']}
onFileItemsReceived={(data) => console.log(data)}
/>Accept only PDF from a mixed batch (allowPartialDrop + whitelist)
// Drop [a.pdf, b.png, c.txt] → only a.pdf arrives in the callback.
<ViewDrop
isEnableMultiDropping
allowPartialDrop
whiteListExtensions={['pdf']}
onFileItemsReceived={(data) => {
// data.file = [{ fileName: 'a.pdf', ... }]
}}
/>fileTypes + whiteListExtensions — only PNG/JPEG images (strict)
The entire drop session is rejected if any file is not a PNG or JPEG image.
<ViewDrop
isEnableMultiDropping
fileTypes={['image']}
whiteListExtensions={['png', 'jpg', 'jpeg']}
onFileItemsReceived={(data) => {
// data.image contains only PNG/JPEG files
}}
/>fileTypes + whiteListExtensions + allowPartialDrop — filter PNG/JPEG per-file
Drop session is always accepted. Non-PNG/JPEG files and non-image files are silently removed from the result.
// Drop [photo.png, photo.heic, doc.pdf]
// → onFileItemsReceived receives only photo.png
<ViewDrop
isEnableMultiDropping
allowPartialDrop
fileTypes={['image']}
whiteListExtensions={['png', 'jpg', 'jpeg']}
onFileItemsReceived={(data) => {
// data.image = [{ fileName: 'photo.png', ... }]
}}
/>fileTypes + blackListExtensions — images, but block HEIC (strict)
Drops containing HEIC files or non-image files are rejected at the session level.
<ViewDrop
isEnableMultiDropping
fileTypes={['image']}
blackListExtensions={['heic', 'heif']}
onFileItemsReceived={(data) => {
// data.image contains any image format except HEIC/HEIF
}}
/>fileTypes + blackListExtensions + allowPartialDrop — remove HEIC per-file
Drop session is always accepted. HEIC/HEIF images are filtered out; all other image formats pass through.
// Drop [photo.png, photo.heic, shot.heif]
// → onFileItemsReceived receives only photo.png
<ViewDrop
isEnableMultiDropping
allowPartialDrop
fileTypes={['image']}
blackListExtensions={['heic', 'heif']}
onFileItemsReceived={(data) => {
// data.image = [{ fileName: 'photo.png', ... }]
}}
/>fileTypes + whiteListExtensions — documents only (PDF, DOCX, XLSX)
<ViewDrop
isEnableMultiDropping
fileTypes={['file']}
whiteListExtensions={['pdf', 'docx', 'xlsx']}
onFileItemsReceived={(data) => {
data.file?.forEach((f) => console.log(f.fileName));
}}
/>Resize and compress images before delivery
<ViewDrop
imageResize={{ maxWidth: 800, maxHeight: 800, quality: 0.8 }}
onImageReceived={(base64) => setImage(base64)}
/>Drop an image → it is scaled down to fit within 800×800 px (aspectFit) and JPEG-compressed at 80% quality before being base64-encoded and sent to the callback.
// Only limit width, keep quality lossless
<ViewDrop
imageResize={{ maxWidth: 1200 }}
onImageReceived={(base64) => setImage(base64)}
/>
// Lossless PNG, no size limit — same as not passing imageResize at all
<ViewDrop
imageResize={{ quality: 1.0 }}
onImageReceived={(base64) => setImage(base64)}
/>
// Crop to fill a square thumbnail
<ViewDrop
imageResize={{ maxWidth: 400, maxHeight: 400, mode: 'aspectFill', quality: 0.9 }}
onImageReceived={(base64) => setImage(base64)}
/>Notes
onImageReceived,onVideoReceived,onAudioReceived,onFileReceivedare only called whenisEnableMultiDroppingis false. When multi-dropping is enabled, useonFileItemsReceivedfor everything.allowPartialDrophas no effect unlessisEnableMultiDroppingis also enabled.fileUrlinFileInfois a raw path without afile://scheme (e.g./private/var/.../photo.png). Use`file://${fileUrl}`when passing it to<Image>, video players, or any API that expects a URI. The file is a temporary copy managed by iOS — copy it to a permanent location if you need it to persist.- Extensions in
whiteListExtensions/blackListExtensionsare case-insensitive ('PDF'and'pdf'are the same). fileTypesandwhiteListExtensions/blackListExtensionsoperate on different mechanisms and cannot substitute for each other.fileTypesuses Apple's UTType conformance system — formats not registered in iOS (e.g..ogg) will never conform tokUTTypeAudio, so adding'ogg'towhiteListExtensionswill not help whenfileTypes=['audio']is set. For non-standard or niche formats, omitfileTypesentirely and rely solely onwhiteListExtensionsto control what is accepted.
Future plans
- Drop preview / badge customisation
- macOS support
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.
License
MIT
Made with create-react-native-library
