Firebase教學:如何實作上傳圖片功能與遠端存取


看過了許多 Firebase 的開發應用( Email / Facebook 登入、即時資料庫存取),你是否疑惑如何用 Firebase 上傳圖片或影片等檔案呢? 今天來跟大家分享在 Firebase 中比較少被我們用到的 Storage 功能。

我們先來看看官方的影片吧:

這次我們就以上傳照片為主,開發一款當使用者從手機選擇照片上傳後,會在我們 CollectionView 上顯示出來的 App。

其實再加上以往所學的註冊登入功能,基本上就是一款簡易版的 Instagram 囉!(有這麼簡單?(誤))

你即將開發的範例App

未開始寫程式碼之前,先來看看最終成果:

firebase-upload-demo

不錯吧?

範例App背後的運作方式

為了讓大家更了解背後 Firebase 與 App 的運作方式,我畫了一張示意圖:

簡單來說,就是利用Firebase的Storage存放照片,而照片連結則存放在Firebase的Database。首先,我們要把欲上傳的照片傳到 Storage 存放,成功後會收到 Storage 回傳的照片連結,再將連結寫入資料庫中方便日後存取。這樣你就可以利用Firebase存取照片。

我想你現在應該明白運作原理,那就讓我們一起來實作!

實作範例App

初始設置

在這部分我們會建立範例App的UI,但如果這部分對你來說太簡單的話可以直接下載起始專案並跳過初始設置的介紹,從 Firebase 設定開始。

先在 StoryBoard 建立兩個畫面,第一頁的 ViewController 放上兩顆按鈕(標題分別是「上傳照片」與「圖片庫」)並嵌入 Navigation Controller,第二頁我們直接拉出 CollectionViewController 並將它與第一頁「圖片庫」按鈕用 Show (e.g. Push) 的 segue 連上,在 CollectionViewCell 上拉入一個 UIImageView。我這邊也附上 [Starter 初始專案] 給你們。

starterSB

再來新增客製 CollectionViewControllerCollectionViewCell 類別的兩個檔案,指定於 StoryBoard 的 CollectionViewControllerCollectionViewCell 上,也記得要給 CollectionViewCell 一個專屬的 identifier(在這邊我用 「Cell」)。

customSBClass

拉一張圖片進來專案,測試 FireCollectionViewController 可不可以 Work,在這之前記得先把 CollectionViewCell 上的 UIImageView 建立起 Outlet 連結:

由於我們已經在 StoryBoard 上建立 CollectionViewControllerCollectionViewCell 的連結,所以可以直接刪除系統在 FireCollectionViewController 類別檔案 viewDidLoad 裡預設的 RegisterClass 那行程式。

直接在 FireCollectionViewController 的 DataSource 裡設定測試的圖片與數量:

完成後執行模擬器,點擊「圖片庫」按鈕應該會出現以下畫面,表示所有初始設定都已經完成囉!可以開始接 Firebase 了!

firebase-image-demo

Firebase Storage 設定

要使用Firebase Storage存放圖片,我們首先要在Firebase進行一些簡單的設定。

  1. 進入 Firebase 首頁 並點選 Get Started For Free(或者你已經在 Firebase 的 Console ),點選 Create New Project。
  2. firebase-home

  3. 將專案命名、選擇地區並按下 Create Project後,會進入專案的管理頁面。
  4. firebase-createnewproj

  5. 在左手邊的選單,點選 Storage 區塊,進入的畫面會直接是一個像下方這樣的圖。
  6. firebase-storage

  7. 之後,點選上方的 Rules 權限區塊,你會見到以下設定:
  8. 直接用滑鼠將 Read, Write 後方的 if 判斷刪除,並按下上方的 Publish。Firebase 會警告你任何有你此網址的人都可以讀寫你的 Firebase。 由於是範例關係,我們是為了方便,把所有權限都打開。這樣比較方便開發,記得別把 Data 區塊的網址連結公布出去即可。
  9. firebase-storage-rule

安裝Firebase SDK

好了!完成Firebase設定之後,是時候回到專案管理頁面 。

  1. 選擇 iOS 並跟著指示一步步完成設定 。先輸入 Xcode 專案的 Bundle ID:
  2. firebase-add-bundleid

  3. 將下載下來的 GoogleService-Info.plist 拉進 Xcode 專案。
  4. firebase-plist

  5. 我習慣用 CocoaPods 安裝Firebase套件。如你不知道如何使用CocoaPods,可以參考 CocoaPods 官方教學,或者先前關於 Firebase 的教學文章。在這邊,先建立一個Podfile,另外加上兩行,分別是 Firebase/Storage 與 Firebase/Database,以安裝所需的SDK,讓我們後續開發可以直接使用。
  6. 最後執行pod install讓CocoaPods下載並安裝相關SDK。
  7. pod-install-firebase

  8. 安裝完畢後在 Xcode 專案的 AppDelegate 中加入 Firebase 的啟動設定。

