PHImageManager requestImageForAsset иногда возвращает ноль для фотографий iCloud

Примерно в 10% случаев PHImageManager.defaultManager().requestImageForAsset возвращает nil вместо действительного UIImage после первого возврата действительного, хотя и «ухудшенного» UIImage. Никакая ошибка или другая подсказка, которую я вижу, не возвращается в информации с nil.

Похоже, это происходит с фотографиями, которые необходимо загрузить из iCloud, когда включены iCloud Photo Library и Optimize iPad Storage. Я пытался изменить параметры, размер и т. д., но, похоже, ничего не имеет значения.

Если я повторю requestImageForAsset после сбоя, он обычно правильно возвращает UIImage, хотя иногда требуется несколько повторных попыток.

Любая идея, что я могу делать неправильно? Или это просто ошибка в рамках Фото?

    func photoImage(asset: PHAsset, size: CGSize, contentMode: UIViewContentMode, completionBlock:(image: UIImage, isPlaceholder: Bool) -> Void) -> PHImageRequestID? {

    let options = PHImageRequestOptions()
    options.networkAccessAllowed = true
    options.version = .Current
    options.deliveryMode = .Opportunistic
    options.resizeMode = .Fast

    let requestSize = !CGSizeEqualToSize(size, CGSizeZero) ? size : PHImageManagerMaximumSize
    let requestContentMode = contentMode == .ScaleAspectFit ? PHImageContentMode.AspectFit : PHImageContentMode.AspectFill

    return PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: requestSize, contentMode: requestContentMode, options: options)
        { (image: UIImage!, info: [NSObject : AnyObject]!) in
            if let image = image {
                let degraded = info[PHImageResultIsDegradedKey] as? Bool ?? false
                completionBlock(image: photoBlock.rotatedImage(image), isPlaceholder: degraded)

            } else {
                let error = info[PHImageErrorKey] as? NSError
                NSLog("Nil image error = \(error?.localizedDescription)")
           }
    }
}

person LenK    schedule 24.06.2015    source источник
comment
У меня точно такая же проблема, вы нашли какое-то решение?   -  person dlinsin    schedule 28.07.2015
comment
Я вижу ту же проблему. Я считаю, что это ошибка и подал отчет об ошибке.   -  person Jordan H    schedule 09.08.2015
comment
Я вижу, что это происходит, когда я устанавливаю options.deliveryMode = .HighQualityFormat, но не когда устанавливаю options.deliveryMode = .Opportunistic   -  person Alfie Hanssen    schedule 05.11.2015
comment
@AlfieHanssen, к сожалению, для меня не имеет значения   -  person Jonathan Crooke    schedule 21.04.2016
comment
Может ли кто-нибудь сказать мне решение этого stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео.   -  person user2786    schedule 03.04.2019
comment
@dlinsin, jordan H, Ленк, у тебя есть какое-нибудь решение этой проблемы?   -  person Dixit Akabari    schedule 16.09.2019


Ответы (11)


Я тоже только что прошел через это. По моим тестам проблема возникает на устройствах с включенной опцией «Оптимизировать хранилище» и заключается в разнице между двумя методами, указанными ниже:

[[PHImageManager defaultManager] requestImageForAsset: ...]

Это позволит успешно получить удаленные изображения iCloud, если ваши параметры настроены правильно.


[[PHImageManager defaultManager] requestImageDataForAsset:...]

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


Вот рабочий фрагмент, который я использую - несите со мной Obj-c :)

 PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
 options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; //I only want the highest possible quality
 options.synchronous = NO;
 options.networkAccessAllowed = YES;
 options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
        NSLog(@"%f", progress); //follow progress + update progress bar
    };

  [[PHImageManager defaultManager] requestImageForAsset:myPhAsset targetSize:self.view.frame.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *image, NSDictionary *info) {
        NSLog(@"reponse %@", info);
        NSLog(@"got image %f %f", image.size.width, image.size.height);
    }];

Полный список доступен на github

Обновлено для Swift 4:

    let options = PHImageRequestOptions()
    options.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat
    options.isSynchronous = false
    options.isNetworkAccessAllowed = true

    options.progressHandler = {  (progress, error, stop, info) in
        print("progress: \(progress)")
    }

    PHImageManager.default().requestImage(for: myPHAsset, targetSize: view.frame.size, contentMode: PHImageContentMode.aspectFill, options: options, resultHandler: {
     (image, info) in
        print("dict: \(String(describing: info))")
        print("image size: \(String(describing: image?.size))")
    })
person ahbou    schedule 03.08.2015
comment
Я действительно не понимаю, как это должно решить исходную проблему? - person dlinsin; 05.08.2015
comment
Я согласен с dlinsin, это выглядит так же, как и то, что я делаю, и все равно регулярно выходит из строя. - person LenK; 19.08.2015
comment
Я им пользовался и ни разу не было сбоев. Это также может быть ошибка в структуре, поскольку она все еще нестабильна. - person ahbou; 19.08.2015
comment
Настоящая проблема с этим методом заключается в том, как получить PHAsset из элементов iCloud Drive... и предположим, что все, что вам нужно, это миниатюра. Вам действительно нужно загружать все изображение, чтобы получить миниатюру? - person Duck; 14.09.2015
comment
Кроме того, для этого метода требуются PHAssets. Я не уверен, что это сработает для изображений на iCloud Drive. Возможно нет. - person Duck; 14.09.2015
comment
Это не для iCloud Drive. Это для получения фотографий из библиотеки фотографий iCloud. - person ahbou; 26.09.2015
comment
Сложная часть networkAccessAllowed. Из документа: при необходимости скачаю образ из iCloud. И по умолчанию установлено значение false. - person James Chen; 17.03.2016
comment
@JamesChen Это то, что я также нашел - я получаю сбой, если для этого не установлено значение true. Спасибо, абу! - person Ross; 26.03.2016
comment
БОЛЬШАЯ ПОМОЩЬ! У меня была такая же проблема. После того, как я прочитал этот ответ, я проверил настройки устройства, и оптимизация хранилища была включена для фотографий, я изменил ее на «Загрузить и сохранить оригиналы», и, наконец, теперь все работает хорошо, спасибо @ahbou - person Passionate.C; 06.12.2018
comment
Может ли кто-нибудь сказать мне решение этого stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео. - person user2786; 03.04.2019
comment
Я сталкиваюсь с той же проблемой, когда получаю файлы .gif и .video. - person Dixit Akabari; 16.09.2019
comment
Эй, ахбоу, ты больше всего мне помог с похожей проблемой. Как только я использовал параметр isSynchronous = false, все заработало, как и ожидалось! Я оценил :) - person sugarakis; 07.02.2021

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

Я не нашел исправления, но обходной путь, вдохновленный @Nadzeya, который работал в 100% случаев для меня, заключался в том, чтобы всегда запрашивать целевой размер, равный размеру актива.

Eg.

PHCachingImageManager().requestImage(for: asset, 
                              targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight) , 
                             contentMode: .aspectFit, 
                                 options: options, 
                           resultHandler: { (image, info) in
        if (image == nil) {
            print("Error loading image")
            print("\(info)")
        } else {
            view.image = image
        }
    });

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

Возможная оптимизация здесь состоит в том, чтобы повторно запросить изображение с размером его актива, только если изображение возвращается как nil.

person Jeremy    schedule 18.03.2017
comment
Может ли кто-нибудь сказать мне решение этого stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео. - person user2786; 03.04.2019

Я пробовал много вещей

  • targetSize больше (400, 400): не работает
  • targetSize равен размеру актива: не работает
  • Отключить Optimize Storage в iCloud Photos в настройках: не работает
  • Отправить requestImage в фоновую очередь: не работает
  • Используйте PHImageManagerMaximumSize: не работает
  • Используйте isNetworkAccessAllowed: не работает
  • Играйте с разными значениями в PHImageRequestOptions, например version, deliveryMode, resizeMode: не работает
  • Добавьте progressHandler: не работает
  • Позвоните requestImage еще раз, если это не удалось: не работает

Все, что я получаю, это nil UIImage и информация с PHImageResultDeliveredImageFormatKey, как в этом радаре Photos Frameworks не возвращает ошибок или изображений для определенных ресурсов

Использовать пропорции

Что работает для меня, см. https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.swift#L34

  • Используйте targetSize с ‹ 200: вот почему я могу загрузить миниатюру
  • Используйте aspectFit: укажите, чтобы contentMode помогло мне

Вот код

let options = PHImageRequestOptions()
options.isSynchronous = true
options.isNetworkAccessAllowed = true

var result: UIImage? = nil

