Skip to content

Commit 796c341

Browse files
authored
Merge pull request #96 from foxmicha/master
add captureScreen
2 parents d90b2c1 + 476b446 commit 796c341

File tree

6 files changed

+71
-6
lines changed

6 files changed

+71
-6
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,27 @@ This method release a previously captured `uri`. For tmpfile it will clean them
110110

111111
NB: the tmpfile captures are automatically cleaned out after the app closes, so you might not have to worry about this unless advanced usecases. The `ViewShot` component will use it each time you capture more than once (useful for continuous capture to not leak files).
112112

113+
## `captureScreen()` Android and iOS Only
114+
115+
```js
116+
import { captureScreen } from "react-native-view-shot";
117+
118+
captureScreen({
119+
format: "jpg",
120+
quality: 0.8
121+
})
122+
.then(
123+
uri => console.log("Image saved to", uri),
124+
error => console.error("Oops, snapshot failed", error)
125+
);
126+
```
127+
128+
This method will capture the contents of the currently displayed screen as a native hardware screenshot. It does not require a ref input, as it does not work at the view level. This means that ScrollViews will not be captured in their entirety - only the portions currently visible to the user.
129+
130+
Returns a Promise of the image URI.
131+
132+
- **`options`**: the same options as in `captureRef` method.
133+
113134
### Advanced Examples
114135

115136
[Checkout react-native-view-shot-example](example)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,18 @@ public void captureRef(int tag, ReadableMap options, Promise promise) {
8585
file = createTempFile(getReactApplicationContext(), format);
8686
}
8787
UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
88-
uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer,reactContext, promise));
88+
uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer,reactContext, getCurrentActivity(), promise));
8989
}
9090
catch (Exception e) {
9191
promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
9292
}
9393
}
9494

95+
@ReactMethod
96+
public void captureScreen(ReadableMap options, Promise promise) {
97+
captureRef(-1, options, promise);
98+
}
99+
95100
private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
96101

97102
/**

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class ViewShot implements UIBlock {
4444
private Promise promise;
4545
private Boolean snapshotContentContainer;
4646
private ReactApplicationContext reactContext;
47+
private Activity currentActivity;
4748

4849
public ViewShot(
4950
int tag,
@@ -56,6 +57,7 @@ public ViewShot(
5657
String result,
5758
Boolean snapshotContentContainer,
5859
ReactApplicationContext reactContext,
60+
Activity currentActivity,
5961
Promise promise) {
6062
this.tag = tag;
6163
this.extension = extension;
@@ -67,13 +69,21 @@ public ViewShot(
6769
this.result = result;
6870
this.snapshotContentContainer = snapshotContentContainer;
6971
this.reactContext = reactContext;
72+
this.currentActivity = currentActivity;
7073
this.promise = promise;
7174
}
7275

7376
@Override
7477
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
7578
OutputStream os = null;
76-
View view = nativeViewHierarchyManager.resolveView(tag);
79+
View view = null;
80+
81+
if (tag == -1) {
82+
view = currentActivity.getWindow().getDecorView().findViewById(android.R.id.content);
83+
} else {
84+
view = nativeViewHierarchyManager.resolveView(tag);
85+
}
86+
7787
if (view == null) {
7888
promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "No view found with reactTag: "+tag);
7989
return;

example/App.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "react-native";
1414
import SvgUri from "react-native-svg-uri";
1515
import omit from "lodash/omit";
16-
import { captureRef } from "react-native-view-shot";
16+
import { captureRef, captureScreen } from "react-native-view-shot";
1717
import { Surface } from "gl-react-native";
1818
import GL from "gl-react";
1919
import MapView from "react-native-maps";
@@ -53,9 +53,9 @@ export default class App extends Component {
5353
snapshotContentContainer: false
5454
}
5555
};
56-
56+
5757
snapshot = refname => () =>
58-
captureRef(this.refs[refname], this.state.value)
58+
(refname ? captureRef(this.refs[refname], this.state.value) : captureScreen(this.state.value))
5959
.then(
6060
res =>
6161
this.state.value.result !== "tmpfile"
@@ -149,6 +149,7 @@ export default class App extends Component {
149149
<Btn label="📷 MapView" onPress={this.snapshot("mapview")} />
150150
<Btn label="📷 WebView" onPress={this.snapshot("webview")} />
151151
<Btn label="📷 Video" onPress={this.snapshot("video")} />
152+
<Btn label="📷 Native Screenshot" onPress={this.snapshot()}/>
152153
<Btn
153154
label="📷 Empty View (should crash)"
154155
onPress={this.snapshot("empty")}

ios/RNViewShot.m

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ - (dispatch_queue_t)methodQueue
1919
return RCTGetUIManagerQueue();
2020
}
2121

22+
RCT_EXPORT_METHOD(captureScreen: (NSDictionary *)options
23+
resolve:(RCTPromiseResolveBlock)resolve
24+
reject:(RCTPromiseRejectBlock)reject)
25+
{
26+
[self captureRef: [NSNumber numberWithInt:-1] withOptions:options resolve:resolve reject:reject];
27+
}
2228

2329
RCT_EXPORT_METHOD(releaseCapture:(nonnull NSString *)uri)
2430
{
@@ -41,7 +47,14 @@ - (dispatch_queue_t)methodQueue
4147

4248
// Get view
4349
UIView *view;
44-
view = viewRegistry[target];
50+
51+
if ([target intValue] == -1) {
52+
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
53+
view = window.rootViewController.view;
54+
} else {
55+
view = viewRegistry[target];
56+
}
57+
4558
if (!view) {
4659
reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
4760
return;
@@ -88,7 +101,9 @@ - (dispatch_queue_t)methodQueue
88101
scrollView.contentOffset = CGPointZero;
89102
scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
90103
}
104+
91105
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
106+
92107
success = [rendered drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
93108
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
94109
UIGraphicsEndImageContext();

src/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ export function releaseCapture(uri: string): void {
114114
}
115115
}
116116

117+
export function captureScreen(
118+
optionsObject?: Options
119+
): Promise<string> {
120+
const { options, errors } = validateOptions(optionsObject);
121+
if (__DEV__ && errors.length > 0) {
122+
console.warn(
123+
"react-native-view-shot: bad options:\n" +
124+
errors.map(e => `- ${e}`).join("\n")
125+
);
126+
}
127+
return RNViewShot.captureScreen(options);
128+
}
129+
117130
type Props = {
118131
options?: Object,
119132
captureMode?: "mount" | "continuous" | "update",

0 commit comments

Comments
 (0)