完成以上步驟後,回到 Xcode 執行程式應該會在 Console 看到一些關於連上 Firebase 的輸出,就表示成功連上囉!

實作照片上傳

終完成準備工作,現在可以實作照片上傳的部分,我們會利用Firebase SDK將照片上傳至Storage。

  1. 假如你不是用我提供的初始案,你需要將 StoryBoard ViewController 上的 「上傳照片」按鈕與 ViewController 類別檔案建立起 IBAction 連結,並在裡面加入 UIImagePickerController 讓我們可以選擇要從「照片圖庫」或「相機」上傳照片:
  2. 新增 UIImagePickerControllerDelegateUINavigationControllerDelegate,如此一來才能取得在照片圖庫或相機的圖片。在這邊我是用 ViewController extension 的方式將 UIImagePickerController 需要的協定放在 class 外方便整理。
  3. 完成後執行程式會像這樣:

    firebase-upload-photo

但當你選擇其中一項時程式可能會 Crash 並顯示如圖的原因,是要你在 Info.plist 中加入存取使用者相機或圖庫的 Key,如此一來才會告知使用者 App 要存取資料並符合蘋果的隱私權規定。我的起始專案已經幫各位完成這個處理了,理論上是不會有隱私權與 Crash 的問題。

image-upload-crash

讓我們進到左側專案目錄的 Info.plist 裡,並加入兩個 Information Property List,分別是:”Privacy – Camera Usage Description” 與 “Privacy – Photo Library Usage Description”。

info-plist-image-privacy

再執行一次應該就可以順利存取相機或者圖片庫囉!注意,相機的部分只有實機可以測試。

好了,開始寫有關照片上傳的程式碼。首先在ViewController.swift 引入 FirebaseStorage。

回到 UIImagePickerControllerDelegatedidFinishPickingMediaWithInfo 裡,我們要在最後取得 selectedImageif 判斷式裡上傳該照片。先修改程式,我之後再慢慢解釋運作:

一開始我們先建立存取 FirebaseStorage 的實體:FIRStorage.storage().reference()。再從該實體指定讀寫的位置:.child(“欲寫入的位置”),此處新增一個名叫 AppCodaFireUpload 的位置。由於 FirebaseStorage 要求上傳的檔案都需要檔名,所以我們直接使用上方已經產生的獨立 UUID 字串當作檔名,並一併寫上副檔名 (.png),如此以來才不會造成覆蓋檔案的錯誤與問題。