PHImageManager.default().requestImage(
  for: asset,
  targetSize: size,
  contentMode: .aspectFit,
  options: options) { (image, _) in
    result = image
}

return result

Асинхронное получение

Вышеупомянутое может вызвать состояние гонки, поэтому убедитесь, что вы выполняете асинхронную выборку, что означает отсутствие isSynchronous. Взгляните на https://github.com/hyperoslo/Gallery/pull/72.

person onmyway133    schedule 27.11.2017
comment
Не могли бы вы изучить этот stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео. - person user2786; 03.04.2019

Я тоже это видел, и единственное, что мне помогло, это установить options.isSynchronous = false. Мои конкретные варианты:

options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false
person nathan    schedule 15.01.2019
comment
В настоящее время я сталкиваюсь с аналогичной проблемой при тестировании на iphone 11 pro, ios 13. И options.version = .current помог мне, ты! Вы спасли мою жизнь! - person user3788747; 26.10.2019

Попробуйте использовать targetSize больше (400, 400). Мне это помогло.

person Nadzeya    schedule 30.06.2016
comment
Может ли кто-нибудь сказать мне решение этого stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео. - person user2786; 03.04.2019

Ничто из вышеперечисленного не помогло мне, но это решение помогло!

private func getUIImage(asset: PHAsset, retryAttempts: Int = 10) -> UIImage? {
    var img: UIImage?
    let manager = PHImageManager.default()
    let options = PHImageRequestOptions()
    options.version = .original
    options.isSynchronous = true
    options.isNetworkAccessAllowed = true
    manager.requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .aspectFit, options: options, resultHandler: { image, _ in
        img = image
    })
    if img == nil && retryAttempts > 0 {
        return getUIImage(asset: asset, retryAttempts: retryAttempts - 1)
    }
    return img
}

Разница здесь в рекурсивных повторных попытках. Это работает для меня в 100% случаев.

person Eyzuky    schedule 10.03.2020
comment
options.isNetworkAccessAllowed = true помогло! Спасибо! знак равно - person chestozo; 04.02.2021

Для меня это сработало, так это позволить PHImageManager загружать данные актива синхронно, но из асинхронного фонового потока. Упрощенно это выглядит так:

    DispatchQueue.global(qos: .userInitiated).async {
        let requestOptions = PHImageRequestOptions()
        requestOptions.isNetworkAccessAllowed = true
        requestOptions.version = .current
        requestOptions.deliveryMode = .opportunistic
        requestOptions.isSynchronous = true
        PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: requestOptions) { image, _ in
            DispatchQueue.main.async { /* do something with the image */ }
        }
    }
person Fran Pugl    schedule 22.08.2017

Я также получал nil за изображения iCloud. Не имело значения, использовал ли я вариант метода requestImage или requestImageData. Моя проблема, как оказалось, заключалась в том, что мое устройство было подключено к сети через Charles Proxy, так как я хотел отслеживать запросы и ответы, сделанные приложением. По какой-то причине устройство не могло работать с iCloud при таком подключении. После того, как я отключил прокси-приложение, я смог получить изображения iCloud.

person Nemanja Kovacevic    schedule 20.03.2018

Решением для меня была установка targetSize

var image: UIImage?
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
options.isSynchronous = true
options.resizeMode = PHImageRequestOptionsResizeMode.exact

let targetSize = CGSize(width:1200, height:1200)

PHImageManager.default().requestImage(for: self, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: options) { (receivedImage, info) in

    if let formAnImage = receivedImage
    {
        image = formAnImage     
    }
}

Хорошее кодирование!

person Faustino Gagneten    schedule 07.08.2018
comment
Может ли кто-нибудь сказать мне решение этого stackoverflow.com/questions/55488998/? Я также сталкиваюсь с той же проблемой с видео. - person user2786; 03.04.2019

Следующее исправило мою проблему:

let options = PHImageRequestOptions()
options.isSynchronous = false
options.isNetworkAccessAllowed = true
options.deliveryMode = .opportunistic
options.version = .current
options.resizeMode = .exact
person albertski    schedule 18.11.2019

Начиная с iOS 14, это также может произойти, если пользователь не предоставил разрешение на использование этой конкретной фотографии с помощью ограниченного средства выбора фотографий. Для получения дополнительной информации https://developer.apple.com/documentation/photokit/delivering_a_great_privacy_experience_in_your_photos_app

person Sarthak Mishra    schedule 23.05.2021