Skip to content

Commit d5b53d6

Browse files
committed
Fix conflicts and update view support table
2 parents 669fa03 + 5d5ccc2 commit d5b53d6

File tree

9 files changed

+202
-54
lines changed

9 files changed

+202
-54
lines changed

README.md

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Snapshot a React Native view and save it to an image.
1010
## Usage
1111

1212
```js
13-
import RNViewShot from "react-native-view-shot";
13+
import { takeSnapshot } from "react-native-view-shot";
1414

15-
RNViewShot.takeSnapshot(viewRef, {
15+
takeSnapshot(viewRef, {
1616
format: "jpeg",
1717
quality: 0.8
1818
})
@@ -28,34 +28,68 @@ RNViewShot.takeSnapshot(viewRef, {
2828

2929
## Full API
3030

31-
### `RNViewShot.takeSnapshot(view, options)`
31+
### `takeSnapshot(view, options)`
3232

3333
Returns a Promise of the image URI.
3434

3535
- **`view`** is a reference to a React Native component.
3636
- **`options`** may include:
37-
- **`width`** / **`height`** *(number)*: the width and height of the image to capture.
37+
- **`width`** / **`height`** *(number)*: the width and height of the final image (resized from the View bound. don't provide it if you want the original pixel size).
3838
- **`format`** *(string)*: either `png` or `jpg`/`jpeg` or `webm` (Android). Defaults to `png`.
3939
- **`quality`** *(number)*: the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpeg)
4040
- **`result`** *(string)*, the method you want to use to save the snapshot, one of:
4141
- `"file"` (default): save to a temporary file *(that will only exist for as long as the app is running)*.
4242
- `"base64"`: encode as base64 and returns the raw string. Use only with small images as this may result of lags (the string is sent over the bridge). *N.B. This is not a data uri, use `data-uri` instead*.
4343
- `"data-uri"`: same as `base64` but also includes the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) header.
44-
- **`filename`** *(string)*: the name of the generated file if any (Android only). Defaults to `ReactNative_snapshot_image_${timestamp}`.
45-
- **`snapshotContentContainer`** *(bool)*: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height. (Android only)
44+
- **`path`** *(string)*: The absolute path where the file get generated. See *`dirs` constants* for more information.
45+
- **`snapshotContentContainer`** *(bool)*: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height.
46+
47+
### `dirs` constants
48+
49+
By default, takeSnapshot will export in a temporary folder and the snapshot file will be deleted as soon as the app leaves. If you use the `path` option, you make the snapshot file more permanent and at a specific file location. To make file location more 'universal', the library exports some classic directory constants:
50+
51+
```js
52+
import { takeSnapshot, dirs } from "react-native-view-shot";
53+
// cross platform dirs:
54+
const { CacheDir, DocumentDir, MainBundleDir, MovieDir, MusicDir, PictureDir } = dirs;
55+
// only available Android:
56+
const { DCIMDir, DownloadDir, RingtoneDir, SDCardDir } = dirs;
57+
58+
takeSnapshot(viewRef, { path: PictureDir+"/foo.png" })
59+
.then(
60+
uri => console.log("Image saved to", uri),
61+
error => console.error("Oops, snapshot failed", error)
62+
);
63+
```
64+
65+
## Supported views
66+
67+
Model tested: iPhone 6 (iOS), Nexus 5 (Android).
68+
69+
| System | iOS | Android | Windows |
70+
|--------------------|--------------------|-------------------|-------------------|
71+
| View,Text,Image,.. | YES | YES | YES |
72+
| WebView | YES | YES<sup>1</sup> | YES |
73+
| gl-react v2 | YES | NO<sup>2</sup> | NO<sup>3</sup> |
74+
| react-native-video | NO | NO | NO
75+
| react-native-maps | YES | [NO](https://github.com/gre/react-native-view-shot/issues/36) | NO<sup>3</sup>
76+
77+
>
78+
1. Only supported by wrapping a `<View collapsable={false}>` parent and snapshotting it.
79+
2. It returns an empty image (not a failure Promise).
80+
3. Component itself lacks platform support.
4681

4782
## Caveats
4883

4984
Snapshots are not guaranteed to be pixel perfect. It also depends on the platform. Here is some difference we have noticed and how to workaround.
5085

51-
- Support of special components like Video / GL views remains untested.
86+
- Support of special components like Video / GL views is not guaranteed to work. In case of failure, the `takeSnapshot` promise gets rejected (the library won't crash).
5287
- It's preferable to **use a background color on the view you rasterize** to avoid transparent pixels and potential weirdness that some border appear around texts.
5388

5489
### specific to Android implementation
5590

56-
- you need to make sure `collapsable` is set to `false` if you want to snapshot a **View**. Otherwise that view won't reflect any UI View. ([found by @gaguirre](https://github.com/gre/react-native-view-shot/issues/7#issuecomment-245302844))
57-
- if you want to share out the screenshoted file, you will have to copy it somewhere first so it's accessible to an Intent, see comment: https://github.com/gre/react-native-view-shot/issues/11#issuecomment-251080804 .
58-
- if you implement a third party library and want to get back a File, you must first resolve the `Uri`. the `file` result returns an `Uri` so it's consistent with iOS and you can give it to `Image.getSize` for instance.
91+
- you need to make sure `collapsable` is set to `false` if you want to snapshot a **View**. Some content might even need to be wrapped into such `<View collapsable={false}>` to actually make them snapshotable! Otherwise that view won't reflect any UI View. ([found by @gaguirre](https://github.com/gre/react-native-view-shot/issues/7#issuecomment-245302844))
92+
- if you implement a third party library and want to get back a File, you must first resolve the `Uri`. (the `file` result returns an `Uri` so it's consistent with iOS and can be given to APIs like `Image.getSize`)
5993

6094
## Getting started
6195

@@ -100,7 +134,6 @@ react-native link react-native-view-shot
100134
3. In Visual Studio, in the solution explorer, right click on your Application project then select `Add``Reference`
101135
4. Under the projects tab select `RNViewShot` (UWP) or `RNViewShot.Net46` (WPF)
102136

103-
104137
## Thanks
105138

106139
- To initial iOS work done by @jsierles in https://github.com/jsierles/react-native-view-snapshot

android/build.gradle

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ android {
2525
}
2626
}
2727

28-
repositories {
29-
mavenCentral()
30-
maven {
31-
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
32-
url "$projectDir/../../../node_modules/react-native/android"
28+
allprojects {
29+
repositories {
30+
mavenLocal()
31+
jcenter()
32+
maven {
33+
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
34+
url "$rootDir/../node_modules/react-native/android"
35+
}
3336
}
3437
}
3538

3639
dependencies {
37-
compile "com.facebook.react:react-native:+" // From node_modules
40+
compile 'com.facebook.react:react-native:+'
3841
}

android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.Context;
55
import android.graphics.Bitmap;
66
import android.os.AsyncTask;
7+
import android.os.Environment;
78
import android.util.DisplayMetrics;
89
import android.view.View;
910

@@ -22,6 +23,8 @@
2223
import java.io.File;
2324
import java.io.FilenameFilter;
2425
import java.io.IOException;
26+
import java.util.HashMap;
27+
import java.util.Map;
2528

2629
public class RNViewShotModule extends ReactContextBaseJavaModule {
2730

@@ -37,6 +40,11 @@ public String getName() {
3740
return "RNViewShot";
3841
}
3942

43+
@Override
44+
public Map<String, Object> getConstants() {
45+
return getSystemFolders(this.getReactApplicationContext());
46+
}
47+
4048
@Override
4149
public void onCatalystInstanceDestroy() {
4250
super.onCatalystInstanceDestroy();
@@ -66,17 +74,25 @@ public void takeSnapshot(int tag, ReadableMap options, Promise promise) {
6674
String result = options.hasKey("result") ? options.getString("result") : "file";
6775
Boolean snapshotContentContainer = options.hasKey("snapshotContentContainer") ? options.getBoolean("snapshotContentContainer") : false;
6876
try {
69-
String name = options.hasKey("filename") ? options.getString("filename") : null;
70-
File tmpFile = "file".equals(result) ? createTempFile(getReactApplicationContext(), format, name) : null;
77+
File file = null;
78+
if ("file".equals(result)) {
79+
if (options.hasKey("path")) {
80+
file = new File(options.getString("path"));
81+
file.createNewFile();
82+
}
83+
else {
84+
file = createTempFile(getReactApplicationContext(), format);
85+
}
86+
}
7187
UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
72-
uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, tmpFile, result, snapshotContentContainer, promise));
88+
uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer, promise));
7389
}
7490
catch (Exception e) {
7591
promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
7692
}
7793
}
7894

79-
private static final String TEMP_FILE_PREFIX = "ReactNative_snapshot_image_";
95+
private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
8096

8197
/**
8298
* Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
@@ -116,11 +132,30 @@ public boolean accept(File dir, String filename) {
116132
}
117133
}
118134

135+
static private Map<String, Object> getSystemFolders(ReactApplicationContext ctx) {
136+
Map<String, Object> res = new HashMap<>();
137+
res.put("CacheDir", ctx.getCacheDir().getAbsolutePath());
138+
res.put("DCIMDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath());
139+
res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
140+
res.put("DownloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
141+
res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
142+
res.put("MovieDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath());
143+
res.put("MusicDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath());
144+
res.put("PictureDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
145+
res.put("RingtoneDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath());
146+
String state;
147+
state = Environment.getExternalStorageState();
148+
if (state.equals(Environment.MEDIA_MOUNTED)) {
149+
res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath());
150+
}
151+
return res;
152+
}
153+
119154
/**
120155
* Create a temporary file in the cache directory on either internal or external storage,
121156
* whichever is available and has more free space.
122157
*/
123-
private File createTempFile(Context context, String ext, String name)
158+
private File createTempFile(Context context, String ext)
124159
throws IOException {
125160
File externalCacheDir = context.getExternalCacheDir();
126161
File internalCacheDir = context.getCacheDir();
@@ -139,12 +174,6 @@ else if (internalCacheDir == null) {
139174
}
140175
String suffix = "." + ext;
141176
File tmpFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
142-
if (name != null) {
143-
File renamed = new File(cacheDir, name + suffix);
144-
tmpFile.renameTo(renamed);
145-
return renamed;
146-
}
147-
148177
return tmpFile;
149178
}
150179

android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private void captureView (View view, OutputStream os) {
124124
}
125125

126126
//evaluate real height
127-
if (this.snapshotContentContainer){
127+
if (snapshotContentContainer) {
128128
h=0;
129129
ScrollView scrollView = (ScrollView)view;
130130
for (int i = 0; i < scrollView.getChildCount(); i++) {

index.js

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
//@flow
2-
32
import { NativeModules, findNodeHandle } from "react-native";
4-
53
const { RNViewShot } = NativeModules;
64

5+
export const dirs = {
6+
// cross platform
7+
CacheDir: RNViewShot.CacheDir,
8+
DocumentDir: RNViewShot.DocumentDir,
9+
MainBundleDir: RNViewShot.MainBundleDir,
10+
MovieDir: RNViewShot.MovieDir,
11+
MusicDir: RNViewShot.MusicDir,
12+
PictureDir: RNViewShot.PictureDir,
13+
// only Android
14+
DCIMDir: RNViewShot.DCIMDir,
15+
DownloadDir: RNViewShot.DownloadDir,
16+
RingtoneDir: RNViewShot.RingtoneDir,
17+
SDCardDir: RNViewShot.SDCardDir,
18+
};
19+
720
export function takeSnapshot(
821
view: number | ReactElement<any>,
9-
options ?: {
10-
width ?: number;
11-
height ?: number;
12-
filename ?: string;
13-
format ?: "png" | "jpg" | "jpeg" | "webm";
14-
quality ?: number;
15-
result ?: "file" | "base64" | "data-uri";
16-
}
22+
options?: {
23+
width?: number,
24+
height?: number,
25+
path?: string,
26+
format?: "png" | "jpg" | "jpeg" | "webm",
27+
quality?: number,
28+
result?: "file" | "base64" | "data-uri",
29+
snapshotContentContainer?: bool
30+
} = {}
1731
): Promise<string> {
1832
if (typeof view !== "number") {
1933
const node = findNodeHandle(view);
@@ -23,4 +37,4 @@ export function takeSnapshot(
2337
return RNViewShot.takeSnapshot(view, options);
2438
}
2539

26-
export default { takeSnapshot };
40+
export default { takeSnapshot, dirs };

0 commit comments

Comments
 (0)