RNViewShot.m 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #import "RNViewShot.h"
  2. #import <AVFoundation/AVFoundation.h>
  3. #import "RCTLog.h"
  4. #import "UIView+React.h"
  5. #import "RCTUtils.h"
  6. #import "RCTConvert.h"
  7. #import "RCTUIManager.h"
  8. #import "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. // forked from RN implementation
  17. // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367
  18. RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)target
  19. withOptions:(NSDictionary *)options
  20. resolve:(RCTPromiseResolveBlock)resolve
  21. reject:(RCTPromiseRejectBlock)reject)
  22. {
  23. [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
  24. // Get view
  25. UIView *view;
  26. view = viewRegistry[target];
  27. if (!view) {
  28. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil);
  29. return;
  30. }
  31. // Get options
  32. CGSize size = [RCTConvert CGSize:options];
  33. NSString *format = [RCTConvert NSString:options[@"format"] ?: @"png"];
  34. NSString *result = [RCTConvert NSString:options[@"result"] ?: @"file"];
  35. // Capture image
  36. if (size.width < 0.1 || size.height < 0.1) {
  37. size = view.bounds.size;
  38. }
  39. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  40. BOOL success = [view drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
  41. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  42. UIGraphicsEndImageContext();
  43. if (!success || !image) {
  44. reject(RCTErrorUnspecified, @"Failed to capture view snapshot", nil);
  45. return;
  46. }
  47. // Convert image to data (on a background thread)
  48. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  49. NSData *data;
  50. if ([format isEqualToString:@"png"]) {
  51. data = UIImagePNGRepresentation(image);
  52. } else if ([format isEqualToString:@"jpeg"] || [format isEqualToString:@"jpg"]) {
  53. CGFloat quality = [RCTConvert CGFloat:options[@"quality"] ?: @1];
  54. data = UIImageJPEGRepresentation(image, quality);
  55. } else {
  56. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported image format: %@. Try one of: png | jpg | jpeg", format], nil);
  57. return;
  58. }
  59. NSError *error = nil;
  60. NSString *res = nil;
  61. if ([result isEqualToString:@"file"]) {
  62. // Save to a temp file
  63. NSString *tempFilePath = RCTTempFilePath(format, &error);
  64. if (tempFilePath) {
  65. if ([data writeToFile:tempFilePath options:(NSDataWritingOptions)0 error:&error]) {
  66. res = tempFilePath;
  67. }
  68. }
  69. }
  70. else if ([result isEqualToString:@"base64"]) {
  71. // Return as a base64 raw string
  72. res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  73. }
  74. else if ([result isEqualToString:@"data-uri"]) {
  75. // Return as a base64 data uri string
  76. NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength];
  77. res = [NSString stringWithFormat:@"data:image/%@;base64,%@", format, base64];
  78. }
  79. else {
  80. reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil);
  81. return;
  82. }
  83. if (res != nil) {
  84. resolve(res);
  85. return;
  86. }
  87. // If we reached here, something went wrong
  88. if (error != nil) reject(RCTErrorUnspecified, error.localizedDescription, error);
  89. else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
  90. });
  91. }];
  92. }
  93. @end