你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> objc.io#21#照片擴展

objc.io#21#照片擴展

編輯:IOS開發基礎

在 iOS 8 發布時,蘋果把六種全新擴展功能介紹給全世界,它們史無前例的提供了訪問操作系統的可行性。現在,開發者可以利用照片擴展來為系統相機應用增加功能。

用戶使用照片編輯擴展的流程並不簡單。從選擇編輯的照片開始,需要點擊三次才能啟動,其中一步驟是非常小一個按鈕:

1.png

然而,這類擴展給開發者提供了為用戶創造無縫體驗,創建一致的方法來管理照片的絕佳的機會。

本文在了解更詳細的編輯工作流程之前,將先簡單討論如何創建擴展以擴展的生命周期,我們會通過常見的相關問題和場景來創建照片編輯擴展從而得出結論。

本文的示例項目 Filtster 演示了如何創建自己的圖片編輯擴展。它诠釋使用了數個 Core Image 濾鏡完成簡單的圖像過濾效果。完整 Filtster 項目代碼 可以在 GitHub 上找到。

創建擴展

所有擴展必須包含在一個功能齊全的 iOS 應用程序之內,照片編輯擴展也不例外。這可能意味著你必須做很多令人吃驚的自定義 Core Image 濾鏡的才能讓它到達用戶手中。蘋果如何嚴格審查還有待觀察,因為蘋果商店內的大多數有圖片編輯功能的應用都是在 iOS 8 引入之前就已經存在了的。

為了創建新的圖片編輯擴展,需要為已有的 iOS 項目添加新的 target,擴展 target 模板如下:

2.png

模板由三部分組成:

1.Storyboard 圖片編輯擴展除了系統在頂部提供的 toolbar 包含取消以及完成按鈕之外,界面幾乎完全可以自定義。

3.png

雖然 storyboard 默認不包含 size classes,系統將允許你選擇並激活該功能,雖然沒有明顯的原因來阻止你使用手動布局,但蘋果還是強烈建議使用 Auto Layout 來創建照片編輯擴展。如果你忽略蘋果的建議你將不得不面對很多潛在風險。

2.Info.plist 設定擴展的類型和可被接受媒體的類型,以及擴展的通用配置。NSExtension 鍵值是一個字典,它包含擴展所需要的配置:

4.png

NSExtensionPointIdentifier 實體告訴系統這是一個使用 com.apple.photo-editing 作為值的照片編輯擴展。唯一特殊的 key 是 PHSupportedMediaTypes,它指明可以被操作的媒體類型。在默認情況下,這是一個包含 Image 實體的數組,當然你也可添加 Video 選項。

3.View Controller 遵守 PHContentEditingController 協議,其中包含了圖片編輯擴展需要的生命周期方法。更多詳情見本文下個部分。

值得注意的是不要忘記提供菜單內的擴展的圖標:

5.png

圖標通過宿主 app 的資源目錄內的 App Icon 提供。這裡文檔有些讓人迷惑,它暗示你必須在擴展本身裡來提供圖標。然而,盡管我們可以提供一個這樣的圖標,但擴展將不會使用選擇它。這一點有些爭議,因為蘋果指定與擴展相關的圖標必須與容器應用程序的相同。

擴展的生命周期

照片編輯擴展建立於 Photos 框架之上的,這意味著編輯不是破壞性的。當一個照片資源被編輯的時候,原始文件始終沒有被修改,編輯的結果將作為副本被保存下來。另外,語義細節包含了如何重新編輯並保存調整後的數據。這個數據的意思是編輯可以基於原始文件重新來過。當你實現圖片編輯擴展的時候,你只負責構建你自己的數據對象。

PHAdjustmentData 類含有編輯所需參數,以及兩個格式化的屬性 (formatIdentifier 和 formatVersion) 用來確定當前編輯擴展針對於之前的編輯過的照片的兼容性。它們兩個都是字符串類型,另外 formatIdentifier 規定為反向域名解析格式。這兩個屬性讓你靈活的創建一套圖像編輯的應用程序以及擴展,每一種都可以用另一種表示。另外 data 屬性是 NSData 類型。可以被用來按你的需要存儲擴展操作的細節,以便讓你的擴展能繼續編輯。

開始編輯

當用戶使用你的擴展來編輯照片的時候,系統會實例化你的視圖控制器並且初始化照片編輯的生命周期。如果照片之前曾經被編輯,它首先會調用 canHandleAdjustmentData(_:) 方法,同時為你提供一個 PHAdjustmentData 對象。因此,你的擴展是否可以處理之前編輯過的數據就很重要,這將決定框架發送的下一個生命周期的方法是什麼。

一旦系統決定提供原始圖片還是之前就被渲染編輯過的圖片,接下來將會調用 startContentEditingWithInput(_:, placeholderImage:)。輸入是一個類型為 PHContentEditingInput 的對象,其中包含了地理位置,創建時間以及媒體類型等來自於原始資源的元數據,以及你需要編輯的資源細節。除了原始尺寸的輸入圖片的路徑以外,輸入對象還包含一個 displaySizedImage 表示相同的圖片數據,但是根據屏幕尺寸進行了適當縮放。這意味著交互編輯可以在較低分辨率下進行,以此來確保擴展可以保持迅速響應操作並節省能量。

下面是實現方法

func startContentEditingWithInput(contentEditingInput: PHContentEditingInput?,  
                                  placeholderImage: UIImage) {
  input = contentEditingInput
  filter.inputImage = CIImage(image: input?.displaySizeImage)
  if let adjustmentData = contentEditingInput?.adjustmentData {
    filter.importFilterParameters(adjustmentData.data)
  }
  vignetteIntensitySlider.value = Float(filter.vignetteIntensity)
  ...
}

上面的實現中存儲了 contentEditingInput,因為要完成編輯並從調整後的數據導入濾鏡參數的時候我們會需要它。

如果你的 canHandleAdjustmentData(_:) 返回 true,startContentEditingWithInput(_:, placeholderImage:) 將會提供原始圖片,然後你的擴展需要根據調整後的數據來重新創建編輯過的圖片。如果這是一個耗時操作,那麼 placeholderImage 將提供一個上次編輯渲染後的臨時圖片來讓你暫時使用。

在這個階段,用戶將通過擴展界面的交互來控制編輯的進程。因為擴展包含一個視圖控制器,你可以使用任何 UIKit 來實現它。示例項目使用了 Core Image 的濾鏡鏈來完成編輯,所以界面使用了一個自定義的 GLKView 子類來減少 CPU 的負載。

取消編輯

在完成編輯時,用戶可以選擇照片界面提供的取消或者完成按鈕。如果想讓用戶確定是否取消尚未保存的編輯內容,shouldShowCancelConfirmation 屬性需要重寫並返回 true:

6.png

確認取消

如果需要取消操作,cancelContentEditing 方法將被調用來允許你清空所有臨時數據。

提交修改

一旦用戶決定保存編輯操作,並且點擊了完成按鈕,finishContentEditingWithCompletionHandler(_:) 將會被調用。在這個時候,原始尺寸圖像需要用與當前顯示的圖片相同設置來編輯,並保存調整後的數據。

在這時,你可以通過在編輯過程開始時提供的 PHContentEditingInput 對象內的 fullSizeImageURL 來獲取原始尺寸的圖片。

要完成編輯,我們需要調用提供的回調函數,並提供一個從輸入創建的 PHContentEditingOutput 對象。這個輸出對象還包含了一個 renderedContentURL 屬性,用來指定你應該把輸出的 JPEG 數據存放在哪裡:

func finishContentEditingWithCompletionHandler(completionHandler: ((PHContentEditingOutput!) -> Void)!) {  
  // 在後台隊列渲染並提供輸出。
  dispatch_async(dispatch_get_global_queue(CLong(DISPATCH_QUEUE_PRIORITY_DEFAULT), 0)) {
    // 從編輯輸入創建編輯輸出。
    let output = PHContentEditingOutput(contentEditingInput: self.input)
    // 提供調整後的數據並且渲染輸出到指定位置。   
    let adjustmentData = PHAdjustmentData(formatIdentifier: self.filter.filterIdentifier,
      formatVersion: self.filter.filterVersion, data: self.filter.encodeFilterParameters())
    output.adjustmentData = adjustmentData
    // 寫入 JPEG 圖片
    let fullSizeImage = CIImage(contentsOfURL: self.input?.fullSizeImageURL)
    UIGraphicsBeginImageContext(fullSizeImage.extent().size);
    self.filter.inputImage = fullSizeImage
    UIImage(CIImage: self.filter.outputImage)?.drawInRect(fullSizeImage.extent())
    let outputImage = UIGraphicsGetImageFromCurrentImageContext()
    let jpegData = UIImageJPEGRepresentation(outputImage, 1.0)
    UIGraphicsEndImageContext()
    jpegData.writeToURL(output.renderedContentURL, atomically: true)
    // 調用完成回調提交編輯後的圖片。
    completionHandler?(output)
  }
}

一旦對 completionHandler 返回,你就可以清空臨時數據,並且修改後的文件已經准備好從擴展返回。

常見問題

與創建圖片編輯擴展相關的內容其中一些可能有些復雜,本節內容將介紹最重要的幾個。

調整數據 (Adjustment Data)

PHAdjustmentData 是一個只包含三個屬性的簡單類,但是想要用好的話,依然需要遵循一些規則。蘋果建議使用反向域名解析格式來指定 formatIdentifier,但是 formatVersion 和 data 如何使用將由你自己決定。

重要的是要確保你不同版本圖片編輯擴展的兼容性,所以我們需要類似語義化版本這樣能提供靈活的管理產品的生命周期的方式。你可以以自己的方式進行解析,也可以依賴於像 SemverKit 之類的第三方框架提供的功能。

最後對於調整數據要說的是 data 本身,它是一個 NSData 數據對象。蘋果提供的唯一建議是它應該用來存放重建編輯時所需要的的設定,而不是編輯本身,這是因為 PHAdjustmentData 對象的尺寸是受 Photo 框架限制的。

對於不是很復雜的擴展 (比如 Filtster),這個數據可以是簡單地對一個字典歸檔,代碼如下:

public func encodeFilterParameters() -> NSData {  
  var dataDict = [String : AnyObject]()
  dataDict["vignetteIntensity"] = vignetteIntensity
  ...
  return NSKeyedArchiver.archivedDataWithRootObject(dataDict)
}

接著提供解析方式:

public func importFilterParameters(data: NSData?) {  
  if let data = data {
    if let dataDict = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [String : AnyObject] {
      vignetteIntensity = dataDict["vignetteIntensity"] as? Double ?? vignetteIntensity
      ...
    }
  }
}

這裡,這兩個方法存在於共享的 FiltsterFilter 類中,同時這個類也負責確定調整數據的兼容性:

public func supportsFilterIdentifier(identifier: String, version: String) -> Bool  
  return identifier == filterIdentifier && version == filterVersion
}

如果你有更復雜的需求,你可以創建一個自定義的設置類,讓它支持 NSCoding 協議並用類似的方式進行歸檔。

用戶需要可以將互不兼容的照片編輯串聯起來 -- 如果當前擴展無法理解調整數據的話,一張預先渲染好的圖像將被作為輸入。比如你可以使用系統的裁剪工具先對圖片進行裁剪,然後再在你的自定義照片編輯擴展中使用。在你存儲編輯後的圖像時,與之綁定的編輯數據只會包含最近的編輯的細節。你可以將之前的不兼容的編輯的調整數據保存到你的輸出調整數據中,這樣你就可以為濾鏡鏈中你的階段實現還原功能。Photo 框架提供的還原功能將移除所有編輯,並把照片恢復到原始狀態:

7.png

代碼/數據共享

照片編輯擴展作為一個嵌入式二進制文件包含在容器應用中。因為蘋果要求這個容器應用必須有完整功能,因此你創建的照片編輯擴展,很可能與容器應用有相同的功能。你可能會希望在應用擴展和容器之間共享代碼和數據。

共享代碼通過 iOS 8 新功能 -- 創建 Cocoa Touch 框架 target 來實現。你可以向其中添加共用的功能,例如濾鏡鏈和自定義視圖類,並在應用和擴展中同時使用。

值得注意的是因為用於創建擴展,你必須在 Target 設置界面將 API 兼容性限制為僅擴展可用:

8.png

共享數據的需求明顯要少很多,在許多情況下並不存在。然而如果需要,你可以通過把應用和擴展都添加到一個關聯到你的開發者賬號的 app group 中的方式,來創建一個共享容器 (shared container)。共享容器代表的是磁盤上的一塊共享的空間,你可以使用任何你喜歡的方式使用它,比如 NSUserDefaults,SQLite 或者寫文件。

調試與分析

Xcode 調試雖然有一些潛在症結,但已經相當友好了。選擇擴展的 scheme 並編譯運行,接著會詢問你希望啟動哪一個應用,因為圖片編輯擴展只能在系統照片應用中實現,所以你應該選擇照片應用:

9.png

如果這麼做啟動的是你的容器應用的話,你可以編輯擴展 scheme 設置 executable 為 Ask on Launch 來解決。

Xcode 然後會等待你打開你的照片編輯擴展,然後將調試器掛載上去。從這時開始,你就可以用調試標准 iOS 應用的方式來調試擴展了。將調試器附加到擴展可能需要一些時間,所以當你激活擴展時,擴展可能會失去響應一段時間。如果你想評估啟動時間的話,可以在 release 模式下運行它。

性能分析和調試類似,分析器在擴展開始執行後附加上去。你可以更新擴展相關 scheme 指定 Xcode 詢問應該啟動哪一個應用來執行分析。

內存限制

擴展不是一個全功能 iOS 應用,因此訪問系統資源時要受到限制。更特別的是,如果用戶使用太多內存,系統將優先關閉擴展進程。我們無法確定具體的內存限制,因為內存管理是由 iOS 內部處理的,但有這肯定是基於像是設備,宿主應用,以及其他應用程序的內存壓力這些因素的。所以其實並沒有硬性的限制,但我們還是應該盡量減少內存占用。

圖片處理是一個高內存操作,特別是處理的對象是來自 iPhone 相機的高清晰度圖片。你需要做幾件事情來確保照片編輯擴展的內存使用量降到最低。

  • 使用顯示尺寸的圖片 當你開始編輯進程時,系統提供了一張適合屏幕尺寸的圖片。用它來替代原始圖片,將在交互編輯階段將顯著減少內存使用。

  • 限制 Core Graphics 上下文數量 雖然使用 Core Graphics 來處理圖片是正確的方式,但不要忘記 Core Graphics 上下文其實是一大塊內存。如果你需要上下文,那麼需要保持數量到最低。盡可能的重用,並想想你是否以最佳的方式在使用它。

  • 使用 GPU 無論通過 Core Image 還是類似 GPUImage 的第三方框架來用 GPU 進行處理,你可以通過鏈式調用濾鏡來降低內存並且消除中間緩存區需求。

因為圖像編輯本身肯定就需要高內存,所以與其他擴展相比,照片編輯擴展的可用內存要多那麼一些。在 ad hoc 測試中,圖片編輯擴展可以使用高於 100 MB 內存。鑒於來自 800 萬像素相機的照片大約 22MB,所以這個內存量對於大多數圖片編輯擴展來說是夠用的。

結論

iOS 8 之前,第三方開發者無法在他自己應用程序之外向用戶提供功能。擴展的出現徹底改變這一狀況,特別是照片編輯擴展允許你把代碼運行於照片應用核心中。盡管多次點擊的流程略顯復雜,但照片編輯擴展使用 Photo 框架的功能提供了連貫和集成的用戶體驗。

可恢復的編輯一直是像 Aperture 或 Lightroom 這樣的桌面應用的殺手級功能。而現在在 iOS 中使用 Photo 框架,也可以為這個功能創建一個通用架構。這具有巨大的潛力,而允許第三方開發者創建照片編輯擴展則使這一步走得更遠。

制作照片編輯擴展方面有不少復雜的課題,但是它們都不是獨一無二的。創建一個直觀的用戶界面,以及設計圖像處理算法都和圖片編輯擴展一樣充滿了挑戰性,而它們都是一個完整的圖片編輯應用的組成部分。

目前為止有多少用戶留意到這些第三方編輯擴展還有待觀察,但總的來說這有助於提高你應用曝光率。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved