123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- #import "RNViewShot.h"
- #import <AVFoundation/AVFoundation.h>
- #import <React/RCTLog.h>
- #import <React/UIView+React.h>
- #import <React/RCTUtils.h>
- #import <React/RCTConvert.h>
- #import <React/RCTScrollView.h>
- #import <React/RCTUIManager.h>
- #if __has_include(<React/RCTUIManagerUtils.h>)
- #import <React/RCTUIManagerUtils.h>
- #endif
- #import <React/RCTBridge.h>
-
- @implementation RNViewShot
-
- RCT_EXPORT_MODULE()
-
- @synthesize bridge = _bridge;
-
- - (dispatch_queue_t)methodQueue
- {
- return RCTGetUIManagerQueue();
- }
-
- RCT_EXPORT_METHOD(captureScreen: (NSDictionary *)options
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject)
- {
- [self captureRef: [NSNumber numberWithInt:-1] withOptions:options resolve:resolve reject:reject];
- }
-
- RCT_EXPORT_METHOD(releaseCapture:(nonnull NSString *)uri)
- {
- NSString *directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
- // Ensure it's a valid file in the tmp directory
- if ([uri hasPrefix:directory] && ![uri isEqualToString:directory]) {
- NSFileManager *fileManager = [NSFileManager new];
- if ([fileManager fileExistsAtPath:uri]) {
- [fileManager removeItemAtPath:uri error:NULL];
- }
- }
- }
-
- RCT_EXPORT_METHOD(captureRef:(nonnull NSNumber *)target
- withOptions:(NSDictionary *)options
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject)
- {
- [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
-
- // Get view
- UIView *view;
-
- if ([target intValue] == -1) {
- UIWindow *window = [[UIApplication sharedApplication] keyWindow];
- view = window.rootViewController.view;
- } else {
- view = viewRegistry[target];
- }
-
- if (!view) {
- reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
- return;
- }
-
- // Get options
- CGSize size = [RCTConvert CGSize:options];
- NSString *format = [RCTConvert NSString:options[@"format"]];
- NSString *result = [RCTConvert NSString:options[@"result"]];
- BOOL snapshotContentContainer = [RCTConvert BOOL:options[@"snapshotContentContainer"]];
-
- // Capture image
- BOOL success;
-
- UIView* rendered;
- UIScrollView* scrollView;
- if (snapshotContentContainer) {
- if (![view isKindOfClass:[RCTScrollView class]]) {
- reject(RCTErrorUnspecified, [NSString stringWithFormat:@"snapshotContentContainer can only be used on a RCTScrollView. instead got: %@", view], nil);
- return;
- }
- RCTScrollView* rctScrollView = view;
- scrollView = rctScrollView.scrollView;
- rendered = scrollView;
- }
- else {
- rendered = view;
- }
-
- if (size.width < 0.1 || size.height < 0.1) {
- size = snapshotContentContainer ? scrollView.contentSize : view.bounds.size;
- }
- if (size.width < 0.1 || size.height < 0.1) {
- reject(RCTErrorUnspecified, [NSString stringWithFormat:@"The content size must not be zero or negative. Got: (%g, %g)", size.width, size.height], nil);
- return;
- }
-
- CGPoint savedContentOffset;
- CGRect savedFrame;
- if (snapshotContentContainer) {
- // Save scroll & frame and set it temporarily to the full content size
- savedContentOffset = scrollView.contentOffset;
- savedFrame = scrollView.frame;
- scrollView.contentOffset = CGPointZero;
- scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
- }
-
- UIGraphicsBeginImageContextWithOptions(size, NO, 0);
-
- success = [rendered drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- if (snapshotContentContainer) {
- // Restore scroll & frame
- scrollView.contentOffset = savedContentOffset;
- scrollView.frame = savedFrame;
- }
-
- if (!success) {
- reject(RCTErrorUnspecified, @"The view cannot be captured. drawViewHierarchyInRect was not successful. This is a potential technical or security limitation.", nil);
- return;
- }
-
- if (!image) {
- reject(RCTErrorUnspecified, @"Failed to capture view snapshot. UIGraphicsGetImageFromCurrentImageContext() returned nil!", nil);
- return;
- }
-
- // Convert image to data (on a background thread)
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
- NSData *data;
- if ([format isEqualToString:@"jpg"]) {
- CGFloat quality = [RCTConvert CGFloat:options[@"quality"]];
- data = UIImageJPEGRepresentation(image, quality);
- }
- else {
- data = UIImagePNGRepresentation(image);
- }
-
- NSError *error = nil;
- NSString *res = nil;
- if ([result isEqualToString:@"base64"]) {
- // Return as a base64 raw string
- res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
- }
- else if ([result isEqualToString:@"data-uri"]) {
- // Return as a base64 data uri string
- NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
- NSString *imageFormat = ([format isEqualToString:@"jpg"]) ? @"jpeg" : format;
- res = [NSString stringWithFormat:@"data:image/%@;base64,%@", imageFormat, base64];
- }
- else {
- // Save to a temp file
- NSString *path = RCTTempFilePath(format, &error);
- if (path && !error) {
- if ([data writeToFile:path options:(NSDataWritingOptions)0 error:&error]) {
- res = path;
- }
- }
- }
-
- if (res && !error) {
- resolve(res);
- return;
- }
-
- // If we reached here, something went wrong
- if (error) reject(RCTErrorUnspecified, error.localizedDescription, error);
- else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
- });
- }];
- }
-
-
- @end
|