Created by Ningyuan 2020/05/23html
先扔个官方设计指南 Human Interface Guidelines:Photo Editingios
iOS 8 以后,苹果提供了几个应用扩展功能,分别是Today Widget
、Share
、Action
、Photo Editing
、Storage Provider
、Custom Keyboard
,丰富了iPhone的操做体验。本次介绍的,是Photo Editing,中文译名为照片(图片)编辑。objective-c
看了下网上的教程,都是比较旧的版本,因而摸索了一下,步骤以下网络
新建一个
Single View App
项目session
新建完毕,选中项目 - TARGETS - General - 点击 侧栏下方的 "+",添加Extension项目app
向下翻,找到 Photo Editing Extension 并选中,Nextasync
在这边填上ProductName -> Finishide
这时Xcode弹窗,是否启用新解决方案,选择 Activate函数
这样就完成了Photo Editing Extension 的新建,如图,Xcode会帮咱们新建默认文件布局
先看一下info.plist文件,能够看到新增了Key-Value:NSExtension,展开其全部子项
PHSupportedMediaTypes:支持编辑的类型,默认为Image类型,还能够添加
NSExtensionMainStroyboard:stroyboard名称
NSExtensionPointIdentifier:照片编辑扩展,标识,不需更改
MainInterface.storyboard,这个是照片扩展的主界面,已经自动生成了"Hello World",Run Target中应该会自动选中咱们当前的扩展应用,运行时选中要调起扩展的App,这里选择Photos。
启动相册后,随便点开一张照片,而后点击右上角"编辑"。
后续在安装本体应用后,也能够直接在图库中选中编辑,进入咱们的扩展应用。
接下来会进入系统照片编辑界面,在右上角找到更多按钮"···"(系统不一样,所在位置不一样),点击
在展开的界面中,能够看到当前可用的扩展应用,PhotoExtensionTest、extension我是以前新建的照片编辑扩展应用,Ex则是刚刚新建的。
若是在这里没看到,可点击"更多"查看。
随后在弹出的界面中的建议这里选择便可。
而后咱们选中刚刚的Ex,就打开了扩展应用。
打开PhotoEditingViewController.m,能够看到系统自动生成以下代码,界面布局咱们能够直接在MainInterface.storyboard中直接布局,也能够经过代码的形式布局。
#import "PhotoEditingViewController.h"
#import <Photos/Photos.h>
#import <PhotosUI/PhotosUI.h>
@interface PhotoEditingViewController () <PHContentEditingController>
@property (strong) PHContentEditingInput *input;
@end
@implementation PhotoEditingViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#pragma mark - PHContentEditingController
- (BOOL)canHandleAdjustmentData:(PHAdjustmentData *)adjustmentData {
// 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 NO;
}
- (void)startContentEditingWithInput:(PHContentEditingInput *)contentEditingInput placeholderImage:(UIImage *)placeholderImage {
// 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".
self.input = contentEditingInput;
}
- (void)finishContentEditingWithCompletionHandler:(void (^)(PHContentEditingOutput *))completionHandler {
// 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(QOS_CLASS_DEFAULT, 0), ^{
// Create editing output from the editing input.
PHContentEditingOutput *output = [[PHContentEditingOutput alloc] initWithContentEditingInput:self.input];
// Provide new adjustments and render output to given location.
// output.adjustmentData = <#new adjustment data#>;
// NSData *renderedJPEGData = <#output JPEG#>;
// [renderedJPEGData writeToURL:output.renderedContentURL atomically:YES];
// Call completion handler to commit edit to Photos.
completionHandler(output);
// Clean up temporary files, etc.
});
}
- (BOOL)shouldShowCancelConfirmation {
// Returns whether a confirmation to discard changes should be shown to the user on cancel.
// (Typically, you should return YES if there are any unsaved changes.)
return NO;
}
- (void)cancelContentEditing {
// Clean up temporary files, etc.
// May be called after finishContentEditingWithCompletionHandler: while you prepare output.
}
复制代码
写代码以前,先简单讲一下几个重要方法。
2.1 startContentEditingWithInput: placeholderImage
viewDidLoad以后,会先走这个函数,拿到系统传进来的contentEditingInput,包含了外面传进来的图片原数据,通常能够在这边进行图片显示操做。
- (void)startContentEditingWithInput:(PHContentEditingInput *)contentEditingInput placeholderImage:(UIImage *)placeholderImage {
// 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".
self.input = contentEditingInput;
}
复制代码
2.2 finishContentEditingWithCompletionHandler:
下面这个函数,在点击界面右上角完成
按钮时触发,将对图片修改好的数据,经过block的形式回调。
- (void)finishContentEditingWithCompletionHandler:(void (^)(PHContentEditingOutput *))completionHandler {
// 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(QOS_CLASS_DEFAULT, 0), ^{
// 根据输入元数据input,建立输出数据output
PHContentEditingOutput *output = [[PHContentEditingOutput alloc] initWithContentEditingInput:self.input];
// Provide new adjustments and render output to given location.
// output.adjustmentData = <#new adjustment data#>;
// NSData *renderedJPEGData = <#output JPEG#>;
// [renderedJPEGData writeToURL:output.renderedContentURL atomically:YES];
// Call completion handler to commit edit to Photos.
completionHandler(output);
// Clean up temporary files, etc.
});
}
复制代码
2.3 shouldShowCancelConfirmation
点击取消按钮的时候,会调用此方法。
- (BOOL)shouldShowCancelConfirmation {
// Returns whether a confirmation to discard changes should be shown to the user on cancel.
// (Typically, you should return YES if there are any unsaved changes.)
return NO;
}
复制代码
2.4 canHandleAdjustmentData:
是否使用已修改过的数据。
- (BOOL)canHandleAdjustmentData:(PHAdjustmentData *)adjustmentData {
// 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 NO;
}
复制代码
2.5 cancelContentEditing
会在 finishContentEditingWithCompletionHandler: 以后进行调用,通常作一些清理临时文件等工做。
- (void)cancelContentEditing {
// Clean up temporary files, etc.
// May be called after finishContentEditingWithCompletionHandler: while you prepare output.
}
复制代码
其余一些自定义布局代码就不放出来了,这里仍是讲一下主要功能代码,其余的网络上也有很多,可自行学习查看。
进入获取input、placeholderImage图片
- (void)startContentEditingWithInput:(PHContentEditingInput *)contentEditingInput placeholderImage:(UIImage *)placeholderImage {
// Input输入
self.input = contentEditingInput;
// 获取选择图片
self.originalImage = placeholderImage;
// 本身处理图片布局之类的
[self updateImageViewWithImage:placeholderImage];
}
复制代码
取消编辑的提示。
- (BOOL)shouldShowCancelConfirmation {
// iconImageView是我定义的一个添加到原始图片上的imageView
// 这里的条件本身设置,我这边只是简单地判断iconImagView.image是否为空
// 不为空则说明已经对图片进行了修改,return YES,则告诉系统须要弹窗提示
if (self.iconImageView.image != nil) {
return YES;
}
return NO;
}
复制代码
编辑图片完成,将图片数据传出去。
- (void)finishContentEditingWithCompletionHandler:(void (^)(PHContentEditingOutput *))completionHandler {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
PHContentEditingOutput *output = [[PHContentEditingOutput alloc] initWithContentEditingInput:self.input];
// 输出图片数据
NSData *imageData = UIImageJPEGRepresentation([self snapShotWithView:self.imageView], 1.0);
// 为图片添加标识符跟版本号,下次进来的时候,会触发canHandleAdjustmentData:方法,能够拿到本次写入的数据,来判断是否使用该图片做为基础进行二次编辑
PHAdjustmentData *adjustmentData = [[PHAdjustmentData alloc] initWithFormatIdentifier:@"com.PhotoExtensionTest" formatVersion:@"1.0" data:imageData];
output.adjustmentData = adjustmentData;
NSData *renderedJPEGData = imageData;
[renderedJPEGData writeToURL:output.renderedContentURL atomically:YES];
// 回调,确认当前修改
completionHandler(output);
});
}
/// 截图
- (UIImage *)snapShotWithView:(UIView *)view {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
[view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
复制代码
canHandleAdjustmentData: 拿到标识符,使用已编辑的图片数据,继续二次编辑
- (BOOL)canHandleAdjustmentData:(PHAdjustmentData *)adjustmentData {
NSString *formatIdentifier = adjustmentData.formatIdentifier;
NSString *formatVersion = adjustmentData.formatVersion;
NSData *storageData = adjustmentData.data;
UIImage *image = [UIImage imageWithData:storageData];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 0, 100, 100 * (image.size.height / image.size.width));
// 拿到存储的标识符与版本号,则return YES
NSLog(@"formatIdentifier:%@ - formatVersion:%@", formatIdentifier, formatVersion);
if (formatIdentifier && formatVersion) {
return YES;
}
// return NO表示不取adjustmentData里的已编辑数据,始终取原数据
return NO;
}
复制代码
编辑界面以下,在底部选择一个小icon,添加至修改图片。
点击完成后,会将数据传到系统图片编辑界面,再次点击完成,则会自动返回相册,能够看到修改为功。
此时再对已修改的图片点击编辑,能够看到系统编辑界面右下角显示"复原",点击则会恢复成本来的图片。
咱们在这里再次右上角的更多按钮,打开咱们的扩展应用。
此时能够看到,启动扩展应用后,点击底部的icon就又添加了一个icon在图片上,这是由于代码中设置的canHandleAdjustmentData: 生效的结果。