RNViewShot.m 5.0KB

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