No Description

RNViewShot.m 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 self.bridge.uiManager.methodQueue;
  16. }
  17. - (NSDictionary *)constantsToExport
  18. {
  19. return @{
  20. @"CacheDir" : [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject],
  21. @"DocumentDir": [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject],
  22. @"MainBundleDir" : [[NSBundle mainBundle] bundlePath],
  23. @"MovieDir": [NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, NSUserDomainMask, YES) firstObject],
  24. @"MusicDir": [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) firstObject],
  25. @"PictureDir": [NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES) firstObject],
  26. };
  27. }
  28. // forked from RN implementation
  29. // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367
  30. RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)target
  31. withOptions:(NSDictionary *)options
  32. resolve:(RCTPromiseResolveBlock)resolve
  33. reject:(RCTPromiseRejectBlock)reject)
  34. {
  35. [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
  36. // Get view
  37. UIView *view;
  38. view = viewRegistry[target];
  39. if (!view) {
  40. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
  41. return;
  42. }
  43. // Get options
  44. CGSize size = [RCTConvert CGSize:options];
  45. NSString *format = [RCTConvert NSString:options[@"format"] ?: @"png"];
  46. NSString *result = [RCTConvert NSString:options[@"result"] ?: @"file"];
  47. BOOL snapshotContentContainer = [RCTConvert BOOL:options[@"snapshotContentContainer"] ?: @"false"];
  48. // Capture image
  49. BOOL success;
  50. if (snapshotContentContainer) {
  51. if (![view isKindOfClass:[RCTScrollView class]]) {
  52. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"snapshotContentContainer can only be used on a RCTScrollView. instead got: %@", view], nil);
  53. return;
  54. }
  55. RCTScrollView* rctScrollView = view;
  56. UIScrollView* scrollView = rctScrollView.scrollView;
  57. CGPoint savedContentOffset = scrollView.contentOffset;
  58. CGRect savedFrame = scrollView.frame;
  59. scrollView.contentOffset = CGPointZero;
  60. scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
  61. if (size.width < 0.1 || size.height < 0.1) {
  62. size = scrollView.contentSize;
  63. }
  64. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  65. success = [scrollView drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
  66. scrollView.contentOffset = savedContentOffset;
  67. scrollView.frame = savedFrame;
  68. }
  69. else {
  70. if (size.width < 0.1 || size.height < 0.1) {
  71. size = view.bounds.size;
  72. }
  73. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  74. success = [view drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
  75. }
  76. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  77. UIGraphicsEndImageContext();
  78. if (!success || !image) {
  79. reject(RCTErrorUnspecified, @"Failed to capture view snapshot", nil);
  80. return;
  81. }
  82. // Convert image to data (on a background thread)
  83. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  84. NSData *data;
  85. if ([format isEqualToString:@"png"]) {
  86. data = UIImagePNGRepresentation(image);
  87. } else if ([format isEqualToString:@"jpeg"] || [format isEqualToString:@"jpg"]) {
  88. CGFloat quality = [RCTConvert CGFloat:options[@"quality"] ?: @1];
  89. data = UIImageJPEGRepresentation(image, quality);
  90. } else {
  91. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported image format: %@. Try one of: png | jpg | jpeg", format], nil);
  92. return;
  93. }
  94. NSError *error = nil;
  95. NSString *res = nil;
  96. if ([result isEqualToString:@"file"]) {
  97. // Save to a temp file
  98. NSString *path;
  99. if (options[@"path"]) {
  100. path = options[@"path"];
  101. NSString * folder = [path stringByDeletingLastPathComponent];
  102. NSFileManager * fm = [NSFileManager defaultManager];
  103. if(![fm fileExistsAtPath:folder]) {
  104. [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&error];
  105. [fm createFileAtPath:path contents:nil attributes:nil];
  106. }
  107. }
  108. else {
  109. path = RCTTempFilePath(format, &error);
  110. }
  111. if (path && !error) {
  112. if ([data writeToFile:path options:(NSDataWritingOptions)0 error:&error]) {
  113. res = path;
  114. }
  115. }
  116. }
  117. else if ([result isEqualToString:@"base64"]) {
  118. // Return as a base64 raw string
  119. res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  120. }
  121. else if ([result isEqualToString:@"data-uri"]) {
  122. // Return as a base64 data uri string
  123. NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  124. res = [NSString stringWithFormat:@"data:image/%@;base64,%@", format, base64];
  125. }
  126. else {
  127. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil);
  128. return;
  129. }
  130. if (res && !error) {
  131. resolve(res);
  132. return;
  133. }
  134. // If we reached here, something went wrong
  135. if (error) reject(RCTErrorUnspecified, error.localizedDescription, error);
  136. else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
  137. });
  138. }];
  139. }
  140. @end