4

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?

Kenny Wyland
  • 20,844
  • 26
  • 117
  • 229

2 Answers2

2

Actually, the asset's property is changed, but it doesn't appear when PHPhotoLibrary didn't modify it with the change.

You can try to fetch the updated asset by calling PHAsset's fetchAssetsWithLocalIdentifiers: method to see to proper result.

MatthewLuiHK
  • 691
  • 4
  • 10
  • 1
    But the docs say the asset is supposed to reflect the change requested inside the performChanges block. Why would I need to refetch it? – Kenny Wyland Nov 07 '15 at 19:37
  • Yes, what you mentioned is what I saw in the document too. I haven't figure it out yet. I only did a quick test about setting the change and saw the result in Photos app, and the photo's property did change eventually... – MatthewLuiHK Nov 10 '15 at 05:58
  • Yeah, but "eventually" doesn't really help the UI for my user. :) – Kenny Wyland Nov 10 '15 at 22:12
  • I think you can fetch the PHAsset with update infomation via fetchAssetsWithLocalIdentifiers: , replace the exist one in your storage and triger a UI update by yourself. – MatthewLuiHK Nov 11 '15 at 13:53
  • Force refetching via `fetchAssetsWithLocalIdentifiers` does not work - the `favorite` status is still the old value afterwards. – Jordan H Jan 09 '16 at 03:09
2

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()
        }
    }
}
Jordan H
  • 52,571
  • 37
  • 201
  • 351