Search code examples
iosphotokitphasset

PHAsset doesn't reflect change after PHAssetChangeRequest completes


In the docs it says:

After Photos runs the change block and calls your completion handler, the asset’s state reflects the changes that you requested in the block.

However, inside the completion handler (as well as after the completion handler), my PHAsset hasn't changed. Here's the code I'm using to change the Favorite status, and it's pulled from PHAsset docs page.

- (IBAction)touchedButtonFavoritePhoto:(id)sender {
    AssetViewController *vc = self.viewControllers[0];
    PHAsset *asset = vc.asset;

    NSLog(@"touched fav 1: %d", asset.favorite);

    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        // Create a change request from the asset to be modified.
        PHAssetChangeRequest *request = [PHAssetChangeRequest changeRequestForAsset:asset];
        // Set a property of the request to change the asset itself.
        request.favorite = !asset.favorite;
        NSLog(@"touched fav 2: %d", request.favorite);

    } completionHandler:^(BOOL success, NSError *error) {
        NSLog(@"Finished updating asset. %@: %d", (success ? @"Success." : error), asset.favorite);
        NSLog(@"touched fav 3: %d", asset.favorite);

        [self dispatchMainSynchronously:NO usingBlock:^{
            [self updateFavoriteButtonForAsset:asset];
            NSLog(@"touched fav 4: %d", asset.favorite);
        }];
    }];

    [self dispatchAfter:2.0 usingBlock:^{
        NSLog(@"touched fav 5: %d", asset.favorite);
    }];
}

The -dispatchAfter: and -dispatchMain: functions above are just convenience functions that call gcd functions to perform the block asynchronously after a certain amount of time or perform the block on the main UI thread.

When I run the code, I see that it starts 1) Asset is Not Fav, then 2) Request is Fav, 3) Asset is still Not Fav, 4) Asset is still Not Fav, 5) Asset is still Not Fav.

AppName[6155:3741600] startingPage.asset: <PHAsset: 0x1265f1b30> 4DFE1BBF-C16B-4150-8350-3FF1291B63B6/L0/001 mediaType=1/0, sourceType=1, (3264x2447), creationDate=2015-01-19 00:42:26 +0000, location=1, hidden=0, favorite=0 
AppName[6155:3741600] touched fav 1: 0
AppName[6155:3741879] touched fav 2: 1
AppName[6155:3741879] Finished updating asset. Success.: 0
AppName[6155:3741879] touched fav 3: 0
AppName[6155:3741600] touched fav 4: 0
AppName[6155:3741600] touched fav 5: 0

What am I doing wrong? Why isn't my asset object updating?


Solution

  • This is a bug in the Photos framework. I believe it's a 9.2 regression. In all previous releases the favorite status is properly updated in the completion block as you expected and as the documentation states.

    However, I did find a workaround. In photoLibraryDidChange, note that change details are delivered for this asset after modifying favorite. And you'll note that the objectAfterChanges does have the new favorite status. Therefore, instead of updating your UI immediately after the change request succeeds, update it after the change details are delivered. For example:

    //MARK: PHPhotoLibraryChangeObserver
    
    func photoLibraryDidChange(changeInstance: PHChange) {
        guard let photoAsset = self.asset,
            let changeDetails = changeInstance.changeDetailsForObject(photoAsset)
            else { return }
    
        dispatch_async(dispatch_get_main_queue()) {
            self.asset = changeDetails.objectAfterChanges as? PHAsset
    
            //self.asset now has the proper favorite status
            self.updateFavoriteButton()
    
            if changeDetails.assetContentChanged {
                self.updateImage()
            }
        }
    }