1. 程式人生 > IOS開發 >iOS 在系統相簿呼叫自己的應用編輯圖片 - Photo Editing Extension

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
[[email protected]

新建完畢,選中專案 - TARGETS - General - 點選 側欄下方的 "+",新增Extension專案

2@2x.png
[[email protected]

向下翻,找到 Photo Editing Extension 並選中,Next

3@2x.png
[[email protected]

在這邊填上ProductName -> Finish

4@2x.png
[[email protected]

這時Xcode彈窗,是否啟用新解決方案,選擇 Activate

5@2x.png
[[email protected]

這樣就完成了Photo Editing Extension 的新建,如圖,Xcode會幫我們新建預設檔案

6@2x.png
[[email protected]

先看一下info.plist檔案,可以看到新增了Key-Value:NSExtension,展開其所有子項

PHSupportedMediaTypes:支援編輯的型別,預設為Image型別,還可以新增

NSExtensionMainStroyboard:stroyboard名稱

NSExtensionPointIdentifier:照片編輯擴充套件,標識,不需更改

7@2x.png
[[email protected]

MainInterface.storyboard,這個是照片擴充套件的主介面,已經自動生成了"Hello World",Run Target中應該會自動選中我們當前的擴充套件應用,執行時選中要調起擴充套件的App,這裡選擇Photos。

8@2x.png
[[email protected]

啟動相簿後,隨便點開一張照片,然後點選右上角"編輯"。

後續在安裝本體應用後,也可以直接在相簿中選中編輯,進入我們的擴充套件應用。

10@2x.png
[[email protected]

接下來會進入系統照片編輯介面,在右上角找到更多按鈕"···"(系統不同,所在位置不同),點選

11@2x.png
[[email protected]

在展開的介面中,可以看到當前可用的擴充套件應用,PhotoExtensionTest、extension我是之前新建的照片編輯擴充套件應用,Ex則是剛剛新建的。

如果在這裡沒看到,可點選"更多"檢視。

12@2x.png
[[email protected]

隨後在彈出的介面中的建議這裡選擇即可。

13@2x.png
[[email protected]

然後我們選中剛剛的Ex,就開啟了擴充套件應用。

14@2x.png
[[email protected]

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,新增至修改圖片。

15@2x.png
[[email protected]

點選完成後,會將資料傳到系統圖片編輯介面,再次點選完成,則會自動返回相簿,可以看到修改成功。

16@2x.png
[[email protected]

此時再對已修改的圖片點選編輯,可以看到系統編輯介面右下角顯示"復原",點選則會恢復成原本的圖片。

我們在這裡再次右上角的更多按鈕,開啟我們的擴充套件應用。

17@2x.png
[[email protected]

此時可以看到,啟動擴充套件應用後,點選底部的icon就又添加了一個icon在圖片上,這是因為程式碼中設定的canHandleAdjustmentData: 生效的結果。

18@2x.png
[[email protected]

5. 小結

  • Photo Editing Extension 只能由系統App,Photos啟動
  • 該拓展可以實現對照片的修改,並且系統可將其復原,不用擔心原資料被影響
  • 照片修改後可設定識別符號與版本,使得在後續對照片的編輯上更靈活

參考資料: