PHAssets/PHAssetCollection/PHCollectionList and Folders

3.9k views Asked by At

I am following Apple's Sample code 'ExampleappusingPhotosframework'.

In the subject of PHAssets/PHAssetCollection, I noticed there is the possibility of 'Folders' being present. These can appear from being created be Mac's Photo's App, or an older iPhoto app, ect....

There are possibilities of a folder being present amount PHAssetCollection's and that folder can contain unknown levels of nested folders, ending with an Album(s) at the end.

So, even above sample project crashes if I have nested folders in the photo library. Most app's just seem to ignore and not show these folders at all.

For an example lets say this example exists:

enter image description here

Folder Level 1 > Folder Level 2 > Custom Album > Image1, Image2

How iOS Photos App Handles it:

enter image description here

Task

In the Root Album list, under user albums, "Folder Level 1" should be seen. When that's tapped, I want to list all the images from all the sub folders regardless of sub levels.....

viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()

    let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addAlbum))
    self.navigationItem.rightBarButtonItem = addButton


    // Create a PHFetchResult object for each section in the table view.
    let allPhotosOptions = PHFetchOptions()
    allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
    allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
    smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
    userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil)
    PHPhotoLibrary.shared().register(self)

}

cellForRowAt:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    switch Section(rawValue: indexPath.section)! {
        case .allPhotos:
            let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.allPhotos.rawValue, for: indexPath)
            cell.textLabel!.text = NSLocalizedString("All Photos", comment: "")
            return cell

        case .smartAlbums:
            let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.collection.rawValue, for: indexPath)
            let collection = smartAlbums.object(at: indexPath.row)
            cell.textLabel!.text = collection.localizedTitle
            return cell

        case .userCollections:
            let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.collection.rawValue, for: indexPath)
            let collection = userCollections.object(at: indexPath.row)

            cell.textLabel!.text = collection.localizedTitle
            return cell
    }
}

Segue:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    guard let destination = (segue.destination as? UINavigationController)?.topViewController as? AssetGridViewController
        else { fatalError("unexpected view controller for segue") }
    let cell = sender as! UITableViewCell

    destination.title = cell.textLabel?.text

    switch SegueIdentifier(rawValue: segue.identifier!)! {
        case .showAllPhotos:
            destination.fetchResult = allPhotos
        case .showCollection:

            // get the asset collection for the selected row
            let indexPath = tableView.indexPath(for: cell)!
            let collection: PHCollection
            switch Section(rawValue: indexPath.section)! {
                case .smartAlbums:
                    collection = smartAlbums.object(at: indexPath.row)
                case .userCollections:
                    collection = userCollections.object(at: indexPath.row)
                default: return // not reached; all photos section already handled by other segue
            }

            // configure the view controller with the asset collection
            guard let assetCollection = collection as? PHAssetCollection
                else { fatalError("expected asset collection") }
            destination.fetchResult = PHAsset.fetchAssets(in: assetCollection, options: nil)
            destination.assetCollection = assetCollection
    }
}

Question

assetCollection?.canContainCollections seems to be the deciding factor. However, how would I fetch all the containing PHAssets within the folder, regardless of the sub-levels/sub-folders? (This cannot be impossible since even the stock app has "All Photos" inside level 1)

1

There are 1 answers

2
holtmann On BEST ANSWER

With the current developer APIs (PhotoKit) there is no easy way (like with a single request) to list all assets in a folder (and its subfolders/albums). However, you can of recursively enumerate all albums in the folder and it's subfolders and then put the results into a single array. This however can be slow depending on the number of levels and assets contained in the nested structure. Please note, that a folder on any level can also contain an album (e.g. on "Folder Level 1" there could also be an album in addition to the folder "Folder Level 2"). Apple's Photos App is not using PhotoKit - so I assume they have a way to do this with a single request.