RNViewShot.m 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. @implementation RNViewShot
  14. RCT_EXPORT_MODULE()
  15. @synthesize bridge = _bridge;
  16. - (dispatch_queue_t)methodQueue
  17. {
  18. return RCTGetUIManagerQueue();
  19. }
  20. RCT_EXPORT_METHOD(captureScreen: (NSDictionary *)options
  21. resolve:(RCTPromiseResolveBlock)resolve
  22. reject:(RCTPromiseRejectBlock)reject)
  23. {
  24. [self captureRef: [NSNumber numberWithInt:-1] withOptions:options resolve:resolve reject:reject];
  25. }
  26. RCT_EXPORT_METHOD(releaseCapture:(nonnull NSString *)uri)
  27. {
  28. NSString *directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
  29. // Ensure it's a valid file in the tmp directory
  30. if ([uri hasPrefix:directory] && ![uri isEqualToString:directory]) {
  31. NSFileManager *fileManager = [NSFileManager new];
  32. if ([fileManager fileExistsAtPath:uri]) {
  33. [fileManager removeItemAtPath:uri error:NULL];
  34. }
  35. }
  36. }
  37. RCT_EXPORT_METHOD(captureRef:(nonnull NSNumber *)target
  38. withOptions:(NSDictionary *)options
  39. resolve:(RCTPromiseResolveBlock)resolve
  40. reject:(RCTPromiseRejectBlock)reject)
  41. {
  42. [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
  43. // Get view
  44. UIView *view;
  45. if ([target intValue] == -1) {
  46. UIWindow *window = [[UIApplication sharedApplication] keyWindow];
  47. view = window.rootViewController.view;
  48. } else {
  49. view = viewRegistry[target];
  50. }
  51. if (!view) {
  52. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
  53. return;
  54. }
  55. // Get options
  56. CGSize size = [RCTConvert CGSize:options];
  57. NSString *format = [RCTConvert NSString:options[@"format"]];
  58. NSString *result = [RCTConvert NSString:options[@"result"]];
  59. BOOL snapshotContentContainer = [RCTConvert BOOL:options[@"snapshotContentContainer"]];
  60. // Capture image
  61. BOOL success;
  62. UIView* rendered;
  63. UIScrollView* scrollView;
  64. if (snapshotContentContainer) {
  65. if (![view isKindOfClass:[RCTScrollView class]]) {
  66. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"snapshotContentContainer can only be used on a RCTScrollView. instead got: %@", view], nil);
  67. return;
  68. }
  69. RCTScrollView* rctScrollView = view;
  70. scrollView = rctScrollView.scrollView;
  71. rendered = scrollView;
  72. }
  73. else {
  74. rendered = view;
  75. }
  76. if (size.width < 0.1 || size.height < 0.1) {
  77. size = snapshotContentContainer ? scrollView.contentSize : view.bounds.size;
  78. }
  79. if (size.width < 0.1 || size.height < 0.1) {
  80. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"The content size must not be zero or negative. Got: (%g, %g)", size.width, size.height], nil);
  81. return;
  82. }
  83. CGPoint savedContentOffset;
  84. CGRect savedFrame;
  85. if (snapshotContentContainer) {
  86. // Save scroll & frame and set it temporarily to the full content size
  87. savedContentOffset = scrollView.contentOffset;
  88. savedFrame = scrollView.frame;
  89. scrollView.contentOffset = CGPointZero;
  90. scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
  91. }
  92. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  93. success = [rendered drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
  94. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  95. UIGraphicsEndImageContext();
  96. if (snapshotContentContainer) {
  97. // Restore scroll & frame
  98. scrollView.contentOffset = savedContentOffset;
  99. scrollView.frame = savedFrame;
  100. }
  101. if (!success) {
  102. reject(RCTErrorUnspecified, @"The view cannot be captured. drawViewHierarchyInRect was not successful. This is a potential technical or security limitation.", nil);
  103. return;
  104. }
  105. if (!image) {
  106. reject(RCTErrorUnspecified, @"Failed to capture view snapshot. UIGraphicsGetImageFromCurrentImageContext() returned nil!", nil);
  107. return;
  108. }
  109. // Convert image to data (on a background thread)
  110. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  111. NSData *data;
  112. if ([format isEqualToString:@"jpg"]) {
  113. CGFloat quality = [RCTConvert CGFloat:options[@"quality"]];
  114. data = UIImageJPEGRepresentation(image, quality);
  115. }
  116. else {
  117. data = UIImagePNGRepresentation(image);
  118. }
  119. NSError *error = nil;
  120. NSString *res = nil;
  121. if ([result isEqualToString:@"base64"]) {
  122. // Return as a base64 raw string
  123. res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  124. }
  125. else if ([result isEqualToString:@"data-uri"]) {
  126. // Return as a base64 data uri string
  127. NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  128. NSString *imageFormat = ([format isEqualToString:@"jpg"]) ? @"jpeg" : format;
  129. res = [NSString stringWithFormat:@"data:image/%@;base64,%@", imageFormat, base64];
  130. }
  131. else {
  132. // Save to a temp file
  133. NSString *path = RCTTempFilePath(format, &error);
  134. if (path && !error) {
  135. if ([data writeToFile:path options:(NSDataWritingOptions)0 error:&error]) {
  136. res = path;
  137. }
  138. }
  139. }
  140. if (res && !error) {
  141. resolve(res);
  142. return;
  143. }
  144. // If we reached here, something went wrong
  145. if (error) reject(RCTErrorUnspecified, error.localizedDescription, error);
  146. else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
  147. });
  148. }];
  149. }
  150. @end