#import "RNViewShot.h" #import #import "RCTLog.h" #import "UIView+React.h" #import "RCTUtils.h" #import "RCTConvert.h" #import "RCTUIManager.h" #import "RCTBridge.h" @implementation RNViewShot RCT_EXPORT_MODULE() @synthesize bridge = _bridge; - (dispatch_queue_t)methodQueue { return self.bridge.uiManager.methodQueue; } // forked from RN implementation // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367 RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)target withOptions:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { // Get view UIView *view; view = viewRegistry[target]; if (!view) { reject(RCTErrorUnspecified, [NSString stringWithFormat:@"No view found with reactTag: %@", target], nil); return; } // Get options CGSize size = [RCTConvert CGSize:options]; NSString *format = [RCTConvert NSString:options[@"format"] ?: @"png"]; NSString *result = [RCTConvert NSString:options[@"result"] ?: @"file"]; // Capture image if (size.width < 0.1 || size.height < 0.1) { size = view.bounds.size; } UIGraphicsBeginImageContextWithOptions(size, NO, 0); BOOL success = [view drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (!success || !image) { reject(RCTErrorUnspecified, @"Failed to capture view snapshot", nil); return; } // Convert image to data (on a background thread) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *data; if ([format isEqualToString:@"png"]) { data = UIImagePNGRepresentation(image); } else if ([format isEqualToString:@"jpeg"] || [format isEqualToString:@"jpg"]) { CGFloat quality = [RCTConvert CGFloat:options[@"quality"] ?: @1]; data = UIImageJPEGRepresentation(image, quality); } else { reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported image format: %@. Try one of: png | jpg | jpeg", format], nil); return; } NSError *error = nil; NSString *res = nil; if ([result isEqualToString:@"file"]) { // Save to a temp file NSString *tempFilePath = RCTTempFilePath(format, &error); if (tempFilePath) { if ([data writeToFile:tempFilePath options:(NSDataWritingOptions)0 error:&error]) { res = tempFilePath; } } } else if ([result isEqualToString:@"base64"]) { // Return as a base64 raw string res = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength]; } else if ([result isEqualToString:@"data-uri"]) { // Return as a base64 data uri string NSString *base64 = [data base64EncodedStringWithOptions: NSDataBase64Encoding64CharacterLineLength]; res = [NSString stringWithFormat:@"data:image/%@;base64,%@", format, base64]; } else { reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil); return; } if (res != nil) { resolve(res); return; } // If we reached here, something went wrong if (error != nil) reject(RCTErrorUnspecified, error.localizedDescription, error); else reject(RCTErrorUnspecified, @"viewshot unknown error", nil); }); }]; } @end