1// Copyright 2015-present 650 Industries. All rights reserved. 2 3// Prior to React Native 0.61, it contained `RCTAssetsLibraryRequestHandler` that handles loading data from URLs 4// with `assets-library://` or `ph://` schemes. Due to lean core project, it's been moved to `@react-native-community/cameraroll` 5// and that made it impossible to render assets using URLs returned by MediaLibrary without installing CameraRoll. 6// Because it's still a unimodule and we need to export bare React Native module, we should make sure React Native is installed. 7#if __has_include(<React/RCTImageURLLoader.h>) 8 9#import <Photos/Photos.h> 10#import <React/RCTDefines.h> 11#import <React/RCTUtils.h> 12#import <React/RCTBridgeModule.h> 13#import <EXMediaLibrary/EXMediaLibraryImageLoader.h> 14 15@implementation EXMediaLibraryImageLoader 16 17RCT_EXPORT_MODULE() 18 19#pragma mark - RCTImageURLLoader 20 21- (BOOL)canLoadImageURL:(NSURL *)requestURL 22{ 23 if (![PHAsset class]) { 24 return NO; 25 } 26 return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame || 27 [requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame; 28} 29 30- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL 31 size:(CGSize)size 32 scale:(CGFloat)scale 33 resizeMode:(RCTResizeMode)resizeMode 34 progressHandler:(RCTImageLoaderProgressBlock)progressHandler 35 partialLoadHandler:(RCTImageLoaderPartialLoadBlock)partialLoadHandler 36 completionHandler:(RCTImageLoaderCompletionBlock)completionHandler 37{ 38 // Using PhotoKit for iOS 8+ 39 // The 'ph://' prefix is used by FBMediaKit to differentiate between 40 // assets-library. It is prepended to the local ID so that it is in the 41 // form of an NSURL which is what assets-library uses. 42 NSString *assetID = @""; 43 PHFetchResult *results; 44 if (!imageURL) { 45 completionHandler(RCTErrorWithMessage(@"Cannot load a photo library asset with no URL"), nil); 46 return ^{}; 47 } else if ([imageURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame) { 48 assetID = [imageURL absoluteString]; 49 results = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil]; 50 } else { 51 assetID = [imageURL.absoluteString substringFromIndex:@"ph://".length]; 52 results = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetID] options:nil]; 53 } 54 if (results.count == 0) { 55 NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", assetID]; 56 completionHandler(RCTErrorWithMessage(errorText), nil); 57 return ^{}; 58 } 59 60 PHAsset *asset = [results firstObject]; 61 PHImageRequestOptions *imageOptions = [PHImageRequestOptions new]; 62 63 // Allow PhotoKit to fetch images from iCloud 64 imageOptions.networkAccessAllowed = YES; 65 66 if (progressHandler) { 67 imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary<NSString *, id> *info) { 68 static const double multiplier = 1e6; 69 progressHandler(progress * multiplier, multiplier); 70 }; 71 } 72 73 // Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic 74 // which means it may call back multiple times - we probably don't want that 75 imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; 76 77 BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero); 78 CGSize targetSize; 79 if (useMaximumSize) { 80 targetSize = PHImageManagerMaximumSize; 81 imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone; 82 } else { 83 targetSize = CGSizeApplyAffineTransform(size, CGAffineTransformMakeScale(scale, scale)); 84 imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast; 85 } 86 87 PHImageContentMode contentMode = PHImageContentModeAspectFill; 88 if (resizeMode == RCTResizeModeContain) { 89 contentMode = PHImageContentModeAspectFit; 90 } 91 92 PHImageRequestID requestID = 93 [[PHImageManager defaultManager] requestImageForAsset:asset 94 targetSize:targetSize 95 contentMode:contentMode 96 options:imageOptions 97 resultHandler:^(UIImage *result, NSDictionary<NSString *, id> *info) { 98 if (result) { 99 completionHandler(nil, result); 100 } else { 101 completionHandler(info[PHImageErrorKey], nil); 102 } 103 }]; 104 105 return ^{ 106 [[PHImageManager defaultManager] cancelImageRequest:requestID]; 107 }; 108} 109 110@end 111 112#endif 113