8

On iOS 8, I want to get all pictures stored on the device. My problem is that I do get them, but some are present more than once. The PHAsset properties (hidden, mediaSubtypes, etc.) are the same for all pictures, so I can't for example rule out the PHAssetMediaSubtypePhotoHDR subtypes. The only way I found is not adding multiple pictures with the same date, but this is a problem when multiple photos were saved with the same creation date.

Does anybody know why I get these duplicates and what I can do to avoid them?

This is how I get the pictures:

    PHFetchOptions *fetchOptions = [PHFetchOptions new];
    fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES],];
    PHFetchResult *phAssets = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
olivier
  • 463
  • 4
  • 6

5 Answers5

6

Since iOS 8.1, the behavior of the fetchAssetsWithMediaType: and fetchAssetsWithOptions: methods has changed, and they no longer include photos synchronized to the device from iTunes or photos stored in an iCloud Shared Photo Stream.

Source: Document Revision History and PHAsset Class Reference.

Rufel
  • 2,630
  • 17
  • 15
  • I'm shocked -- _shocked!_ -- to discover that both of the Apple links are broken. I'm actively researching this, and sincerely would love to see documentation or back-up of this claim. – Clay Bridges Jan 14 '15 at 19:09
  • 1
    @ClayBridges: The second link points to Apple's **PHAsset Class Reference**. If you expand the `fetchAssetsWithMediaType:options:` method documentation, you can read the following note in the **Discussion** section: _IMPORTANT - If this method is called by an app linked on or after iOS 8.1, the results do not include photos synchronized to the device from iTunes or photos stored in an iCloud Shared Photo Stream._ – Rufel Jan 20 '15 at 19:26
5

You can try to use Moments Collections:

PHFetchResult * moments = [PHAssetCollection fetchMomentsWithOptions:nil];            
for (PHAssetCollection * moment in moments) {
    PHFetchResult * assetsFetchResults = [PHAsset fetchAssetsInAssetCollection:moment options:nil];
    for (PHAsset * asset in assetsFetchResults) {
        //Do something with asset, for example add them to array
    }
}
Ponf
  • 1,190
  • 1
  • 12
  • 28
  • The problem with the above approach is that you need to get all the assets beforehand - This takes time. On a iPhone 5S with some thousand images it has taken about 1.5 Seconds. We can't wait that long !! – Tapan Thaker Oct 16 '14 at 05:40
  • @TapanThaker - Where is it spending the time? Is it for grabbing just the PHAsset and putting it in an array or are you doing something else? I will soon be looking at this issue too. – BlueVoodoo Oct 16 '14 at 10:22
  • Just putting it in an arrray. – Tapan Thaker Oct 16 '14 at 11:39
  • The user has thousands of images around 3-4 K – Tapan Thaker Oct 16 '14 at 11:40
  • 1
    This is the approach I am using to avoid duplicates. I have around 5k photos and 250 videos and it plow though in much much less than a second. Are you printing a bunch of stuff or doing something wasteful? The issue that I have a problem with is that PHAssetCollections are immutable so you have to use NSMutableArray instead which doesn't work so well with the new photo notifications. – VaporwareWolf Oct 25 '14 at 02:35
  • Maybe I'm wrong but when I try to go through moments with for loop, compiler gives me an error that moments are not sequential type. I did it with enumerateObjectsUsingBlock. – Lachezar Todorov Jun 15 '16 at 15:37
  • And after implementing, there are more duplicates then before. – Lachezar Todorov Jun 15 '16 at 15:54
3

I had the same problem, and for me, the duplicates were images that were in the my photostream album. To work around the issue, i now use the FetchMoments method from the PHAssetCollection class, and then I fetch all assets for each moment in the fetch result. This way i get all images without getting repeated images.

If someone finds a better solution, please let me know.

Nuno
  • 31
  • 2
  • Thank you very much for your answer. Both yours and the one from Ponf seem to have come right at the same time and are similar. I had to choose and marked his as answer because even though your explanation is better, he included working code, which may make it helpful quicker for people with the same problem. – olivier Sep 24 '14 at 16:00
  • Yep this is what I've been using as well. It works well and still picks up iCloud photos. – VaporwareWolf Oct 25 '14 at 02:36
0

On a flyer, are these assets are part of a burst? (cf. PHAsset.burstIdentifier, etc.) If so, you can adjust accordingly.

Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
0

you can use the "PHImageRequestOptions" to setup only high quality images for example!

//Setting up the deliveryMode in PHImageRequestOptions()
fileprivate func imageRequestOptions() -> PHImageRequestOptions {
    let requestOption = PHImageRequestOptions()
    requestOption.deliveryMode = .highQualityFormat
    return requestOption
}

fileprivate func fetchImages(){

    let fetchOptions = assetsFetchOptions() //get fetchOptions only. Don`t worry
    let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)

    allPhotos.enumerateObjects({ (asset, index, stop) in
        print(asset)

        let imageManager = PHImageManager.default()
        let targetSize = CGSize(width: 200, height: 200)

        //This function uses the "imageRequestOptions()" function to set up the "options:" field in this .requestImage() function.
        imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: self.imageRequestOptions(), resultHandler: { (image, info) in

            if let image = image {
                self.images.append(image)
                self.assets.append(asset)

                if self.selectedImage == nil {
                    self.selectedImage = image
                }
            }

            if index == allPhotos.count - 1 {
                self.collectionView?.reloadData()
            }
        })
    })
}