Geen omschrijving

RNViewShot.m 6.0KB

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