How to use PHCachingImageManager
Refer this Photos Framework example
In this project you should look into the file AAPLAssetGridViewController.m and how it handles caching based on your scroll position.
- (void)updateCachedAssets
{
BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
if (!isViewVisible) { return; }
// The preheat window is twice the height of the visible rect
CGRect preheatRect = self.collectionView.bounds;
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
// If scrolled by a "reasonable" amount...
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
// Compute the assets to start caching and to stop caching.
NSMutableArray *addedIndexPaths = [NSMutableArray array];
NSMutableArray *removedIndexPaths = [NSMutableArray array];
[self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
[removedIndexPaths addObjectsFromArray:indexPaths];
} addedHandler:^(CGRect addedRect) {
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
[addedIndexPaths addObjectsFromArray:indexPaths];
}];
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
self.previousPreheatRect = preheatRect;
}
}
//In your case this computeDifference method changes to handle horizontal scrolling
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
{
if (CGRectIntersectsRect(newRect, oldRect)) {
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
CGFloat oldMinY = CGRectGetMinY(oldRect);
CGFloat newMaxY = CGRectGetMaxY(newRect);
CGFloat newMinY = CGRectGetMinY(newRect);
if (newMaxY > oldMaxY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
addedHandler(rectToAdd);
}
if (oldMinY > newMinY) {
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
addedHandler(rectToAdd);
}
if (newMaxY < oldMaxY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
removedHandler(rectToRemove);
}
if (oldMinY < newMinY) {
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
removedHandler(rectToRemove);
}
} else {
addedHandler(newRect);
removedHandler(oldRect);
}
}
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
{
if (indexPaths.count == 0) { return nil; }
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
PHAsset *asset = self.assetsFetchResults[indexPath.item];
[assets addObject:asset];
}
return assets;
}
The image caching manager heats up with your assets of the desired size and then you can retrieve them from the image caching manager itself.
Hope it helps you.
how to use PHCachingImageManager().stopCachingImages
After a lot of trial and error :
PHImageManager.default().requestImageData(for: asset, options: nil) { (data, str, orientation, info) in
let formAnImage = UIImage(data: data!)
//you will get an original image
}
The above option was not working perfectly for all cases, if user selects an older image, the data is nil , So i have used below solution :
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
options.isSynchronous = true
options.resizeMode = PHImageRequestOptionsResizeMode.exact
let targetSize = CGSize(width:SCREENWIDTH(), height:SCREENHEIGHT())
PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: options) { (receivedImage, info) in
if let formAnImage = receivedImage
{
//You will get image here..
}
}
swift 4, how do I load over 3000~ pictures in collectionView?
Here are some tips to improve performance when scrolling through the photo library:
Don't implement your own image cache or use the default image manager, use your own instance of PHCachingImageManager
. You can tell it to start caching images surrounding your scroll position to improve performance.
Don't use synchronous fetching of images. Use the asynchronous methods and update the image view in the completion block, though note that the completion block can fire before the fetch method has even returned, and fire multiple times as you get better quality images out of the library.
Cancel fetches as your cells go off screen.
I haven't worked with the framework for a while, but I wrote up some notes about it here which are hopefully still relevant.
PHCachingImageManager.requestImage is always called twice?
Affraid there is not much you can do about requestImage:forAsset
being called twice. However, you can change how you are putting images in the array to avoid duplicates. Might I suggest:
self.assetsTurnedIntoImages[indexPath.row] = image
This will cause the image passed into completion the second time around to replace the one that was called the first time, avoiding duplication.
You will want to pre-initialize enough space for your array ahead of time so that indexPath.row
does not try to access your array out of bounds.
In ViewDidLoad
add something to the effect of:
self.assetsTurnedIntoImages = [UIImage](repeating: UIImage(), count: assets.count)
How to reduce memory use with PhotoKit?
What I have done now is
- Reduce the target size of fetched
PHAsset
objects; Try to adopt caching. My code is like below:
CGFloat scale = 1.5; //or an even smaller one
if (!_manager) {
_manager = [[PHCachingImageManager alloc] init];
}
if (!_options) {
_options = [[PHImageRequestOptions alloc] init];
}
_options.resizeMode = PHImageRequestOptionsResizeModeExact;
_options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
NSRange range = NSMakeRange(0, _fetchedPhotos.count);
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range];
NSArray *assets = [_fetchedPhotos objectsAtIndexes:set];
CGSize targetSize = CGSizeMake(_layout.itemSize.width*scale, _layout.itemSize.height*scale);
[_manager startCachingImagesForAssets:assets targetSize:targetSize contentMode:PHImageContentModeAspectFill options:_options];
Now even I scroll the UICollectionView
fast enough, the top memory would be less than 50 MB
, and thanks to Caching
(I guess it is working based on my code) it doesn't fluctuate that much, memory use is like this:
UPDATE
According to another post here, it is recommended not to specify PHImageRequestOptions
object. Instead, you could leave it to iOS to decide what is the best for you to present the photos with best quality and least time.
Related Topics
How to Remove the Bottom Gap of Uipageviewcontroller
Nspredicate to Match "Any Entry in an Nsdatabase with Value That Contains a String"
Property 'Self.*' Not Initialized at Super.Init Call
iOS 11 Search Bar Jumping to Top of Screen
Fbsdkaccesstoken Currentaccesstoken Is Not Being Updated After Log In
Having Uiview Drawrect Occur in a Background Thread
Replace iOS App Emoji with Twitter Open Source Twemoji
Assertion Failure in Void _Uiperformresizeoftextviewfortextcontainer
How to Pass Params on Timer Selector
Drawing Rounded Rect in Core Graphics
Check If 3D Touch Is Supported and Enabled on the iOS9 Device
How to Properly Add Child View Controller in iOS 8 with Swift
How to Code the Launchscreen Programmatically
Autolayout: Origin and Size Should Change According to Width and Height Factor
How to Get an Error Description When Playback Fails on Mpmovieplayercontroller
Convert Spelled Out Number to Number