(原文:Understanding Photo Editing Extensions in iOS 8 作者:Joyce Echessa 译者:X140Yu)
Photo Editing Extension 允许用户在 Photos 应用里使用第三方应用编辑照片或视频。在此之前,用户不得不先在相机应用里拍摄照片,然后切换到照片编辑应用里编辑,或者必须从相册里导入照片。现在,这步应用间的切换可以被省略了,用户能不从照片应用切换出去就能编辑照片了。在 Photo Editing Extension 里编辑完成并确认修改后,Photos 应用里的图片也会获得同样的调整。照片的初始版本也保存下来,这样用户可以随时恢复在扩展应用里做出的修改。
我们一起来看看怎样制作一个 Photo Editing Extension。由于我们的关注点在创建应用程序扩展而不是写一个完整的应用,我已经创建了一个你们可以下载并且一路跟着做的上手项目。
这是一个识别图片上的脸然后自动给脸部打上马赛克的简单应用。在没有识别出脸的情况下,图片是不会被更改的。这将会成为我们扩展应用的母应用。扩展应用必须是母应用的一部分,不可能只有一个单单的扩展应用。如果想更多地了解一下扩展应用是什么原理,你可以浏览我们之前写的关于Today extension的一篇文章。
我把图片处理的代码放在了一个框架里。这样我就可以在扩展应用和母应用里使用同一份代码。当你创建了一个应用扩展,它必须是它自己的 target,所以如果你想在主程序和扩展程序里重用这些代码,你必须把公共的代码嵌入进框架中去。在下载的程序中,我已经创建了框架。如果你想了解如何创建,阅读一下上一段提到的文章。
我们将创建一个对于其它应用也可以使用这种功能的扩展应用。
创建扩展应用
一开始,在项目导航中选择 FaceBlur project,然后选择 Editor > Add Target。选择 iOS > Application Extension > Photo Editing Extension 然后点击 Next。
把 Product Name 设置成 FaceBlur Filter,语言选择 Swift,其它保持不变。
当询问你时候是否启用新解决方案的时候,选择 Activate。
在项目导航器里有一个标有 FaceBlur Filter 的新组。它包含三个文件 - PhotoEditingViewController.swift, MainInterface.storyboard 和 Info.plist。
在 MainInterface.storyboard 中,视图构建器是我们在之前 iOS 版本中使用过的,它没有自适应布局(adaptive layout)。View controller 是 iPhone 大小,并且在视图构建器的底部没有 Size Class 的控制区域。但是我们想创建一个可以适应各种不同尺寸屏幕的应用扩展程序。
在 Document Outline 中选择 Photo Editing View Controller。在 File Inspector 中,把 Use Size Classes 打勾,并在弹出的窗口中选择确认。
在 storyboard 中删掉这个 View Controller,从 Object Library 里拽一个新的 View Controller。正如你所见,它将是一个接近正方形的形状而不是一个特定型号设备的形状。
在 Identity inspector,把这个 View Controller 的类设置成 PhotoEditingViewController,并且在 Attributes Inspector 里把 Is Initial View Controller 打勾。
在 Document Outline 里,选中 View ,在 Attributes Inspector 里把它的颜色设置成 Dark Gray Color。
向 View 的底部拽两个按钮。各放在左右。把它们的颜色设置成白色,并把左右按钮的标题分别设置成 Cancel 和 Add Filter。现在你的 View 应该跟下图的 View 差不多。
选中 Cancel 按钮,向它的左侧和底部添加约束。
选中 Add Filter 按钮,向它的右侧和底部添加约束。
向主 View 中拽一个 Image View。调整它的大小,来让它的顶部,左部和右部边缘能够紧贴 View 的边缘。拖住它的底部一直向下拉,直到在靠近按钮的时候出现了蓝色的导航线再停下。保持 Image View 为选中状态,选择 Pin 菜单,把 Constrain to margins 取消。把顶部,左侧和右侧的约束设置成0,对于底部的约束,单击底部约束下拉菜单,选择 Bottom Layout Guide。添加这四个约束。
按住 Control 键,同时从 Image View 到 PhotoEditingViewController 类拖动鼠标,为 Image View 添加一个 ouelet(使用 Assistant editor,把左右两个文件分别调整为 storyboard 和 类文件)。把 outlet 命名为 imageView。为底部的两个按钮分别添加 outlet,命名为 cancelButton 和 addFilterButton。
按住 Control 键,同时从 Cancel 按钮到 PhotoEditingViewController 类拖动鼠标,把 Connection 改为 Action。把 Type 设置成 UIButton,命名它为 cancel。对于 Add Filter 也一样,把这个 Action 命名为 addFilter。你现在类应该拥有如下的 outlets 和 actions。
@IBOutlet weak var imageView: UIImageView! @IBOutlet weak var cancelButton: UIButton! @IBOutlet weak var addFilterButton: UIButton! @IBAction func cancel(sender: UIButton) { } @IBAction func addFilter(sender: UIButton) { }
PhotoEditingViewController 类遵从了 PHContentEditingController 协议,在这个类里会有几个协议方法的实现。
startContentEditingWithInput(contentEditingInput:, placeholderImage:) - 这个方法在 View Controller 出现之前并且 asset 可以被编辑时被调用。它被传入两个对象 - 一个 PHContentEditingInput 实例(要被编辑的 asset)和一个 UIImage 实例(可能是原本的图像或者根据下面提到的 canHandleAdjustmentData(adjustmentData:) 方法的返回值得到的被编辑过的图像)。
finishContentEditingWithCompletionHandler(completionHandler:) - 这个方法在图像被编辑以后被调用。一个存储编辑和描述编辑调整的数据的 PHContentEditingOutput 实例会被创建。这个实例会被传入 completionHandler。
cancelContentEditing – 这个方法在用户取消编辑时被调用。你应当在这里清理不需要的资源。
canHandleAdjustmentData(adjustmentData:) – 如果你的扩展应用能对之前的编辑生效,那这个方法返回 true。
我们将处理以上这几个方法。
先导入 ImageProcessingKit 框架。在文件添加以下代码。
import ImageProcessingKit
在类中添加以下两个变量。
var processedImage: UIImage? let processor = ImageProcessor()
按照以下形式修改 addFilter()。
@IBAction func addFilter(sender: UIButton) { if let input = input { processedImage = processor.processImage(input.displaySizeImage) imageView.image = processedImage } }
这里把显示的照片改成了编辑后的照片而并没有把改变应用于图片。如果一个扩展应用真的提供了许多种滤镜,那么这里可以让用户在选择确定之前,来尝试多种不同的滤镜。
按照下面的形式修改 cancel(),当用户点击 Cancel 按钮时,把 Image View 设置成原来的图片。
@IBAction func cancel(sender: UIButton) { if let input = input { imageView.image = input.displaySizeImage processedImage = nil } }
按照以下形式修改 startContentEditingWithInput()。
func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?, placeholderImage: UIImage) { input = contentEditingInput if let input = contentEditingInput { imageView.image = input.displaySizeImage } }
当编辑模式将要启动时,这个方法能够获取输入的图片并把它显示在 Image View 里。
按照以下形式修改 finishContentEditingWithCompletionHandler() 。
func finishContentEditingWithCompletionHandler(completionHandler: ((PHContentEditingOutput!) -> Void)!) { if input == nil { self.cancelContentEditing() return } dispatch_async(dispatch_get_global_queue(CLong(DISPATCH_QUEUE_PRIORITY_DEFAULT), 0)) { let contentEditingOutput = PHContentEditingOutput(contentEditingInput: self.input) let archiveData = NSKeyedArchiver.archivedDataWithRootObject("Face Blur") let identifier = "com.appcoda.FaceBlur.FaceBlur-filter" let adjustmentData = PHAdjustmentData(formatIdentifier: identifier, formatVersion: "1.0", data: archiveData) contentEditingOutput.adjustmentData = adjustmentData if let path = self.input!.fullSizeImageURL.path { var image = UIImage(contentsOfFile: path)! image = self.processor.processImage(image) let jpegData = UIImageJPEGRepresentation(image, 1.0) var error: NSError? let saveSucceeded = jpegData.writeToURL(contentEditingOutput.renderedContentURL, options: .DataWritingAtomic, error: &error) if saveSucceeded { completionHandler(contentEditingOutput) } else { println("Save error") completionHandler(nil) } } else { println("Load error") completionHandler(nil) } } }
首先,我们检查一下是否有图片被选中,如果没有,什么都不做,直接 return。如果有,我们启动一个异步 block 来做修改的工作。
接着,通过传进来的对象创建了一个 PHContentEditingOutput 实例。我们又创建了一个 PHAdjustmentData 实例。随着修改的进行,调整的数据也会被一路保存下来。PHAdjustmentData 对象提供了一个你可以用来重构编辑的“菜谱”,如果你的扩展应用提供了多种不同的滤镜,这个对象能把应用滤镜的顺序给保存下来,所以它们可以随时恢复到之前某一个已编辑图片的状态。这个数据需要一个独一无二的标识和一个版本号码。对于标识,我们把域名标识倒过来来表示,为了保证它的独一无二性。版本号码可以帮助你扩展应用后面的版本不把你先前版本不同的给归档。
然后,我们把需要修改的整张图片从磁盘中载入并应用滤镜。然后,把这个数据转换成 JPEG 格式,重新写入磁盘。如果成功了,completion handler 会被调用,并且被传入修改后的对象。
为了运行此扩展,先要检察解决方案是不是被设置成 FaceBlur Filter。运行工程,会弹出一个窗口让你选择通过哪个应用来运行。我们选择 Photos。
Photos 应用会启动。选择一张图片并进入编辑模式。在底部的右下角,会有一个内含三个点的圆形图标。
单击它来激活第三方编辑图片的应用程序扩展。
选择 FaceBlur 然后点击 Add Filter。如果检测出了脸,这张图片会被处理并且被编辑。你可以选择取消修改或者选择确认来保存这些修改。
在选择确定以后,会返回 Photos 应用,这里将显示编辑过后的图像。
你也可以在照片的编辑模式下按 Revert 来取消之前用滤镜做的修改。
总结
我们已经了解了如何创建一个简单的照片编辑应用扩展了。你也可以把它变得更复杂:创建一个可以处理先前编辑的应用扩展。我希望这篇博文能在你处理你自己的应用扩展时,给你一个良好的参照。如果你想得到关于应用扩展的更多信息,一定要看看 App Extension Programming Guide。
为了让你有个参考,你可以下载整个 Xcode 项目。
注意:当然运行这个应用扩展时,你可能会注意到一些错误还有 CoreMedia 的许可信息。这些在 Xcode 6 的 GM 版本发布后开始出现,所以在以后的版本可能不会出现,或者在 iOS 8 将来的版本会被修复。但是现在来看,除了这些问题,整个应用扩展运行良好。