阅读 352

iOS 在系统相册调用自己的应用编辑图片 - Photo Editing Extension

Created by Ningyuan 2020/05/23

先扔个官方设计指南 Human Interface Guidelines:Photo Editing

iOS 8 之后,苹果提供了几个应用扩展功能,分别是Today WidgetShareActionPhoto EditingStorage ProviderCustom Keyboard,丰富了iPhone的操作体验。本次介绍的,是Photo Editing,中文译名为照片(图片)编辑。

1. 新建

看了下网上的教程,都是比较旧的版本,于是摸索了一下,步骤如下

新建一个Single View App项目

1@2x.png
[1@2x.png

新建完毕,选中项目 - TARGETS - General - 点击 侧栏下方的 "+",添加Extension项目

2@2x.png
[2@2x.png

向下翻,找到 Photo Editing Extension 并选中,Next

3@2x.png
[3@2x.png

在这边填上ProductName -> Finish

4@2x.png
[4@2x.png

这时Xcode弹窗,是否启用新解决方案,选择 Activate

5@2x.png
[5@2x.png

这样就完成了Photo Editing Extension 的新建,如图,Xcode会帮我们新建默认文件

6@2x.png
[6@2x.png

先看一下info.plist文件,可以看到新增了Key-Value:NSExtension,展开其所有子项

PHSupportedMediaTypes:支持编辑的类型,默认为Image类型,还可以添加

NSExtensionMainStroyboard:stroyboard名称

NSExtensionPointIdentifier:照片编辑扩展,标识,不需更改

7@2x.png
[7@2x.png

MainInterface.storyboard,这个是照片扩展的主界面,已经自动生成了"Hello World",Run Target中应该会自动选中我们当前的扩展应用,运行时选中要调起扩展的App,这里选择Photos。

8@2x.png
[8@2x.png

启动相册后,随便点开一张照片,然后点击右上角"编辑"。

后续在安装本体应用后,也可以直接在图库中选中编辑,进入我们的扩展应用。

10@2x.png
[10@2x.png

接下来会进入系统照片编辑界面,在右上角找到更多按钮"···"(系统不同,所在位置不同),点击

11@2x.png
[11@2x.png

在展开的界面中,可以看到当前可用的扩展应用,PhotoExtensionTest、extension我是之前新建的照片编辑扩展应用,Ex则是刚刚新建的。

如果在这里没看到,可点击"更多"查看。

12@2x.png
[12@2x.png

随后在弹出的界面中的建议这里选择即可。

13@2x.png
[13@2x.png

然后我们选中刚刚的Ex,就打开了扩展应用。

14@2x.png
[14@2x.png

2. 代码了解

打开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.
}
复制代码

3. 代码实现

其他一些自定义布局代码就不放出来了,这里还是讲一下主要功能代码,其他的网络上也有不少,可自行学习查看。

进入获取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;
}
复制代码

4. 效果

编辑界面如下,在底部选择一个小icon,添加至修改图片。

15@2x.png
[15@2x.png

点击完成后,会将数据传到系统图片编辑界面,再次点击完成,则会自动返回相册,可以看到修改成功。

16@2x.png
[16@2x.png

此时再对已修改的图片点击编辑,可以看到系统编辑界面右下角显示"复原",点击则会恢复成原本的图片。

我们在这里再次右上角的更多按钮,打开我们的扩展应用。

17@2x.png
[17@2x.png

此时可以看到,启动扩展应用后,点击底部的icon就又添加了一个icon在图片上,这是因为代码中设置的canHandleAdjustmentData: 生效的结果。

18@2x.png
[18@2x.png

5. 小结

  • Photo Editing Extension 只能由系统App,Photos启动
  • 该拓展可以实现对照片的修改,并且系统可将其复原,不用担心原数据被影响
  • 照片修改后可设置标识符与版本,使得在后续对照片的编辑上更灵活

参考资料:

关注下面的标签,发现更多相似文章
评论