1#import "RNViewShot.h"
2#import <AVFoundation/AVFoundation.h>
3#import <React/RCTLog.h>
4#import <React/UIView+React.h>
5#import <React/RCTUtils.h>
6#import <React/RCTConvert.h>
7#import <React/RCTScrollView.h>
8#import <React/RCTUIManager.h>
9#if __has_include(<React/RCTUIManagerUtils.h>)
10#import <React/RCTUIManagerUtils.h>
11#endif
12#import <React/RCTBridge.h>
13
14@implementation RNViewShot
15
16RCT_EXPORT_MODULE()
17
18@synthesize bridge = _bridge;
19
20- (dispatch_queue_t)methodQueue
21{
22  return RCTGetUIManagerQueue();
23}
24
25RCT_EXPORT_METHOD(captureScreen: (NSDictionary *)options
26                  resolve:(RCTPromiseResolveBlock)resolve
27                  reject:(RCTPromiseRejectBlock)reject)
28{
29  [self captureRef: [NSNumber numberWithInt:-1] withOptions:options resolve:resolve reject:reject];
30}
31
32RCT_EXPORT_METHOD(releaseCapture:(nonnull NSString *)uri)
33{
34  NSString *directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
35  // Ensure it's a valid file in the tmp directory
36  if ([uri hasPrefix:directory] && ![uri isEqualToString:directory]) {
37    NSFileManager *fileManager = [NSFileManager new];
38    if ([fileManager fileExistsAtPath:uri]) {
39      [fileManager removeItemAtPath:uri error:NULL];
40    }
41  }
42}
43
44RCT_EXPORT_METHOD(captureRef:(nonnull NSNumber *)target
45                  withOptions:(NSDictionary *)options
46                  resolve:(RCTPromiseResolveBlock)resolve
47                  reject:(RCTPromiseRejectBlock)reject)
48{
49  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
50
51    // Get view
52    UIView *view;
53
54    if ([target intValue] == -1) {
55      UIWindow *window = [[UIApplication sharedApplication] keyWindow];
56      view = window;
57    } else {
58      view = viewRegistry[target];
59    }
60
61    if (!view) {
62      reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
63      return;
64    }
65
66    // Get options
67    CGSize size = [RCTConvert CGSize:options];
68    NSString *format = [RCTConvert NSString:options[@"format"]];
69    NSString *result = [RCTConvert NSString:options[@"result"]];
70    BOOL renderInContext = [RCTConvert BOOL:options[@"useRenderInContext"]];
71    BOOL snapshotContentContainer = [RCTConvert BOOL:options[@"snapshotContentContainer"]];
72
73    // Capture image
74    BOOL success;
75
76    UIView* rendered;
77    UIScrollView* scrollView;
78    if (snapshotContentContainer) {
79      if (![view isKindOfClass:[RCTScrollView class]]) {
80        reject(RCTErrorUnspecified, [NSString stringWithFormat:@"snapshotContentContainer can only be used on a RCTScrollView. instead got: %@", view], nil);
81        return;
82      }
83      RCTScrollView* rctScrollView = (RCTScrollView *)view;
84      scrollView = rctScrollView.scrollView;
85      rendered = scrollView;
86    }
87    else {
88      rendered = view;
89    }
90
91    if (size.width < 0.1 || size.height < 0.1) {
92      size = snapshotContentContainer ? scrollView.contentSize : view.bounds.size;
93    }
94    if (size.width < 0.1 || size.height < 0.1) {
95      reject(RCTErrorUnspecified, [NSString stringWithFormat:@"The content size must not be zero or negative. Got: (%g, %g)", size.width, size.height], nil);
96      return;
97    }
98
99    CGPoint savedContentOffset;
100    CGRect savedFrame;
101    if (snapshotContentContainer) {
102      // Save scroll & frame and set it temporarily to the full content size
103      savedContentOffset = scrollView.contentOffset;
104      savedFrame = scrollView.frame;
105      scrollView.contentOffset = CGPointZero;
106      scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
107    }
108
109    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
110
111    if (renderInContext) {
112      // this comes with some trade-offs such as inability to capture gradients or scrollview's content in full but it works for large views
113      [rendered.layer renderInContext: UIGraphicsGetCurrentContext()];
114      success = YES;
115    }
116    else {
117      // this doesn't work for large views and reports incorrect success even though the image is blank
118      success = [rendered drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
119    }
120    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
121    UIGraphicsEndImageContext();
122
123    if (snapshotContentContainer) {
124      // Restore scroll & frame
125      scrollView.contentOffset = savedContentOffset;
126      scrollView.frame = savedFrame;
127    }
128
129    if (!success) {
130      reject(RCTErrorUnspecified, @"The view cannot be captured. drawViewHierarchyInRect was not successful. This is a potential technical or security limitation.", nil);
131      return;
132    }
133
134    if (!image) {
135      reject(RCTErrorUnspecified, @"Failed to capture view snapshot. UIGraphicsGetImageFromCurrentImageContext() returned nil!", nil);
136      return;
137    }
138
139    // Convert image to data (on a background thread)
140    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
141
142      NSData *data;
143      if ([format isEqualToString:@"jpg"]) {
144        CGFloat quality = [RCTConvert CGFloat:options[@"quality"]];
145        data = UIImageJPEGRepresentation(image, quality);
146      }
147      else {
148        data = UIImagePNGRepresentation(image);
149      }
150
151      NSError *error = nil;
152      NSString *res = nil;
153      if ([result isEqualToString:@"base64"]) {
154        // Return as a base64 raw string
155        res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
156      }
157      else if ([result isEqualToString:@"data-uri"]) {
158        // Return as a base64 data uri string
159        NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
160        NSString *imageFormat = ([format isEqualToString:@"jpg"]) ? @"jpeg" : format;
161        res = [NSString stringWithFormat:@"data:image/%@;base64,%@", imageFormat, base64];
162      }
163      else {
164        // Save to a temp file
165        NSString *path = RCTTempFilePath(format, &error);
166        if (path && !error) {
167          if ([data writeToFile:path options:(NSDataWritingOptions)0 error:&error]) {
168            res = path;
169          }
170        }
171      }
172
173      if (res && !error) {
174        resolve(res);
175        return;
176      }
177
178      // If we reached here, something went wrong
179      if (error) reject(RCTErrorUnspecified, error.localizedDescription, error);
180      else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
181    });
182  }];
183}
184
185
186@end
187