Document Provider Extensions

躍然發表於2015-01-17

通過iOS 8 app extensions,我們可以選擇多種方式去分享我們app的功能。Document Provider extension是這些擴充套件之一,它允許我們的app和其它app進行檔案傳送、接收互動。

如果你曾無休止地為你的apps尋找雲端儲存無縫銜接方式,以此實現文件共享,那麼Document Provider簡直就是為你設計的。( 好吧,這簡直就是為我設計的。^-^ )

為了實現應用間共享資料,iOS 使用了兩種單獨的擴充套件。

•Document Picker:當另一個app啟動iCloud file picker時呈現出的view controller。

•File Provider: 一個由NSFileCoordinator協調在host app與extension apps之間移動資料的非UI元件。NSFileCoordinator,正如其名,協調讀取檔案。它也允許多執行緒操作,例如host app及containing app,可以同時獲取檔案而不用彼此叨擾。

你還需要一個containing app 為這些擴充套件做一個大本營,但實際上,啟動另外一個app和host app就好了。另外,你必須為host app及extensions app建立一個嚴格的定義介面,以此分享檔案資料。


diagram

Document Providers 使用四中方式在應用間共享資料:Import,Open, Export, and Move。


share operations

接下來讓我們一一認識它們:

1.Import:Import從提供者那裡獲得檔案並拷貝到我們的host app。最經典的應用場景是在內容建立類應用中的使用。例如,像keynote、PowerPoint這樣的演示製作應用,希望匯入圖片,視訊,以及音訊。它們希望拷貝一份這些資料以此保證它們隨時可用。

2.Open:和import一樣,open同樣從檔案提供者那裡獲得資料並匯入我們的host app,只是不同的是,這些資料沒有被拷貝一份至我們的host app,資料還在原處。例如,你或許在音樂播放器、視訊播放器,再或者影像編輯器中使用該方式。

3.Export: Export使我們的host app可以儲存檔案至其它提供者。例如,這些提供者可能是常用的像Dropbox、iCloud Drive這樣的雲端儲存系統。host app可以通過export儲存檔案到提供者的儲存空間。在接下來的編輯器例子中,當使用者完成編輯,他們可以匯出檔案,然後稍後可以在其它app中開啟這些檔案。

4.Move: 除了host app不會持有一份兒檔案的拷貝,其它Moving和export差不多。這或許是最不常用的操作,因為大多數iOS apps不是為了拋棄它們的資料才建立的。

這一章,你將建立一個相簿應用,應用中會用到檔案獲取器(document picker)和檔案供應擴充套件(file provider extension)。該應用使用Amazon S3 (Simple Storage Service) 作為後臺服務端。Amazon S3可以為你提供的任何儲存檔案作為一個模型,無論這些檔案在雲端、公司資料中心,或者在一臺連線到Internet的電腦上。

該教程需要在Xcode 6和iOS 8環境下實踐,同時蘋果開發者賬號也是必須的,因為你需要有效的iCloud授權,以此獲得Document Picker。

注意:該章節學習需要有一定基礎,僅僅適合由中級向高階進階的開發者。文中會有一些操作我們不進行一步步詳細操作講解,例如在故事版中自動佈局view controllers。所以在進行下文之前你最好確定自己對這些技能比較熟悉。

廢話少說,開始吧

在這一節的資原始檔中,找到我們的起始工程(戳這裡)。在Xcode裡開啟該工程,但先不要著急執行它。在你執行之前,你必須做些修改才好。接下來我們將詳細介紹怎樣修改。

起始工程(Imgvue)是一個基於Amazon S3 (Simple Storage Service)為後臺的影像管理應用。當然,當你針對document provider extension開發你自己的應用時,你可以使用其它雲端儲存服務,像Dropbox(吐槽一下,以前我還真打算用這個來作為我電子書app的資源提供者,直到有天我突然發現在我們天朝如果不翻牆就再也訪問不到它主頁),Box,Google,Azure,etc。

Amazon很流行,並且提供它的SDK處理服務互動,除此還可以在非登入條件下在全球內獲得資料。因此,對本章而言,Amazon是個理想的選擇。

Imgvue提供巨大的,公開的空間儲存影像。但也很明顯,它不適合提交至App Store,因為它沒有加強限制,並且對影像共享與上傳沒進行監管。但話又說回來,如果單單作為一個學習例子,它已經非常不錯了。

設定S3

這個版本的Imgvue基於Amazon S3。Amazon提供了一個可以處理超複雜的請求籤名過程的Objective-C框架。

使用該應用,你需要一個Amazon Web Services(AWS)賬號,以及一個S3空間。S3根據你儲存空間使用大小進行收費,所以注意了,在S3儲存本文需要的圖片資源可能會產生一些費用(大概每月幾美分,直到你刪除你的S3空間)。好訊息是,如果你是第一次註冊AWS,你將有資格在第一年裡使用它們的免費套餐

注意:在真實的應用中一定當心S3的收費,如果你在S3儲存的影像有數以萬計的人在使用,那麼費用會有所增加。所以,當心了!

首先,進入http://aws.amazon.com/ ,登入或者建立一個新帳號。然後耐心操作幾步驟,並且輸入信用卡資訊。但不要怕,本教程中接下來你將要上傳的幾個圖片會收費很便宜,甚至完全免費(如果你使用免費套餐的話)。


Sign in 

然後在控制檯(console),開啟S3儀表板 (dashboard)


console

S3組織資訊到buckets。點選Create Bucket按鈕建立一個新的bucket。你的應用將放置圖片至該bucket.

注意:完成以上操作非常重要,否則,你的應用將會崩潰,而且你為完成該章所做的所有努力都會付諸東流。


Simple Storage Service

接下來,配置你的空間。鍵入空間名字,然後選擇你所在地區名。這裡注意你起的名字必須是全球唯一的。可以試著使用包含你的名字或者你所在領域的字串。


Bucket Name and Region

拷貝你的區間名並儲存到安全的地方,很快你會再使用到它。完成這些後,點選建立按鈕。

下一步你要做的是建立一個可以繫結你應用到AWS servers的安全證書。點選左上角Services按鈕,再點選IAM開啟個人中心,進入管理控制檯。


IAM

點選Users標籤,再點選Create New Users按鈕。


Create New Users

鍵入你喜歡的username,比如ImgVue,然後點選Create按鈕。


Enter Users Names

下一頁,點選Show User Security Credentials來顯示你的Access Key ID以及Secret Access Key。拷貝它們,並儲存至安全位置,不久之後你會用到它們。點選Close按鈕,再次點選Close按鈕。

接下來你要做的是,允許你新建的使用者可以訪問你的新空間(bucket)。在使用者列表中選擇你新建的使用者,滾動檢視到User Policies部分,點選Attach User Policy。


Attach User Policy

在下一頁面,選中Custom Policy單選按鈕,然後點選Select。


Custom Policy

在Policy Name鍵入ImgVue_Bucket_Access,在Policy Document鍵入以下字串(使用之前你鍵入的空間(bucket)名取代[BUCKET NAME]):


Policy Document

這允許你的使用者可以訪問你建立的空間(bucket),但沒有給你的使用者其它過多的許可權。這樣給使用者有限的訪問許可權在Amazon Web Services是最好的選擇。

完成以上操作,點選Apply Policy。好了,這裡完成了S3設定,接下來回去編程式碼吧。

App frameworks

正如本章之前所提到的那樣,app extensions在Container app和extensions app之間需要一些共享程式碼。Imgvue已經為此分解成三部分。

1.Imgvue:這是你的Container App.它一則用來列表縮圖顯示你S3 bucket中的圖片,二則允許使用者從相簿選取圖片上傳。

2.S3Kit framework:該框架打包Amazon提供的AWSS3.framework。AWSS3.framework是前iOS8時代的非模組靜態框架,它不能直接使用Swift程式碼。因此,S3Kit framework在它們之間架起了一座友好大橋。S3Kit就像一個協調者,它提供了在Imgvue app ,extensions app,S3之間轉移圖片的邏輯。

3.UIWidgets:該框架包含一些Document Picker extension和host app要共享使用的縮略集合檢視(collection views)以及其它一些螢幕檢視。

注:Amazon提供的框架還不能讓我們太興奮,特別是,它的網路請求是同步的,並且它們以丟擲異常來處理errors。因為這些原因,這些框架不適合用在商業app之中,不過,對於演示Document Providers是怎樣工作的,它卻足夠了。

S3請求籤名相當複雜,講解它將偏離這章的目的,而且需要平添許多步驟。記得在building和running程式的時候,模擬器或者真機裝置需要連線網路,否則程式會崩潰。

選擇你的工程,選中S3Kit 目標,在General 標籤的Deployment Info部分,確保Allow app extension API only是選中的。


Deployment Info

這確保該框架只用在iOS SDK中那些支援擴充套件的呼叫,例如,擴充套件並不需要獲取UIApplication例項。在build extension將要使用的框架時,要時刻謹記這一點。

Setting up the app

在S3Kit framework中開啟BucketInfo.swift,替換ACCESS_KEY_ID 和 SECRET_KEY為你從Amazon獲取的值,設定BUCKET_NAME為你選擇的bucket id。

因為該應用要用的iCloud,你需要在Xcode和Apple Provisioning Portal中心配置App ID和Bundle Identifier。在Imgvue應用的target的General標籤欄,設定Bundle Identifier為一個像com.mydomain.Imgvue這樣的唯一的Bundle ID。在下面的team一欄選擇你所在已付費賬號下的team。


identity

Build and run,你將看到一個清新而乾淨的螢幕頁面;現在還沒有圖片。使用app中的Upload tab上傳一些圖片。

當你再次返回圖片collection view,你的圖片將會被從S3上面載入下來。

注:如果你得到缺少entitlement的錯誤資訊,手動連線iTunes,建立一個provisioning profile,然後在 Build Settings\Code Signing\Provisioning Profile 配置你的工程使用它。


images

注意:你也可以在S3控制檯驗證圖片是否上傳成功,以及大量上傳其它圖片。

Adding the extension

目前為止效果已經很好了,但是現在只有Imgvue可以獲取那些美麗的圖片,而你希望其它應用同樣可以得到這些圖片。所以,Document Provider extensions來了。

在Xcode的工程導航欄選擇Imgvue Xcode Project file,從editor選單選擇Add Target.繼續選擇iOS\Application Extension\Document Provider,然後點選Next。


Document Provider

你需要為你的工程起一個簡短好記的名字,然後使用者會在他們正在使用的應用中一眼看出你的應用支援擴充套件。

在我們這篇教程,為工程起名為S3Images。選中選擇框Include a File Provider extension。


product name

當彈出啟用新的schemes,單擊Activate。


Activate scheme

現在為止,我們已經建立了兩個folders,即targets和schemes。S3Images,文件選擇器擴充套件,S3ImagesFileProvider,使用NSFileCoordinator連線你的檔案系統。如果你僅僅計劃實現匯入匯出功能,那麼你不需要Provider extension。

Document Picker extension

Document Picker是一個允許你的應用選擇檔案以及與檔案互動的檢視控制器。預設情況下,應用啟動Document Picker可以連線iCloud Drive。Document Picker允許使用者從一列表檔案位置選擇你的擴充套件,觸發你的Document Picker的介面進行載入。

Document Picker extension模板由一個故事版檔案及DocumentPickerViewController管理類組成。若想從S3 bucket獲得圖片,你還需要一個collection view。

1.在S3Images 資料夾,開啟MainInterface.storyboard。你將看到下圖所示介面。


Document Picker View Controller

2.刪除已經存在的 untitled.txt按鈕。

3.拖拽一個Container View 到Document Picker View Controller的場景檢視。

4.設定Container View的Auto Layout約束,使其寬高等同其父檢視寬高,填充場景螢幕。居中Container View。

5.拖拽一個Navigation Controller到場景畫布。

6.按住Control鍵,拖拽Container View到Navigation controller,然後選中Embed。現在Document Picker的子檢視將邊為navigation view。(這也是將一個navigation stack設定在Document Picker之內的方法。)

7.刪除原檢視容器中的無關檢視控制器。

8.刪除navigation controller的主table view controller,替代為Collection View Controller。

9.按住Control鍵,拖拽navigation controller到collection view controller,然後選中Root View Controller,設定Collection View Controller 為navigation view controller的主檢視控制器。

10.在collection view controller 的Identity Inspector一欄,設定其類為ThumbnailCollectionViewController,設定其module為UIWidgets。這告訴storyboard,設定控制器的class為類似container app的隔離模型的類。

好了,暫停!現在最好呼吸點新鮮空氣放鬆下。你的故事版現在看起來是這樣子的:


storyboard

OK,小憩後,我們繼續開始吧。

1.在collection view 中點選Collection View Cell原型。

2.在Identity Inspector一欄,設定custom class 為ThumnailCell,設定其module為UIWidgets。

3.在Attribute Inspector導航欄,設定Reuser Identifier為ThumbCell.

4.拖拽一個Image View到cell上面,使用約束確保其填充整個cell空間。

5.開啟cell的Connection Inspector,連線imageView outlet到剛才那個新的image view。

6.在Collection View的Size Inspector,設定其寬高為80。

7.同樣在Size Inspector,改變cells 和lines的Min.Spacing為0。


Size Inspector

現在ThumbnailViewController已經和S3的圖片建立了連線,所以接下來你應該迫不及待要測試一下效果了吧。

確保extension可以連線這些模型,在S3Images工程的target's Build Phase中新增S3Kit.framework和UIWidgets.framework。


target's Build Phase

然後,在Imgvue和另外兩個Document Provider的targets,在Capabilities標籤欄,設定App Groups 的capability狀態為enable。確保三個targets同樣設定。

這需要你對共享容器進行授權設定,以此在你的container app與extension app間進行共享檔案以及傳送URLs。


Capabilities

現在,雖然extension app還不能做很多,但它已經可以在document picker中顯示從s3獲得圖片縮圖了。為了顯示這個document picker,你需要另外一個獨立的host app。

Build and run,看看是不是一切OK。接下來,我們來看看那個host app。

DocTester

這章節中用到的另一個應用是DocTester,它是個準系統應用,其主要目的是做為你的host app。開啟和匯入功能,可以載入圖片並在主檢視顯示。可以匯出和移動已下載的圖片並上傳至伺服器。

開啟 DocTester.xcodeproj 設定這個app。

因為檔案擴充套件意味著擴充套件iCloud 檔案,一個host app 在啟動之前需要進行iCloud enable設定。

首先,在target的General一欄,更新Bundle Identifier為你賬號下面可以使用的app id,修改Team一欄為你的team id。


Bundle Identifier

然後在Capabilities一欄,設定iCloud 和iCloud documents為enable狀態。


iCloud

現在在執行app之前還有最後一步,是最後一步哦,我保證!;-)

在Xcode中的menu選單,選擇Open Development Tool\iOS Simulator.在模擬器的設定中,登入iCloud。寫的這裡,你需要有一個有效的iCloud Drive 賬號,並且設定Document Provider extensions為enable。


Sign in iCloud

現在開始執行DocTester。這樣模擬器中將安裝該應用,然後Imgvue可以使用它了。在Xcode,選擇Product\Stop(CMD-Period.) 很好!

DocTester安裝好,你可以測試Document Picker了。

Using the Document Picker

接下來,切換到Imgvue工程中,選擇S3Images scheme並執行,這時模擬器將啟動並顯示一個apps列表。選擇DocTester而不是Imgvue。


scheme

程式將啟動,非常基本的應用!


DocTester

點選匯入按鈕。如果你已經設定了iCloud Drive,iCloud file chooser將出現。如果你沒有設定,iOS將一步一步引導你進行設定。


iCloud Drive

切換到Imgvue,點選左上方的Locations按鈕,使出現Document Provider extension 選單。點選More...使extension為enable並新增它到選單。


More

這取消了匯入按鈕,再次在螢幕的上方點選Import按鈕。現在S3Images extension將出現在本地列表中,點選它,新增Document Picker extension。如下圖所示,在選擇檢視中,視窗的上方有兩個toolbar。上面一個,有Locations和Done按鈕,只是iOS系統檔案選擇器的一部分,是不可設定的。第二個是container view的contained nav controller的navigation bar。稍後,你需要新增程式碼隱藏這個看起來怪異的傢伙。


extension

現在點選圖片還不會有任何反應。雖然你已經新增了Document Picker extension UI,你還沒有建立file providing。接下來你需要新增它。

Troubleshooting

儘管一切應該正常了,可是仍有可能選擇器不會出現。導致這種現象可能源於幾種原因。特別的,如果extension app 崩潰,那麼在Console app檢視crash logs並debug。你也可以通過使用Command-/快捷鍵檢視模擬器的logs。

下面幾點需要檢查:

• 確保你的網路連線是正常的。如果有網路相關錯誤,重啟裝置或者模擬器。

• 確保你有個iCloud account並且iCloud Drive設定為enable。

• App Id和Team設定在所有的targets上應該保持一致。同時,targets 下面的Team也需要關聯App ID。

• DocTester 和  Imgvue targets需要設定iCloud Documents capabilities為enable。如果它可用,使用fix issues button。

• 檢查你的target settings,看看在General tab是否存在問題列表。如果有,嘗試下Fix Issue,或者嘗試手動生成provisioning profile。

• Imgvue, S3Images, S3ImagesFileProvider targets都需要匹配的app group 授權。有時,在Capabilities pan的設定的extension沒有更新到.entitlements或者.plist檔案,所以一一檢查它們。

• 有時,building和running沒有全部更新host app。如果有些想要的改變缺失了,終止並重啟host和container apps,然後re-run它們。

Import

document provider的四個操作中最簡單的一個是Import,或者是"download and copy to my app"功能。

實現匯入,extension app需要返回共享檔案容器中檔案的下載url,然後host app接受這個url。隨後,當document picker消失的時候,host app儲存檔案到沙盒目錄。

為了生成url,當使用者選擇一張圖片,extension controller需要獲得通知。ThumbnailCollectionViewController有一個代理方法可以實現這個功能。

在DocumentPickerViewController.swift檔案的最上邊,匯入如下modules:


import modules

然後,通過新增ThumbnailDelegate和NSFileManagerDelegate宣告DocumentPickerViewController實現這些代理方法。

classDocumentPickerViewController:UIDocumentPickerExtensionViewController,ThumbnailDelegate,NSFileManagerDelegate{

新增ThumbnailDelegate協議中需要實現的didSelectImage方法。

// MARK: ThumbnailDelegate

func  didSelectImage(image:S3Image) {let  imageUrl = image.url!

//1

let   filename = imageUrl.pathComponents.lastasString

//2

let   outUrl =documentStorageURL.URLByAppendingPathComponent(filename)

// 3

let   coordinator =NSFileCoordinator()

coordinator.coordinateWritingItemAtURL(outUrl,

options: .ForReplacing,

error:nil,

byAccessor: { newURLin

//4

let  fm =NSFileManager()

fm.delegate=self

fm.copyItemAtURL(imageUrl, toURL: newURL, error:nil)

})

// 5

dismissGrantingAccessToURL(outUrl)

}

該方法使用你從S3下載的圖片的url並建立一個新的url傳遞給host app。讓我們一步一步看來。

1.filename是path字串的最後組成部分,path字串的前部分是host app快取圖片的沙盒路徑。

2.documentStorageURL是由UIDocumentPickerExtensionViewController類定義的特定路徑。File Provider extension和host app均可訪問。為image新增唯一的檔名字尾,可作為image的標識,這樣之後provider extension就可以獲得該檔案了。

3.建立檔案coordinator,以此確保可以訪問到檔案儲存位置。coordinator可以寫檔案到檔案儲存路徑,也可以覆蓋掉檔案儲存路徑下已經存在的任何檔案。

4.accessor block使用新的URL而不是outUrl,是因為coordinator想要寫檔案到臨時位置並在之後刪除它。你建立了一個檔案管理器,它從download路徑拷貝檔案到儲存路徑。

5.呼叫dismissGrantingAccessToURL關閉document picker,回到host app。

接下來,實現DocumentPickerViewController中的必要方法NSFileManagerDelegate:

// MARK: NSFileManagerDelegate

func  fileManager(fileManager:NSFileManager,

shouldProceedAfterError error:NSError,

copyingItemAtURL srcURL:NSURL,

toURL dstURL:NSURL) ->Bool{

return true

}

在檔案路徑下已經有老版本檔案的時候,該方法允許檔案替換。

最後,使用下面程式碼替換prepareForPresentationInMode()模板方法:

override func   prepareForPresentationInMode(mode:UIDocumentPickerMode) {

super.prepareForPresentationInMode(mode)

let  vc =self.childViewControllers;letnav = vc.first

as  UINavigationController

let  thumbController = nav.topViewController as  ThumbnailCollectionViewControllerthumbController.navigationController?.setNavigationBarHidden(

true, animated:false)

thumbController.delegate=self

}

你可能發現了,該方法有點類似viewDidLoad()。你設定了對ThumbnailCollectionViewController的引用,因此你可以設定picker的delegate。

使用DocTester再次build and run the extension。

嗯...選中一個圖片沒有任何反應?對啊,那是因為File Provider extension模板提供了空資料。接下來你要解決這個問題。

The File Provider extension

File Provider extension有幾個目的:

• 提供和保護檔案的URLs:即使檔案不存在,file provider仍可以按照期望執行下載。

• 提供檔案後設資料(metadata):Document-based apps主要通過後設資料查詢進行檔案系統互動(Mac比iOS更甚)。file provider可以在沒有獲得檔案資料的前提下提供後設資料。當host app 提供一個檔案列表,而使用者只想開啟其中一個,這時該功能會很有用。

• 管理bookmark URLs:Bookmaks建立一個使用者之後可以使用的文件URL,通過該URL實現狀態恢復。如果extension/container app修改了檔案或者清空了本地檔案,該功能確保使用者重啟host app時檔案可以到特定的狀態。

嚴格說,最後兩個要點以及超出了本章所講範圍,但如果你真感興趣,那麼再去看看WWDC 2014 session 234, “Building a Document-Based App”,你將可以得到更多細枝末節。

注:如果extension provides僅僅提供匯入匯出功能,那麼你將不需要File Provider extension。Document Picker extension可以直接從儲存器拷入拷出檔案資料。

開啟S3ImagesFileProvider中的FileProvider.swift檔案,在檔案頭部匯入S3Kit。

importS3Kit

現在,用以下程式碼替換掉startProvidingItemAtURL方法:

override func  startProvidingItemAtURL(url:NSURL,

completionHandler: ((error:NSError?) ->Void)?) {

let   fileManager =NSFileManager()

let   path = url.path!

if    fileManager.fileExistsAtPath(path) {//1

//if the file is already, just return

completionHandler?(error:nil)

return

}

//load the file data from Amazon S3letkey = url.lastPathComponent//2

let   bucket =BucketInfo()

let   fileData = bucket.load(key)!//3

var  error:NSError? =nil

var  fileError:NSError? =nilfileCoordinator.coordinateWritingItemAtURL(url,

options: .ForReplacing,

error: &error,

byAccessor: { newURLin//4

_= fileData.writeToURL(newURL, options: .AtomicWrite,

error: &fileError)

})

if   error !=nil{//5completionHandler?(error: error);

}else{

completionHandler?(error: fileError);

}}

該方法確保當host app 請求檔案URL的時候image data是可以獲得的。開始的時候,documentStorageURL駐留在container app的沙盒目錄之外的共享資料夾下,因此,在方法結束的時候,應該確保檔案已經存在。

1.如果檔案已經存在於路徑,方法執行callback並返回,結束執行方法。

2.如果期望路徑下不存在檔案,那麼必須進行下載。S3 keys和這款app的檔名一樣。因為Document Picker extension構建的URL含有檔名(字串尾部組成),所以我們可以從其中提取出來。

3. synchronous load方法從S3下載檔案。

4.file coordinator提供一個臨時URL位置,因此檔案資料可以被寫入URL。如果URLs不同,file coordinator負責移除那些資料。通過指定更換,file coordinator知道其內容將被完全替換。

5.寫入檔案之後,completion handler被執行。如果寫入過程中出現錯誤,返回error物件。

Reading the image in the host app

file provider通過NSFileCoordinator被訪問。注意被DocTester的ViewController.swift回撥呼叫的importImage函式。它看起來是這樣子的(你不需要新增這些,它已經存在於DocTester了):

func  importImage(url :NSURL) {

let  fileCoordinator =NSFileCoordinator(filePresenter:nil)

// 1

fileCoordinator.coordinateReadingItemAtURL(url,options: .WithoutChanges,

error:nil) { newURLin

// 2

if let  data =NSData(contentsOfURL: newURL) {let   image =UIImage(data: data)self.imageView.image= image

}}}

這讀取Document Picker extension-supplied URL並載入圖片到app的imageView。下面我們詳細講解:

1、coordinateReadingItemAtURL獲得沙盒URL讀檔案,當檔案已經準備好可以被讀了,執行accessor block。因為它必須拷貝檔案到不同的位置(為了安全或多點接入之目的),它提供一個新的URL代表檔案。

2.被提供的URL在accessor block內部可像一般URL那樣使用。

現在選擇S3Images,build and run。選擇DocTester執行,選擇Import然後選中一張圖片。

瞧!picker將消失,app內顯示一張圖片。


select image

注:如果你遇到問題,從S3ImagesProvider target執行DocTester。

Export

這一節,Document Provider extensions傳輸圖片檔案到host app,但沒有上傳新圖片。為了可以實現上傳,Document Picker需要提供一個允許使用者鍵入檔名的檢視,而不是僅僅提供thumbnail chooser。

在S3Images target下開啟MainInterface.storyboard檔案。

1.新增一個新的View Controller到storyboard。

2.從 thumbnail controller向右拖拽一個segue到新建的controller。選中Push segue並設定它的identifier為upload。


upload

3.拖拽一個Text Field到view controller的view。像在the top和the top layout guide 之間設定vertical space那樣設定它的horizontal space左右邊緣各10pt。設定placeholder為Type new filename。


Type new filename

4.在檢視上面新增新的按鈕,設定它居中對齊,並設定它的上邊緣到其上面的text field下邊緣8pt。設定按鈕的title為Upload。


Upload button

5.在S3Images工程中使用Cocoa Touch Class template新建檔案,命其名為UploadViewController,設定它的superclass為UIViewController。

6.回到storyboard,在Identity Inspector設定新建的那個view controller的類為UploadViewController。

7.開啟Assistant Editor來顯示UploadViewController.swift。按住CTRL鍵,分別拖拽text field和button到UploadViewController.swift,在彈出的提示中分別輸入textField和button,為它們命名。在從button CTRL拖拽一次,新建一個名為upload的IBAction。


Assistant Edit

上面就是你如何設定UI以允許使用者設定檔名並上傳圖片的步驟。

接下來你需要編碼來處理UI事件。完成以上步驟,再次擊打鍵盤開始編碼吧。Wow,曙光將近,你幾乎就要成功了。


haha

你需要定義一個新的protocol代理,以此實現當使用者設定name時通知Document Picker子類。在UploadViewController.swift上面新增如下程式碼:

protocolUploadControllerDelegate {

      func  nameChosen(name:String)

}

當傳遞了一個輸入文字,nameChosen方法被執行。然後該方法需要在delegate物件中被執行。

在UploadViewController新增新的例項變數:

var delegate: UploadControllerDelegate?

現在重寫viewWillAppear:方法設定delegate:

override func  viewWillAppear(animated:Bool) {

      super.viewWillAppear(animated)

     let  navController =parentViewController

     let  docPicker = navController?.parentViewControllerdelegate =           

      docPickerasDocumentPickerViewController

}

以上方法通過hierarchy找到Document Picker view controller,然後設定它為delegate。

新增以下程式碼,實現點選button時呼叫該delegate。

@IBAction func  upload(sender:AnyObject) {

        let name :NSString=textField.text

         if name.length>0{

              delegate?.nameChosen(name)

       }

}

以上對filename進行了非空校驗。

回到DocumentPickerViewController.swift檔案,為DocumentPickerViewController類新增UploadControllerDelegate協議。

classDocumentPickerViewController:UIDocumentPickerExtensionViewController,ThumbnailDelegate,NSFileManagerDelegate,UploadControllerDelegate{

新增如下程式碼實現delegate:

// MARK: UploadControllerDelegate

func nameChosen(name:String) {

let  justFilename = name.stringByDeletingPathExtensionletexportURL   documentStorageURL.URLByAppendingPathComponent(name).URLByAppendingPathExtension("jpg")

upload(name, outURL: exportURL)

}

上面程式碼獲得檔名,格式化它,然後藉助輔助方法進行上傳。第一句,防止使用者新增自己的檔名字尾。然後建立了一個JPG格式的URL。最後,呼叫之後你要實現的輔助方法:

func upload(name:String, outURL:NSURL) {

let access =

originalURL!.startAccessingSecurityScopedResource()//1ifaccess {

let fc =NSFileCoordinator()

varerror:NSError?

fc.coordinateReadingItemAtURL(originalURL!,//2

options: .ForUploading,

error: &error) { url in

let   bucket =BucketInfo()//3

let   data =NSData(contentsOfURL: url)!

bucket.uploadData(data, name: name) { errorin//4

if  error !=nil{

       println("error uploading:\(error)")  }  }

data.writeToURL(outURL, atomically:true)//5

self.dismissGrantingAccessToURL(outURL)//6}

originalURL!.stopAccessingSecurityScopedResource()

}}

下面一步步講解:

1.首先,確保host app擁有合適的資格,因為它在它沙盒提供一個URL。

2.file coordinator確保檔案可以讀取,並在需要的時候提供新的檔案URL。

3.BucketInfo是S3Kit下的輔助類,通過它可以獲得雲端儲存。你使用它進行上傳。

4.如果檔案上傳失敗,沒有復原方法,你只能再次上傳。

5.因為Move和Export僅僅是拷貝檔案到container app,這裡同樣拷貝檔案到extension sandbox。只有host app可以通過移除它的備份檔案進行Move操作。extension需要檔案在路徑處存在,否則會導致crash。

6.這裡傳遞新的備份本地路徑到host app。通過Document Picker,host app可以在這個位置更新檔案。

Nice work!感覺如何?想知道更多嗎?很好,因為你需要呼叫upload segue來展示upload chooser。


almost there

修改prepareForPresentationInMode,在Export或Move mode下檢查其mode並顯示upload介面。

document picker extension擁有相同的切入點。prepareForPresentationInMode用來檢測mode,並分別為它們定製views。因為Export 和 Move mode上傳檔案到S3,upload segue被用來顯示“enter file name”檢視。如果是Import 或者Open mode,根據delegate的設定,thumbnail chooser檢視將被顯示。

在prepareForPresentationInMode(mode:)找到如下程式碼:

thumbController.delegate=self

使用以下程式碼替換它:

if   mode==.ExportToService|| mode==.MoveToService{

thumbController.performSegueWithIdentifier("upload",sender:nil)

}else{

thumbController.delegate=self }


export

Build and run S3Images target,選中DocTester app進行run。

選擇Export,它將隨機下載一張cat圖片。在upload controller選中S3Images,鍵入名字,並按上傳。當controller消失的時候,圖片已經被上傳到了S3 bucket。執行Imgvue app驗證一下。太棒了!


upload

Opening and moving files

雖然有四種Document Provider執行方式:Import, Open, Export和Move。你僅僅實現了兩個。因為app的實現僅僅提供上傳、下載到S3兩種選擇,Move和Export被處理是一樣的,Import和Open被處理是一樣的。

Open、Move這些常用存取方法與Export、Import方法的不同之處是policy-based以及沒有被storage provider的semantics強制實施。

例如,當開啟或者匯入一個檔案,檔案從S3被下載,移動到共享路徑,並返回給host app檔案的URL。

如果host app是要匯入檔案,它儲存檔案到沙盒,或者,像DocTester這樣,僅僅載入資料到記憶體。

如果意圖是開啟URL並在host app的生命週期內使用它,那麼你可以使用startAccessingSecurityScopedResource來持有該URL以此獲得書籤並通過app啟動重新獲取檔案。

例如,在DocTester的ViewController.swift中被Document Picker回撥的openImage函式就是這樣做的。至少對於載入圖片已經足夠了。

func  openImage(url :NSURL) {

let   accessing = url.startAccessingSecurityScopedResource()//1

if  accessing {

    let   fileCoordinator =NSFileCoordinator(filePresenter:nil)

    fileCoordinator.coordinateReadingItemAtURL(url,options: .WithoutChanges,

error:nil) { newURL  in

     if   let  data =NSData(contentsOfURL: newURL) {

     let  image =UIImage(data: data)

     self.imageView.image= image

}}

url.stopAccessingSecurityScopedResource()//2}

}

如果host app在shared container中修改了URL,itemChangedAtURL將被呼叫。File Provider extension template配有存根此方法。

在S3ImagesFileProvide中開啟FileProvider.swift檔案,像如下那樣替換template方法:

override func  itemChangedAtURL(url:NSURL) {

// 1

let   key = url.lastPathComponent

let   bucket =BucketInfo()

// 2

fileCoordinator.coordinateReadingItemAtURL(url,options: .ForUploading,

error:nil) { newURL  in

   var   error:NSError

   let    data =NSData(contentsOfURL: newURL)!

   bucket.uploadData(data,name: key,completion: { errorin

   // 3

   if(error !=nil) {

   println("error uploading updated file:\(error)")

}})} }

1.extensions始終使用filename作為S3 key。

2.NSFileCoordinator在讀取器有可選的輕便的ForUploading屬性。

3.上傳更新檔案到S3。

當儲存檔案在host app和providing app之間進行共享時,該方法被執行。也就是說,在Import或者Export mode,host app 擁有檔案的一份兒拷貝,並且當它完成轉移會停止與共享URL的互動。但是,在Open和Move  mode,檔案依然在provider中,host app 只要需要會一直使用它。

你可以測試一下,使用DocTester build and run S3Images。開啟一張圖片到DocTester,然後點選改變。

這將繪製一個綠色正方形到圖片之上並在shared container更新它。更新觸發File Provider extension更新S3中備份兒。

現在執行Imgvue檢視S3中修改的檔案。再次匯入一個不同的圖片。Modify方法將繼續更新在應用內的圖片,但它將導致呼叫itemChangedAtURL,因此不更新S3的檔案。


Modify

Summary

Document Provider extensions允許使用者在自己的裝置上面不同應用間共享自定義的雲端儲存檔案。一個特殊的共享路徑在extensions 和 host apps之間被使用。存取到container被NSFileCoordinator控制。File Provider extension允許監聽shared container。

這一章僅僅觸及了File Provider extensions功能的冰山一角--它還可以提供placeholder資訊,檔案大小,以及在不下載整個檔案的情況下展示圖片縮圖。

想了解更多,看看2014 WWDC video,“Building a Document-based App”(https://developer.apple.com/videos/wwdc/2014/#234).

你還可以自定義Document Picker extension為只提供特定的檔案型別,或者在四個模型的組合集合下操作。這被info.plist下NSExtension>NSExtensionAttributes控制。

UIDocumentPickerModes是一個含有所有支援模式的陣列,UIDocumentPickerSupportedFileTypes是含有所有支援的UTType字串的陣列。預設為public.content。

另一個有趣的地方需要注意的是UIDocumentMenuViewController,它是一個允許使用者從activated Document Picker extensions中選擇的選單。

關於該類比較好的是,你可以新增額外的non-document-pickers。例如,在UIWidgets module(used by the upload tab in the container app)下的UploadChooserViewController有一個選單,用來從相簿載入圖片。

當然,extensions有一些限制,也並不是對每個app都適用。首先,extra Document Pickers必須被enabled,這需要使用者在iCloud drive有登入到裝置的iCloud account賬號。

然後,host app需要document-based和擁有iCloud 檔案權利。最後,使用者必須導航到非直觀的介面分別enable每個extension。

這需要大量工作,或許正因如此,Apple只建議像Dropbox, Box, Google Drive等這些為其它應用提供檔案儲存的app使用該extension。

對於你只是想操作自己文件的app,你可以直接使用iCloud和iTunes。此外,應用程式可以使用URL方案和支援UTTypes開啟其它檔案。在這種情況下,Action, Share,或者Photo Editing extension可能更合適。

否則,盡情享用Document Providers吧!

注:原文來自iOS 8 by Tutorials --Chapter 13:Document Provider Extensions2.

附Amazon雲相關知識連結:

1、http://blog.csdn.net/awschina/article/details/17149515

2.http://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/ec2-connect-to-instance-linux.html

3.http://aws.amazon.com/cn/getting-started/?sc_ichannel=ha&sc_icountry=cn&sc_icampaign=ha_cn_GettingStarted&sc_icontent=ha_213&sc_idetail=ha_cn_213_1&sc_iplace=ha_cn_ed&sc_isegment=d&

相關文章