iOS 在系統相冊調用本身的應用編輯圖片 - Photo Editing Extension

Created by Ningyuan 2020/05/23html

先扔個官方設計指南 Human Interface Guidelines:Photo Editingios

iOS 8 以後,蘋果提供了幾個應用擴展功能,分別是Today WidgetShareActionPhoto EditingStorage ProviderCustom Keyboard,豐富了iPhone的操做體驗。本次介紹的,是Photo Editing,中文譯名爲照片(圖片)編輯。objective-c

1. 新建

看了下網上的教程,都是比較舊的版本,因而摸索了一下,步驟以下網絡

新建一個Single View App項目session

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

新建完畢,選中項目 - TARGETS - General - 點擊 側欄下方的 "+",添加Extension項目app

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

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

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

在這邊填上ProductName -> Finishide

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啓動
  • 該拓展能夠實現對照片的修改,而且系統可將其復原,不用擔憂原數據被影響
  • 照片修改後可設置標識符與版本,使得在後續對照片的編輯上更靈活

參考資料:

相關文章
相關標籤/搜索