再來將 selectedImageUIImagePNGRepresentation 轉換成 Data 形式,如此才能上傳。由於轉換後的 Data 是 optional 的,所以在用 if let 的方法解包取得可以轉換的 Data: uploadData。 而 storageRef.put(uploadData, metadata: nil, completion {()} 這行就是 FirebaseStorage 關鍵的存取方法。

之後,使用剛剛建立的 FirebaseStorage 實體,將轉換成 png 的圖片 Data 放入。該方法會回傳一個 completion,裡面包含回傳成果的相關資料,與錯誤時回傳的錯誤資訊。

如果回傳成功,FirebaseStorage 會將已上傳照片的連結回傳出來。並使用回傳 data 的 downloadURL() 取得該連結。

你可以試試在瀏覽器貼上回傳的照片連結,如顯示的圖跟你一開始選擇上傳的照片是一樣的,那就恭喜你完成上傳照片到 FirebaseStorage 的步驟了!

另外,你可以回到 Firebase console 的 Storage 裡面確認檔案是否已經成功上傳。你可以在 Storage 裡看到多了一個 AppCodaFireUpload 的資料夾,點進去就可以看到成功上傳的圖片囉!

firebase-folder

將已上傳的圖片連結寫進 Firebase Database

還記得我們最後要在 CollectionView 中顯示那些我們上傳到 Firebase Storage 的照片嗎?其實要下載照片或顯示照片可以直接使用 Firebase Storage 的 SDK,但我這邊傾向將檔案與 API 分開,將上傳到 Storage 的照片網址存入 Firebase Database 方便日後管理與存取。

如果你已經懂的如何將資料 讀取 / 寫入 Firebase Database 你可以直接跳過以下部分。

實作程式前,你需要進去 Firebase Console 點進左側的 Database 區塊,為了開發方便同樣將上方 Rules 的 .read & .write 都改成 true,才可以直接讀寫資料庫:

再來讓我們在同樣的 ViewController 檔案裡 import FirebaseDatabase 模組:

修改imagePickerController(_:didFinishPickingMediaWithInfo:)內的程式碼如下(加入的部分以黃色作標示):

我們在剛剛取得 Storage 回傳的網址 if let 裡面加上 FirebaseDatabase 的實體,目的就是要將照片連結寫進 Firebase 資料庫中 。建立 Database 實體的方式跟建立 Storage 一樣,我們給他一個指定寫入資料的位置:AppCodaFireUpload,再將下一層位置指定為先前建立的獨立 ID,如此一來我們每一張上傳的圖片連結就會被放在這個獨立 ID 的位置下。

之後,將回傳的照片網址寫入 FirebaseDatabase 裡:

  • 寫入的方法就用 setValue,將 uploadImageUrl 傳入。
  • Database 方法同樣會回傳一個 completion,裡面一樣放著可能會有的錯誤資訊跟上傳資訊。但在這邊我們就單純判斷如果沒有錯誤就直接 print 「圖片已儲存」即可。

執行程式看看,有成功了嗎? 如果有成功你可以直接到 Firebase console 左側的 Database 看看是不是已經把圖片成功寫入資料庫了:

firebase-upload-file

在 CollectionView 上顯示 Firebase 上的照片:

最後的一個步驟,就是在Collection View顯示我們之前上傳至Firebase Storage的照片。先回到我們的 FireCollectionViewController 裡,引入FirebaseDatabase並在 viewDidLoad 上方新增一個全域變數 fireUploadDic,形別為 [String:Any] 的 Dictionary:

目的是要暫存從 Firebase Database 抓下來的資料

之後,在 viewDidLoad 裡新增 Firebase Database 的實體並指定位置,再從 observe 的方法裡取得我們放在資料庫裡的檔案:

讓我們看看以上程式的運作:

  • observe 方法裡指定要監聽的資料種類:.value(它也可以指定當資料庫的值被改變、刪除…等狀態)
  • 之後會回傳一個 Block 給我們,讓我們可以取得資料庫回傳的資料:snapshot。
  • 在 Block 裡我們轉換回傳資料值 (.value) 的形別為 [String:Any](因為我們存在 Firebase 上的資料就是這樣的形別)。
  • 如果成功轉換,我們就把從 Firebase 回傳的資料放進全域變數 fireUploadDic 裡,並在完成時重新整理 CollectionView 讓他可以讀取並顯示圖片。

在 Block 裡面你會發現我用 [weak self] 而非原先可以直接呼叫的 self,原因在於如果不用 weak self,在 Firebase 的 Block 裡會無法釋放 self 而產生 retain cycle,造成大量的記憶體問題。(可以在網上查詢更多關於 weak self 的資訊)

再來改變一下 CollectionView DataSource 的 numberOfItemsInSection 內的資料。用一個 if let 判斷是否可以解包 optional 的全域變數 fireUploadDic,如果可以就回傳內部圖片資料的數量給 CollectionView ; 不行的話就回傳 0。

最後在 CollectionView cellForItemAt 內設定 cell 上面 UIImageView 要顯示的照片:

在函式裡我們同樣用一個 if let 確認是否可以解包 fireUploadDic。如果成功解包,我們建立一個 Array 並放入所有透過 Dictionary.keys 方法所取得 fireUploadDic (dataDic) 裡面的 Key (也就是每一個我們從 Firebase 資料庫抓下來的 UUID)。之後,再透過 indexPath.row 抓出 keyArray 中每一個 key,並用該 key 取得位於 dataDic 裡面的 value(也就是我們的圖片連結),並轉型為 String。

成功取得 imageUrlString 以後,再嘗試將它轉為 URL。之後透過 URLSession dataTask 去解析圖片的 URL 網址,這裡也會傳三個參數:

  • 解析完的資料(data)
  • 網路回應(response)
  • 錯誤(error)

當遇到錯誤,一如往常我們簡單的把它 print 出來就好。倘若成功取得解析的 data,我們將 optional 的 data 解包成 imageData,並將它轉為 UIImage 後設定在 cell 上方 fireImageView

由於 UI 處理不應該被網路解析的處理給凍結,所以我們將設定 UIImage 的地方搬回 mainQueue 主執行緒裡。如此一來當網路處理完某一筆網路圖片的解析後就會直接先放上 cell 的 fireImageView 上,而不會等所有圖片網址都解析完以後再一次放上去而造成畫面凍結。

執行看看吧!點進圖片庫裡有沒有看到剛剛上傳到 Storage 的圖片呀?成功的話會有像這樣的結果:

firebase-upload-demo

呼~ 恭喜完成小小的圖片上傳程式啦!有任何問題的話歡迎一起討論哦~

也歡迎你分享實作的結果或類似的應用!

最後再附上完整專案,再麻煩讀者從 Firebase 的專案下載 GoogleService-Info 拉進專案即可使用。


<p>Kuan – 任職於 25sprout 新芽網路擔任 iOS 開發者,喜歡閱讀關於科技、設計、商業相關書籍文章,熱愛嘗試與實作不同想法,歡迎一起討論研究。可以透過email與我聯絡([email protected])。</p>

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This