react-native-fast-image 8.23.0 → 8.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -105,6 +105,25 @@ When `source` changes, the image that's showing stays until the new one has load
105
105
 
106
106
  Remote url to load the image from. e.g. `'https://facebook.github.io/react/img/logo_og.png'`.
107
107
 
108
+ #### Photo library images (iOS)
109
+
110
+ A photo library url (`ph://<localIdentifier>`, as camera roll libraries give) loads when the app has SDWebImagePhotosPlugin. Add it to the app's `ios/Podfile` and run `pod install`:
111
+
112
+ ```ruby
113
+ pod 'SDWebImagePhotosPlugin'
114
+ ```
115
+
116
+ With Expo, add it with [expo-build-properties](https://docs.expo.dev/versions/latest/sdk/build-properties/):
117
+
118
+ ```json
119
+ [
120
+ "expo-build-properties",
121
+ { "ios": { "extraPods": [{ "name": "SDWebImagePhotosPlugin" }] } }
122
+ ]
123
+ ```
124
+
125
+ The app needs access to the photo library, which it has if it got the url from there. A photo library image is decoded at about the view's size, since photos are large and usually shown small; `onLoad` still reports the photo's own size. `FastImage.preload` of a `ph://` source loads the full-size photo (there's no view to size it for), which doesn't make a view's smaller copy load faster, so preloading photo library images usually isn't worth it. Without the plugin, a `ph://` source fails with `onError`, saying so. `assets-library://` urls aren't supported. On Android, photo pickers give `content://` urls, which load as they are.
126
+
108
127
  ---
109
128
 
110
129
  ### `source.headers?: object`
@@ -239,7 +258,7 @@ iOS only. Decodes a large image at about the size it's shown at, instead of at f
239
258
 
240
259
  Use it when you show images much larger than their views and can't get them at the right size, e.g. user uploads or other people's URLs in a list. If you control the images, serve them at the size they're shown instead (resized on your server or by an image CDN), which also saves bandwidth.
241
260
 
242
- Decoding a smaller copy can take a little longer, so use it where the memory matters. Needs SDWebImage 5.19.7 or later. On Android images are already decoded at about the view's size.
261
+ Decoding a smaller copy can take a little longer, so use it where the memory matters. Needs SDWebImage 5.19.7 or later. Photo library images are always decoded this way. On Android images are already decoded at about the view's size.
243
262
 
244
263
  ---
245
264
 
@@ -89,7 +89,12 @@ static CGSize FFFPixelSize(NSData* data) {
89
89
  // After a load that isn't downsampled (e.g. a preload), this class
90
90
  // wouldn't be used, and the box would be decoded as SDWebImage does (to
91
91
  // fill it, stretched). With its own url, a downsampled load only shares
92
- // downloads with other downsampled ones.
92
+ // downloads with other downsampled ones. Not a photo library url, which
93
+ // isn't downloaded, and whose loader reads everything after ph:// as the
94
+ // photo's identifier.
95
+ if ([url.scheme isEqualToString: @"ph"]) {
96
+ return url;
97
+ }
93
98
  NSURLComponents* components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
94
99
  if (!components) {
95
100
  return url;
@@ -53,9 +53,18 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
53
53
  - (SDWebImageOptions)cacheOptions;
54
54
 
55
55
  // The loader for `cache: 'web'` images, whose HTTP cache is their own (see
56
- // webURLCache), or nil for SDWebImage's shared one.
56
+ // webURLCache), for photo library images (ph://, see FFFPhotosLoader), or nil
57
+ // for SDWebImage's shared one.
57
58
  - (id<SDImageLoader>)imageLoader;
58
59
 
60
+ // Whether the source is a photo library image (ph://<localIdentifier>),
61
+ // which is decoded at about the view's size.
62
+ - (BOOL)isPhotoLibrary;
63
+
64
+ // A photo library image's own size in pixels (for onLoad when it comes from
65
+ // a cache, where the size it was decoded from isn't recorded), or zero.
66
+ - (CGSize)photoPixelSize;
67
+
59
68
  // The HTTP cache of `cache: 'web'` images: their own rather than the app's
60
69
  // shared NSURLCache, so clearDiskCache can empty it without the app's other
61
70
  // responses. 50 MB, as on Android.
@@ -5,9 +5,150 @@
5
5
  #import <SDWebImage/SDWebImageError.h>
6
6
  #import <SDWebImage/SDWebImageDownloaderOperation.h>
7
7
  #import <SDWebImage/NSData+ImageContentType.h>
8
+ #import <objc/message.h>
8
9
 
9
10
  static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
10
11
 
12
+ // The photo's own size in pixels, looked up by its identifier (the url after
13
+ // ph://), or zero. Through the runtime, as the Photos framework is only there
14
+ // when the app links it (with SDWebImagePhotosPlugin). Kept once looked up.
15
+ static CGSize FFFLookUpPhotoPixelSize(NSURL *url)
16
+ {
17
+ Class assetClass = NSClassFromString(@"PHAsset");
18
+ SEL fetch = NSSelectorFromString(@"fetchAssetsWithLocalIdentifiers:options:");
19
+ NSString *prefix = @"ph://";
20
+ NSString *string = url.absoluteString;
21
+ if (!assetClass || ![assetClass respondsToSelector:fetch] || ![string hasPrefix:prefix]) {
22
+ return CGSizeZero;
23
+ }
24
+ id (*fetchAssets)(id, SEL, NSArray *, id) = (void *)objc_msgSend;
25
+ id result = fetchAssets(assetClass, fetch, @[[string substringFromIndex:prefix.length]], nil);
26
+ id asset = [result respondsToSelector:@selector(firstObject)] ? [result firstObject] : nil;
27
+ if (!asset) {
28
+ return CGSizeZero;
29
+ }
30
+ return CGSizeMake([[asset valueForKey:@"pixelWidth"] doubleValue], [[asset valueForKey:@"pixelHeight"] doubleValue]);
31
+ }
32
+
33
+ static NSCache<NSString *, NSValue *> *FFFPhotoSizes(void)
34
+ {
35
+ static NSCache *sizes;
36
+ static dispatch_once_t once;
37
+ dispatch_once(&once, ^{
38
+ sizes = [NSCache new];
39
+ });
40
+ return sizes;
41
+ }
42
+
43
+ static CGSize FFFPhotoPixelSize(NSURL *url)
44
+ {
45
+ NSString *key = url.absoluteString;
46
+ NSValue *known = key ? [FFFPhotoSizes() objectForKey:key] : nil;
47
+ if (known) {
48
+ return known.CGSizeValue;
49
+ }
50
+ CGSize size = FFFLookUpPhotoPixelSize(url);
51
+ if (key && size.width > 0 && size.height > 0) {
52
+ [FFFPhotoSizes() setObject:[NSValue valueWithCGSize:size] forKey:key];
53
+ }
54
+ return size;
55
+ }
56
+
57
+ // Loads photo library images (ph://<localIdentifier>) with
58
+ // SDWebImagePhotosPlugin's loader when the app has it (the pod): FastImage
59
+ // finds it at runtime, so it doesn't depend on it or on the Photos framework.
60
+ // Its own instance, so the app's use of the plugin is left as it is. Without
61
+ // the plugin, or for a source it can't load (assets-library://), the load
62
+ // fails with `failure`.
63
+ @interface FFFPhotosLoader : NSObject <SDImageLoader>
64
+ @property (nonatomic, strong) id<SDImageLoader> plugin;
65
+ @property (nonatomic, copy) NSString *failure;
66
+ @end
67
+
68
+ @implementation FFFPhotosLoader
69
+
70
+ + (FFFPhotosLoader *)photosLoader
71
+ {
72
+ static FFFPhotosLoader *loader;
73
+ static dispatch_once_t once;
74
+ dispatch_once(&once, ^{
75
+ loader = [FFFPhotosLoader new];
76
+ Class pluginClass = NSClassFromString(@"SDImagePhotosLoader");
77
+ id<SDImageLoader> plugin = pluginClass ? [pluginClass new] : nil;
78
+ if ([plugin conformsToProtocol:@protocol(SDImageLoader)]) {
79
+ // One image per load, at full quality. By default the plugin
80
+ // first sends a quick low quality one, which a view would show
81
+ // (and fade in) before the one it asked for.
82
+ // PHImageRequestOptionsDeliveryModeHighQualityFormat. Skipped if
83
+ // a later version of the plugin renames these.
84
+ NSObject *requestOptions = [(NSObject *)plugin respondsToSelector:NSSelectorFromString(@"imageRequestOptions")]
85
+ ? [(NSObject *)plugin valueForKey:@"imageRequestOptions"]
86
+ : nil;
87
+ if ([requestOptions respondsToSelector:NSSelectorFromString(@"setDeliveryMode:")]) {
88
+ [requestOptions setValue:@1 forKey:@"deliveryMode"];
89
+ }
90
+ loader.plugin = plugin;
91
+ } else {
92
+ loader.failure = @"Photo library images (ph://) need SDWebImagePhotosPlugin: add pod 'SDWebImagePhotosPlugin' to the app's Podfile";
93
+ }
94
+ });
95
+ return loader;
96
+ }
97
+
98
+ + (FFFPhotosLoader *)assetsLibraryLoader
99
+ {
100
+ static FFFPhotosLoader *loader;
101
+ static dispatch_once_t once;
102
+ dispatch_once(&once, ^{
103
+ loader = [FFFPhotosLoader new];
104
+ loader.failure = @"assets-library:// urls aren't supported: use the photo's ph:// url (with SDWebImagePhotosPlugin)";
105
+ });
106
+ return loader;
107
+ }
108
+
109
+ - (BOOL)canRequestImageForURL:(NSURL *)url
110
+ {
111
+ return YES;
112
+ }
113
+
114
+ - (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url
115
+ options:(SDWebImageOptions)options
116
+ context:(SDWebImageContext *)context
117
+ progress:(SDImageLoaderProgressBlock)progressBlock
118
+ completed:(SDImageLoaderCompletedBlock)completedBlock
119
+ {
120
+ if (!self.plugin) {
121
+ if (completedBlock) {
122
+ NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: self.failure}];
123
+ completedBlock(nil, nil, error, YES);
124
+ }
125
+ return nil;
126
+ }
127
+ return [self.plugin requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
128
+ if (!image || !completedBlock) {
129
+ if (completedBlock) {
130
+ completedBlock(image, data, error, finished);
131
+ }
132
+ return;
133
+ }
134
+ // Looks up the photo's own size for onLoad here, off the main thread
135
+ // (the image is decoded at about the view's size).
136
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
137
+ FFFPhotoPixelSize(url);
138
+ dispatch_async(dispatch_get_main_queue(), ^{
139
+ completedBlock(image, data, error, finished);
140
+ });
141
+ });
142
+ }];
143
+ }
144
+
145
+ - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error
146
+ {
147
+ return self.plugin ? [self.plugin shouldBlockFailedURLWithURL:url error:error] : NO;
148
+ }
149
+
150
+ @end
151
+
11
152
  // Downloads `cache: 'web'` images. The HTTP cache only stores a response whose
12
153
  // body is an image: a page sent instead (e.g. a captive portal's, with status
13
154
  // 200) would be served from there until it expired, and each load would fail.
@@ -75,8 +216,24 @@ static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
75
216
  return 0;
76
217
  }
77
218
 
219
+ - (BOOL)isPhotoLibrary
220
+ {
221
+ return [_url.scheme isEqualToString:@"ph"];
222
+ }
223
+
224
+ - (CGSize)photoPixelSize
225
+ {
226
+ return [self isPhotoLibrary] ? FFFPhotoPixelSize(_url) : CGSizeZero;
227
+ }
228
+
78
229
  - (id<SDImageLoader>)imageLoader
79
230
  {
231
+ if ([self isPhotoLibrary]) {
232
+ return [FFFPhotosLoader photosLoader];
233
+ }
234
+ if ([_url.scheme isEqualToString:@"assets-library"]) {
235
+ return [FFFPhotosLoader assetsLibraryLoader];
236
+ }
80
237
  if (_cacheControl != FFFCacheControlWeb) {
81
238
  return nil;
82
239
  }
@@ -570,7 +570,9 @@ NSString *FFFErrorMessage(NSError *error)
570
570
 
571
571
  - (void) sendOnLoad: (UIImage*)image {
572
572
  // The full image's size, also when it was decoded smaller.
573
- CGSize size = [FFFDownsampledImage sourceSizeOfImage: image];
573
+ // A photo library image's own size (it's decoded at about the view's
574
+ // size), looked up when it loaded.
575
+ CGSize size = [_source isPhotoLibrary] ? [_source photoPixelSize] : [FFFDownsampledImage sourceSizeOfImage: image];
574
576
  if (CGSizeEqualToSize(size, CGSizeZero)) {
575
577
  size = image.size;
576
578
  }
@@ -646,11 +648,15 @@ NSString *FFFErrorMessage(NSError *error)
646
648
  }
647
649
  }
648
650
 
649
- // Whether images are decoded at about the view's size (downsample).
650
- // Not for `repeat`, which tiles the image at its own size, or SDWebImage
651
- // before 5.19.
651
+ // Whether images are decoded at about the view's size (downsample, from
652
+ // SDWebImage 5.19, and always for photo library images, which are large and
653
+ // usually shown small: Photos makes them at the size asked for, on any
654
+ // version). Not for `repeat`, which tiles the image at its own size.
652
655
  - (BOOL) downsamples {
653
- return _downsample && _resizeMode != RCTResizeModeRepeat && [FFFDownsampledImage isSupported];
656
+ if (_resizeMode == RCTResizeModeRepeat) {
657
+ return NO;
658
+ }
659
+ return [_source isPhotoLibrary] || (_downsample && [FFFDownsampledImage isSupported]);
654
660
  }
655
661
 
656
662
  // Whether the view has been laid out with an area. One that's 0 wide or tall
@@ -823,6 +829,13 @@ NSString *FFFErrorMessage(NSError *error)
823
829
  }
824
830
  NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
825
831
  [FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
832
+ if ([_source isPhotoLibrary]) {
833
+ // Photos makes the photo at the size asked for, with no file
834
+ // downloaded: SDWebImage would keep this smaller copy on disk as the
835
+ // photo's original, and a larger view would get it from there.
836
+ context[SDWebImageContextOriginalStoreCacheType] = @(SDImageCacheTypeNone);
837
+ context[SDWebImageContextOriginalQueryCacheType] = @(SDImageCacheTypeNone);
838
+ }
826
839
  if (!_source.memoryCache) {
827
840
  // The smaller copy is only kept in memory, so it isn't kept (the
828
841
  // downloaded file is still on disk).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.23.0",
3
+ "version": "8.24.0",
4
4
  "description": "🚩 FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",