Search code examples
swiftswiftuiphotokit

Not possible to loop over PHFetchResult with ForEach


I'm currently working on a photos app for iOS / macOS and I'm struggling with PhotoKit.

I did create a class where I manage all my PhotoKit requests.

class PhotosAPI: ObservableObject {
    
    @Published var all = PHFetchResult<PHAsset>()
    @Published var allAlbums = PHFetchResult<PHAssetCollection>()
    @Published var allSmartAlbums = PHFetchResult<PHAssetCollection>()
    
    // Functions to get the Collections / Assets    
}

This part is working so far but now I'm struggeling with showing those data in my View.

In my View I would like to present all assets in a List / Grid

struct ShowImages: View {
    @ObservedObject var photos = PhotosAPI()

    var body: some View {
        List(photos.all, id: \.self) { item in
            Text("\(item)")
        }
    }
}

But I do get an error:

Initializer 'init(_:id:rowContent:)' requires that 'PHFetchResult' conform to 'RandomAccessCollection

I did try all day today to fix this but I wasn't successful and I couldn't find anything useful in google.

Does anyone have an idea how I can get PHFetchResults to loop over them?

At the end I was able to show the pictures with below code. But this looks like very bad code to me. I would prefer to loop directly over the PHFetchResult. Does anyone know how I can get this done?

ForEach(0..<photos.all.count) { index in
    Text("\(photos.all.object(at: index).localIdentifier)")
}  

Solution

  • You can implement RandomAccessCollection for PHFetchResult or create wrapper that conforms RandomAccessCollection.

    struct PHFetchResultCollection: RandomAccessCollection, Equatable {
    
        typealias Element = PHAsset
        typealias Index = Int
    
        let fetchResult: PHFetchResult<PHAsset>
    
        var endIndex: Int { fetchResult.count }
        var startIndex: Int { 0 }
    
        subscript(position: Int) -> PHAsset {
            fetchResult.object(at: fetchResult.count - position - 1)
        }
    }
    

    Then you will be able to use PHFetchResultCollection with ForEach

    let collection = PHFetchResultCollection(fetchResult: fetchResult)
    
    var body: some View  {
        ForEach(collection, id: \.localIdentifier) {
            ...
        }
    }