Как использовать PHCachingImageManager

Я знаю, что на SO уже есть этот вопрос, но я не думаю, что данный ответ является удовлетворительным/полным: Как приложение IOS Photos может отображать сотни фотографий на одном экране?

Чего я хочу достичь

Я хочу что-то вроде выбора изображения в WhatsApp (iOS) (см. скриншот). Когда вы открываете камеру, вы также видите горизонтальный слайдер, где вы можете увидеть все изображения из вашей галереи.

скриншот WhatsApp

Что я пробовал

Прямо сейчас у меня есть следующий код в моем appDelegate:

let options = PHFetchOptions()

options.sortDescriptors = [
    NSSortDescriptor(key: "creationDate", ascending: true)
]
if let results = PHAsset.fetchAssetsWithMediaType(.Image, options: options) {

    results.enumerateObjectsUsingBlock { (object, idx, _) in
        if let asset = object as? PHAsset {
            Variables.assets.append(asset)
        }
    }

    println(Variables.assets.count)

    Variables.imageManager.startCachingImagesForAssets(Variables.assets, targetSize: CGSizeMake(viewWidth, viewWidth), contentMode: .AspectFill, options: self.requestOptions)
}

Позже я загружаю изображения в UITableViewController и вызываю следующую функцию при прокрутке:

func fetchPhotoAtIndex(index : Int) {

    let asset = Variables.assets[Variables.assets.count - 1 - index] as PHAsset

    Variables.imageManager.requestImageForAsset(asset, targetSize: CGSizeMake(self.viewWidth, self.viewWidth), contentMode: .AspectFill, options: self.requestOptions, resultHandler: { (image, _) in

        println("\(asset.localIdentifier) \(asset.creationDate) ")

        [...]

        self.tableView.reloadData()
    })

}

Это работает хорошо, но у меня проблема в том, что при каждом запуске приложения все мои активы кэшируются. Это правильный подход для моей проблемы? Как я могу отображать фотографии галереи в режиме реального времени, как это делает WhatsApp?


person Alexander Scholz    schedule 29.05.2015    source источник
comment
Зачем вам активы, если вы можете получить их снова? Хранить их может быть сложно, так как некоторые ресурсы могли быть удалены/добавлены/изменены после того, как ваше приложение было уничтожено. Также вам не нужно получать их все снова. Вы должны просто получить те, которые вам нужны, чтобы показать пользователю на экране в это время. Некоторый контекст необходимости их хранения может помочь.   -  person jarora    schedule 06.06.2015
comment
@jarora Я отредактировал весь свой вопрос, предоставил код и пример, которого хочу добиться.   -  person Alexander Scholz    schedule 15.06.2015


Ответы (1)


Обратитесь к этому примеру Photos Framework. В этом проекте вы должны просмотрите файл AAPLAssetGridViewController.m и то, как он обрабатывает кэширование в зависимости от положения прокрутки.

 - (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;
    }

Менеджер кэширования изображений нагревается вашими активами нужного размера, а затем вы можете получить их из самого менеджера кэширования изображений.

Надеюсь, это поможет вам.

person jarora    schedule 04.07.2015
comment
Привет, я использую ваш код в своем приложении, и строка PHAsset *asset = self.assetsFetchResults[indexPath.item]; выдает следующее исключение для меня: *** Terminating app due to uncaught exception 'NSRangeException', reason: '0x1957a880: index (0) beyond bounds (0)' Это происходит не каждый раз, т.е. я знаю об этом исключении из HockeyApp. Любая идея, что может быть причиной этого? - person Banana; 23.09.2015
comment
Ну, у меня тоже были подобные исключения, но я не смог понять причину этого. Единственный известный мне обходной путь — добавить проверку: indexPath.item ‹ self.assetsFetchResults.count перед добавлением актива в массив «активы». - person jarora; 23.09.2015