肇鑫的技术博客

业精于勤,荒于嬉

Something Apple Didn't Say Right On Photos Framework

I am developing an app basing on Photos framework. However, I find that Photos framework is something mainly for Photos.app and not well documented.

I will listed them for further references.

System Default Alert Message May Confuse Users.

When removing an album from photo library, you must use class func deleteAssetCollections(_ assetCollections: NSFastEnumeration). The system will automatically represents an alert to ask the user whether to remove the album. However, there is a message says that only the album is removed, the photos inside the album are kept.

The message is accurate for Photos.app. However, if we want to remove the photos together with the album, there is no API for that, so we have to call the remove API twice, one for photos, the other for album. There will be two alerts shown and the alert message mentioned may confuse the user as the photos are removed this time.

Folders

Folder can contain other Folders, but not its parents.

Folder, which is called PHCollectionList is said can contain other folders. That leads me to say what if folder A contains folder B and B contains A, which make the relation recursive. So I created an example, and I found that when A contains B and B contains A, photo library would rise an error. So the recursive relation is not allowed.

The Collection operations are all movement, not copying.

When putting other collections into one folder, whether you use addChildCollections(_:) or insertChildCollections(_:at:) is irrelevant. The collections are just moved inside the folder, not copied.

This rule also makes there is not duplicated folders in photo library.

The result of fetchCollections(in:options:) is shallow.

Although fetchCollections(in:options:) says it returns "By default, the returned PHFetchResult object contains all collections in the specified collection list." The document is not accurate. It only returns the shallow collections, not all collections. For example, we have three folders, A, B and C. The relations of them is: A -> B -> C, A contains B, B contains C.

let collections = PHCollection.fetchCollections(in: A, options: nil)
print(collections.count)
// 1. Print 1 instead of 2. As folder B is the only collection with shallow search.

Other constrains of folder operations

PHPhotoLibrary.shared().performChanges {
    let requestA = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderA")
    let requestB = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderB")
    let folderB = requestB.placeholderForCreatedCollectionList
    let requestC = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderC")
    let folderC = requestC.placeholderForCreatedCollectionList
    requestA.addChildCollections([folderB] as NSFastEnumeration) // move B to A, works
    requestB.addChildCollections([folderC] as NSFastEnumeration) // move C to B, works
    requestA.addChildCollections([folderC] as NSFastEnumeration) // move C to A, doesn't work
} completionHandler: { success, error in
    if success {
        print("success")
    } else {
        print(error?.localizedDescription ?? "nil")
    }
}

Result in Photos.app: A -> B -> C
test_1

Line 9 doesn't work. I guess because C has been already in A by contained by B.

However, code below works.

PHPhotoLibrary.shared().performChanges {
    let requestA = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderA")
    let requestB = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderB")
    let folderB = requestB.placeholderForCreatedCollectionList
    let requestC = PHCollectionListChangeRequest.creationRequestForCollectionList(withTitle: "FolderC")
    let folderC = requestC.placeholderForCreatedCollectionList
    requestA.addChildCollections([folderB] as NSFastEnumeration) // move B to A, works
    requestA.addChildCollections([folderC] as NSFastEnumeration) // move C to A, works
    requestB.addChildCollections([folderC] as NSFastEnumeration) // move C to B, works
} completionHandler: { success, error in
    if success {
        print("success")
    } else {
        print(error?.localizedDescription ?? "nil")
    }
}

Apple avoids structural complexity by design.

In Photos.app on macOS, you can only move the collections besides the target folder on the same level. This avoid the complexity of the model structure. In fact, you can move any folder to the target folder. However, that will make not every move operation working, which may confuse user that doesn't familiar with the rules.