iOS 在系統相簿呼叫自己的應用編輯圖片 - Photo Editing Extension
Created by Ningyuan 2020/05/23
先扔個官方設計指南 Human Interface Guidelines:Photo Editing
iOS 8 之後,蘋果提供了幾個應用擴充套件功能,分別是Today Widget
、Share
、Action
、Photo Editing
、Storage Provider
、Custom Keyboard
,豐富了iPhone的操作體驗。本次介紹的,是Photo Editing,中文譯名為照片(圖片)編輯。
1. 新建
看了下網上的教程,都是比較舊的版本,於是摸索了一下,步驟如下
新建一個
Single View App
專案
新建完畢,選中專案 - TARGETS - General - 點選 側欄下方的 "+",新增Extension專案
向下翻,找到 Photo Editing Extension 並選中,Next
在這邊填上ProductName -> Finish
這時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,就開啟了擴充套件應用。
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,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,^{
// 根據輸入元資料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,^{
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,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,新增至修改圖片。
點選完成後,會將資料傳到系統圖片編輯介面,再次點選完成,則會自動返回相簿,可以看到修改成功。
此時再對已修改的圖片點選編輯,可以看到系統編輯介面右下角顯示"復原",點選則會恢復成原本的圖片。
我們在這裡再次右上角的更多按鈕,開啟我們的擴充套件應用。
此時可以看到,啟動擴充套件應用後,點選底部的icon就又添加了一個icon在圖片上,這是因為程式碼中設定的canHandleAdjustmentData: 生效的結果。
5. 小結
- Photo Editing Extension 只能由系統App,Photos啟動
- 該拓展可以實現對照片的修改,並且系統可將其復原,不用擔心原資料被影響
- 照片修改後可設定識別符號與版本,使得在後續對照片的編輯上更靈活