iosphotosframework

Photo Editing Extension - Revert Image to Original


I'm developing a photo editing extension for iOS, bundled in a container app that provides the same photo editing functionality.

In order to reuse code, I have a view controller class that adopts the required PHContentEditingController protocol, and I subclassed it for use both as the main interface of the app extension, and as the "working screen" of the container app.


On the editing extension, the controller's methods are called by the Photos app's editing session as described in Apple's documentation and various tutorials you can find around the web.

On the container app, on the other hand, I first obtain a PHAsset instance by means of the UIImagePickerController class, and directly start the editing session manually on my "work" view controller like this:

// 'work' is my view controller which adopts
// `PHContentEditingController`. 'workNavigation'
// embeds it.

let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { (adjustmentData) in
    return work.canHandle(adjustmentData)
}

asset.requestContentEditingInput(with: options, completionHandler: { [weak self] (input, options) in
    // (Called on the Main thread on iOS 10.0 and above)
    guard let this = self else {
        return
    }
    guard let editingInput = input else {
        return
    }
    work.asset = asset
    work.startContentEditing(with: editingInput, placeholderImage: editingInput.displaySizeImage!)
    this.present(workNavigation, animated: true, completion: nil)
})

When the user finishes editing, the work view controller calls finishContentEditing(completionHandler: on itself to finish the session:

self.finishContentEditing(completionHandler: {(output) in
    // nil output results in "Revert" prompt.
    // non-nil output results in "Modify" prompt. 

    let library = PHPhotoLibrary.shared()
    library.performChanges({
        let request = PHAssetChangeRequest(for: self.asset)
        request.contentEditingOutput = output

    }, completionHandler: {(didSave, error) in
        if let error = error {
            // handle error...

        } else if didSave {
            // proceed after saving...

        } else {
            // proceed after cancellation...
        }
    })
})

Within the editing session, the user can 'clear' the previous edits passed as adjustment data, effectively reverting the image to its original state. I've noticed that, if I finish the editing by calling the completion handler passed to finishContentEditing(completionHandler:) with nil as its argument (instead of a valid PHContentEditingOutput object), the Photos framework will prompt the user to "revert" the image instead of "modifying" it:

func finishContentEditing(completionHandler: @escaping (PHContentEditingOutput?) -> Void) {

    guard let editingInput = self.editingInput, let inputURL = editingInput.fullSizeImageURL else {
        return completionHandler(nil)
    }

    if editingInput.adjustmentData != nil && hasUnsavedEdits == false {
        // We began with non-nil adjustment data but now have 
        // no outstanding edits - means REVERT:

        return completionHandler(nil)
    }

    // (...proceed with writing output to url...)

However, this only works when running from the container app. If I try the same trick from the extension (i.e., load an image that contains previous edits, reset them, and tap 'Done') I get the dreaded "Unable to Save Changes" message...


What is the correct way to revert previous edits to an image from within a photo editing extension?


Solution

  • Months later, still no answer, so I begrudgingly adopted this workaround (which is still preferable to an error alert):

    When the user taps "Done" from the Photos extension UI and the image has no edits applied to it (either because the user reset previous edits, or didn't apply any changes to a brand new image), perform the following actions from within finishContentEditing(completionHandler:):

    1. Create adjustment data that amounts to no visible changes at all ("null effect") and archive it as Data.

    2. Create a PHAdjustmentData instance with the "null effect" data from above, with the formatVersion and formatIdentifier properly set.

    3. Create a PHContentEditingOutput instance from the editing input passed at the beginning of the session (as usual), and set the adjustment data created above.

    4. Read the unmodified image from the inputURL property of the editing input, and write it unmodified to the url specified by the PHContentEditingOutput instance's renderedContentURL property.

    5. Call the completionHandler block, passing the editing output instance (as normal).

    The result: The image is saved in its original state (no effects applied), and no alerts or errors occur.

    The drawback: The library asset remains in the 'edited' state (because we passed non-nil editing output and adjustment data, there was no other choice), so the next time the user tries to edit it from Photos.app, the red 'Revert' button will be present:

    enter image description here

    However, choosing 'revert' results in no visible changes to the image data (duh!), which can be confusing to the user.

    —-

    Update

    I checked what the built-in “Markup” extension does:

    enter image description here

    ...and it is consistent with my workaround above, so I guess this is the best that can be done.