Tuesday, October 28, 2014

How to create Photo Editing Extension in Swift?


In the first Extension Tutorial, you learned about basics of Extension & how it works.

In this tutorial, you will take a deep dive into the Photo Editing Extension. This post will let you know how you can create the photo editing Extension using Swift language.

Photo Editing extension allows the user to edit the photo and video in the Photo app. You can choose your extension to open from the photo app and do some editing and return the edited image to the photo app. But make sure the functionality your extension provides is best suited for Photo Extension.

Create Photo editing extension-

Create a project with “Single View Application” template  and select languages as Swift and set the project name “PhotoExtensionSample”.




Add Target-

In the Xcode project navigator, select the project & add new target to the project. Choose PhotoEditingExtension in Application Extension and named it “Photo Extension”. Also make sure that you have select the language Swift for both Project & target. 



This target will comprises of 3 files- 
1) PhotoExteniosnViewController
2) MainInterface.storyboard 
3) Info.plist

PhotoEditingViewController is a subclass of UIViewController & adopts the PHContentEditingController protocol & includes the life cycle method of PHContentEditingController. Your PhotoEditingViewController will includes the following methods-

1) func canHandleAdjustmentData(adjustmentData: PHAdjustmentData?) -> Bool
2) func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage

3) func finishContentEditingWithCompletionHandler(completionHandler: ((PHContentEditingOutput!) -> Void)!)
4) var shouldShowCancelConfirmation: Bool 
5) func cancelContentEditing()



Run Extension-

Now, if you run your extension then it will gives you the option to run the host app. Choose Photos app. It will launch the photo app & you can select any photo to edit. 



From the photo edit screen tap on the button just before the Done button. It will shows all the Photo Editing Extension & chose your Extension. Now, it will launch your extension. Your extension is showing Hello World. If you taps on the cancel & down button then it will return the control to the host app.




Design User Interface-

Lets make some modification to the MainInterface.storyboard. Add the imageView and slider on the view with appropriate constraints. 

Now defines the outlet and action method for slider in the PHContentEditingController class.


Implement Life cycle methods-

When the control is pass to the extension then its developer responsibility to grab the image and allows the user to edit the photos.
System will call startContentEditingWithInput method & provides the content for editing.

func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage) {
// Present content for editing, and keep the contentEditingInput for use when closing the edit session.
// If you returned YES from canHandleAdjustmentData:, contentEditingInput has the original image and adjustment data.
// If you returned NO, the contentEditingInput has past edits "baked in".
input = contentEditingInput
beginImage = CIImage(image: input?.displaySizeImage)
filter = CIFilter(name: "CISepiaTone")
filter.setValue(beginImage, forKey: kCIInputImageKey)
filter.setValue(0.5, forKey: kCIInputIntensityKey)
let outputImage : CIImage = filter.outputImage
editingImageView?.image = UIImage(CIImage: outputImage)
}

PHContentEditingInput will provides the content for editing the image. Once you get the image, you can perform some editing operation & pass the edited image to the photo app. In this method, i have created the sepia filter and assign the input image with intensity 0.5. You can change this intensity value by changing the slider value & generate the output image.

@IBAction func sliderValueChanged(sender: UISlider){
let sliderValue = sender.value
filter.setValue(sliderValue, forKey: kCIInputIntensityKey)
let outputImage = filter.outputImage
editingImageView?.image = UIImage(CIImage: outputImage)
}

When user taps on the done button it will call finishContentEditingWithCompletionHandler method. In this method, you will create PHContentEditingOutput and apply the changes to the full size image which user has done and calls the completion block by passing the output.

func finishContentEditingWithCompletionHandler(completionHandler: ((PHContentEditingOutput!) -> Void)!) {
// Update UI to reflect that editing has finished and output is being rendered.
// Render and provide output on a background queue.
dispatch_async(dispatch_get_global_queue(CLong(DISPATCH_QUEUE_PRIORITY_DEFAULT), 0)) {
// Create editing output from the editing input.
let output = PHContentEditingOutput(contentEditingInput: self.input)
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(self.filter.valueForKey(kCIInputIntensityKey))
let newAdjustmentData = PHAdjustmentData(formatIdentifier: self.formatIdentifier,
formatVersion: self.formatVersion,
data: archivedData)
output.adjustmentData = newAdjustmentData
// Write the JPEG Data
let fullSizeImage = CIImage(contentsOfURL: self.input?.fullSizeImageURL)
UIGraphicsBeginImageContext(fullSizeImage.extent().size);
self.filter.setValue(fullSizeImage, forKey: kCIInputImageKey)
UIImage(CIImage: self.filter.outputImage).drawInRect(fullSizeImage.extent())
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
let jpegData = UIImageJPEGRepresentation(outputImage, 1.0)
UIGraphicsEndImageContext()
jpegData.writeToURL(output.renderedContentURL, atomically: true)
// Call completion handler to commit edit to Photos.
completionHandler?(output)
// Clean up temporary files, etc.
}
}

Here we also need to set the adjustmentData for output. PHAdjustmentData is initialised with formatIdentifier, formatVersion & data. Format identifier should be unique so assign your bundle identifier as Format Identifier & pass the slider value in the archived form to the Adjustment data.

When user tries to edit the image which has been previously edited then system will call canHandleAdjustmentData. In this method, you can check that your extension can handles the adjustment data or not and return the appropriate BOOL value.

let formatIdentifier = "com.mindfire.PhotoExtensionSample"
let formatVersion    = "1.0"

func canHandleAdjustmentData(adjustmentData: PHAdjustmentData?) -> Bool {
// Inspect the adjustmentData to determine whether your extension can work with past edits.
// (Typically, you use its formatIdentifier and formatVersion properties to do this.)
return adjustmentData?.formatIdentifier == formatIdentifier &&
adjustmentData?.formatVersion == formatVersion
//        return false
}

After this method, it will call startContentEditingWithInput method. In this method, you can resume the editing in the image if it has been previously edited by your extension. startContentEditingWithInput has the property adjustmentData which will return the adjusted value which you have set in the finishContentEditingWithCompletionHandler method.

func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage) {
// Present content for editing, and keep the contentEditingInput for use when closing the edit session.
// If you returned YES from canHandleAdjustmentData:, contentEditingInput has the original image and adjustment data.
// If you returned NO, the contentEditingInput has past edits "baked in".
input = contentEditingInput
beginImage = CIImage(image: input?.displaySizeImage)
filter = CIFilter(name: "CISepiaTone")
filter.setValue(beginImage, forKey: kCIInputImageKey)
if let adjustmentData = contentEditingInput?.adjustmentData {
let value = NSKeyedUnarchiver.unarchiveObjectWithData(adjustmentData.data) as NSNumber
filter.setValue(value, forKey: kCIInputIntensityKey)
slider?.value = value.floatValue
}
else {
filter.setValue(0.5, forKey: kCIInputIntensityKey)
}
let outputImage : CIImage = filter.outputImage
editingImageView?.image = UIImage(CIImage: outputImage)
}

Now, run your extension it will look like this-




Discard Changes-

If you taps on the cancel button it will directly discard all the changes. If you want some confirmation from user before it discard the changes. You need to return true value from shouldShowCancelConfirmation method. 




You can download the sample app from Github.



1 comment: