【Swift UI/Firebase】Cloud Storageの実装方法!画像や動画をサーバー管理
この記事からわかること
- Swift/Firebaseで作成したiOSアプリにCloud Storageを導入する方法
- Swift UIでCocoa Podsを使用している場合のインストール方法
- 画像や動画をサーバーへアップロード/ダウンロードするには?
- クラウドバケットの作成方法
- パーミッションルールとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
Firebase自体の概要や登録方法については下記記事を参考にしてください。
Cloud Storageとは?
Cloud Storage for Firebaseとはユーザーがアプリケーションを介してファイルを安全にアップロード/ダウンロードできるクラウドストレージサービスです。サーバーにファイルを保存できることで異なるデバイスやプラットフォーム間でファイルを共有することが可能になります。
アップロードできるファイルの種類は画像、動画、音声、テキストファイルなど、あらゆるファイルの形式をサポートしています。自身のサーバーを使用するより、豊富で簡潔なAPIを利用して強固なセキュリティと高いスケーラビリティが確保されているのが大きなメリットになります。
また時間のかかるアップロードやダウンロードがネットワークの環境などにより、停止してしまっても途中から再開することが可能になっているため、速度と帯域幅を節約することができるようです。
無料の使用枠
「Cloud Storage」も無料プラン(Spark プラン)で利用することが可能です。その場合は「最大容量5GB」に制限されているようです。5GB(=5120MB)あれば圧縮して容量を節約した画像(1枚1MBと仮定)だとすると約4000枚〜5000枚程度は保存できるかと思います。
高品質を保って画像でも5MBくらいだと思うので最低約1000枚程度は保存できると思います。
iOSアプリにCloud Storageを導入する流れ
流れ
- Firebaseプロジェクトを作成
- iOSアプリの登録
- GoogleService-Info.plistの追加
- SDKの導入
- 初期化コードの組み込み
- Cloud Storageバケットを作成
- 完了
ここでは通常の「Firebaseプロジェクトを作成」や「iOSアプリの登録」などの手順は割愛しています。詳細は以下の記事を参考にしてください。
【Swift/Xcode】Firebaseの導入方法!iOSアプリでの使い方
Cloud Storage SDKの導入
「Cocoa Pods」を使用して「Cloud Storage SDK」を導入していきます。「PodFile」に以下の一文を追記してpod install
を実行します。
pod 'FirebaseStorage'
おすすめ記事:【Swift UI】CocoaPodsのインストール方法と使い方!
初期化コードの組み込み
最後にアプリのエントリポイント部分に初期化のためのコードを記述していきます。
import SwiftUI
import FirebaseCore // 追加
// 追加
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
@main
struct TestFirebaseApp: App {
// 追加
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Cloud Storageバケットを作成
続いて実際にアップロード先となる「Cloud Storageバケット」を作成していきます。Firebaseにログインして「Storage」>「始める」をクリックします。
クラウドストレージのセキュリティルールは本番環境であればロックモードで、練習であればテストモードにチェックを入れて「次へ」をクリックします。
テスト段階でロックモードを指定した場合はクライアントがデータの読み取り/書き取りができない状態になっているので「ルール」のfalseをtrueに変更しておきます。(※本番環境実際に使用する場合は明確に制限を設けるようにしてください。)
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
詳細なセキュリティルールは公式サイトを参考にしてください。
公式リファレンス:Firebase Cloud Storage セキュリティ ルールで条件を使用する
続いてロケーションを問われるので「東京(asia-northeast1)」にして進めていきます。(※大阪(asia-northeast2)でも良いかもです。)
以下のような画面にきりかわったらバケットの作成は完了です。ファイルがアップロードされるとこの画面からファイルを確認することができるようになります。またここから手動でアップロードすることも可能になっています。
また上部にあるgs:/
で始まるパスがCloud Storageのルートパスになります。このディレクトリの配下にフォルダなどを作成し、実際に画像などのファイルが格納されていきます。http
とかではないので注意してください。
これで指定のiOSアプリからCloud Storageを使用できるようにすることができました。
Cloud Storageの使い方
ここからはiOSアプリからCloud Storageを操作する方法をまとめていきます。
上部にimport
文が必要になるので忘れずに記述しておいてください。
import FirebaseStorage
クラウドバケットの参照を取得する
Cloud Storageを使用するためには作成したクラウドバケットの参照を取得する必要があります。Realtime Databaseを利用したことがある人はほぼ同じ操作方法なのでわかりやすいと思います。
// ストレージサービスへの参照を取得
let storage: Storage = Storage.storage()
// ストレージ参照を作成
let storageRef: StorageReference = storage.reference()
print(reference.bucket) // [プロジェクト名].appspot.com
storageRef
に格納されたStorageReference
型はfullPath
、name
、bucket
プロパティなどを持っており、その参照の情報を取得することができます。例えばstorage.reference()
のbucket
プロパティを参照することで表示されていたバケットのURLを取得することが可能です。
公式リファレンス:Apple プラットフォームで Cloud Storage 参照を作成する
子供や親の階層を参照する
バケット内が参照できるようになったのでバケット内の階層の参照を取得する方法も見ていきます。まだ画像などが何もアップロードされていないので掴みにくいかもですが、バケット内は通常のPCなどと同じツリー型のフォルダ構造で管理されます。StorageReference
から親や子を参照するためのメソッドが用意されています。
// 1つ上の階層(親)の参照
reference.parent()
// 1つ下のimagesディレクトリの中のsample.jpgの参照
let imagesRef = storageRef.child("images")
let sampleImgRef = imagesRef.child("sample.jpg")
// 1つ下のimagesディレクトリの中のsample.jpgの参照
var sampleImgRef = storageRef.child("images/sample.jpg")
// ルートの参照
let rootRef = sampleImgRef.root()
// フルパスを指定しても参照可能
let storagePath = "\(your_firebase_storage_bucket)/images/sample.jpg"
let imagesRef = storage.reference(forURL: storagePath)
child
メソッドは引数に渡した名前に一致する子供の参照を取得します。名前にはimages/sample.jpg
のようにパスを指定することもできるので使いやすい方を使用すればOKです。
参照名の制約
参照名(ディレクトリやファイル名など)に指定できる文字列には制約が設けられています。
- reference.fullPathの全体の長さを1~1,024バイトの間まで
- 改行またはラインフィード文字は使用NG
- #、[、]、*、? は使用NG
画像ファイルをアップロードする
ファイルをアップロードする方法は以下の手順になります。
- 参照を作成する
- ファイルをアップロード
1.参照を作成する
参照を作成する方法は先ほどのchild
メソッドを使用して希望の階層参照を構築するだけです。
let sampleImgRef = reference.child("sample.jpg")
2.ファイルをアップロード
ファイルをアップロードする方法は以下の2パターン用意されています。
- Data型を使用する
- URL(パス)を使用する
Data型を使用してアップロード
1つ目は画像ファイルをSwift内でData
型に変換してアップロードする方法です。UIImage
型で取得した画像データをjpegData
やpngData
メソッドを使用してData
型に変換しputData
メソッドでアップロードを実行します。
let sampleImgRef = reference.child("sample.jpg")
// 50%に圧縮してData型に変換する
guard let data = image.jpegData(compressionQuality: 0.5) else { return }
let uploadTask = sampleImgRef.putData(data) { metadata, error in
print(error) // パーミッションエラーなどが発生すれば取得できる
guard let metadata = metadata else { return }
// メタデータの取得
let size = metadata.size
// 成功すればサイズを取得できる
print(size)
}
メタ情報の取得が必要なければcompletionHandlerのないputData
メソッドも用意されているのでそちらを使用するとコードがスッキリします。
let uploadTask = sampleImgRef.putData(data)
URL(パス)を使用してアップロード
対象の画像をData
型に変換しなくとも端末内に保存されているパスが取得できればそのパスを使用してアップロードすることも可能です。パスはURL
型でputFile
メソッドの引数に渡します。
let sampleImgRef = reference.child("sample.jpg")
// path;ローカルに保存されている画像のパス
guard let url = URL(string: path) else { return }
let uploadTask = sampleImgRef.putFile(from: url) { metadata, error in
print(error) // パーミッションエラーなどが発生すれば取得できる
guard let metadata = metadata else { return }
// 成功すればサイズを取得できる
let size = metadata.size
print(size)
}
アップロードタスクを管理する
putData
やputFile
メソッドの返り値はStorageUploadTask
型になっており、時間のかかるアップロード処理をタスクとして操作することが可能です。
@objc(putFile:metadata:completion:) @discardableResult
open func putFile(from fileURL: URL,
metadata: StorageMetadata? = nil,
completion: ((_: StorageMetadata?, _: Error?) -> Void)?) -> StorageUploadTask {}
例えば一時停止や再開、キャンセルなどが可能になっています。
// アップロード開始
let uploadTask = sampleImgRef.putFile(from: path)
// 一時停止
uploadTask.pause()
// 再開
uploadTask.resume()
// キャンセル
uploadTask.cancel()
アップロードタスクを観測する
アップロードタスクの状態はobserve
メソッドを使用して観測することができます。例えば進捗状況をプログレス値で取得するには以下のように実装します。
let observer = uploadTask.observe(.progress) { snapshot in
if let progress = snapshot.progress {
let percentComplete = 100.0 * Double(progress.completedUnitCount) / Double(progress.totalUnitCount)
print("Upload progress: \(percentComplete)%")
}
}
observe
メソッドの引数にはStorageTaskStatus
型で観測したい状態を指定します。
@objc(FIRStorageTaskStatus) public enum StorageTaskStatus: Int {
case unknown
case resume
case progress
case pause
case success
case failure
}
成功や失敗を観測したい場合は以下のように実装することができます。
// 成功
let observer = uploadTask.observe(.success) { snapshot in
print("アップロード成功")
}
// 失敗
let observer = uploadTask.observe(.failure) { snapshot in
if let error = snapshot.error as? NSError {
switch (StorageErrorCode(rawValue: error.code)!) {
case .objectNotFound:
break
case .unauthorized:
break
case .cancelled:
break
case .unknown:
break
default:
break
}
}
}
画像ファイルをダウンロードする
ファイルをダウンロードする方法もアップロードと同じく以下の手順になります。
- 参照を作成する
- ファイルをダウンロード
1.参照を作成する
参照を作成する方法は先ほどのchild
メソッドを使用して希望の階層参照を構築するだけです。
let sampleImgRef = reference.child("sample.jpg")
2.ファイルをダウンロード
ファイルをダウンロードする方法は以下の3パターン用意されています。
- UIImage(NSData)としてダウンロードする
- ローカルファイルパスに直接ダウンロードする
- オンラインのファイルを表すダウンロードURLを生成する
メモリ内のNSDataにダウンロードする
ダウンロードした画像を一時的に表示したいだけなどの際はメモリ(変数)内にダウンロードすることが可能です。参照を取得したらgetData
メソッドを使用します。引数には許容する最大サイズを指定します。completionHandler
から画像データを取得できるのでUIImage
型などに変換すれば画面に表示できるようになります。
let sampleImgRef = reference.child("sample.jpg")
// 最大許容サイズ 1MB (1 * 1024 * 1024 バイト) でメモリにダウンロード
sampleImgRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
guard let data = data else { return }
if let error = error {
print(error)
} else {
let image = UIImage(data: data)
}
}
デバイス上のファイルを表すNSURLにダウンロードする
サーバーにある画像ファイルを直接ローカルフォルダ(端末内)にダウンロードしたい場合はwrite(toFile:)
メソッドを使用します。引数には保存したいローカルパスを指定します。
let sampleImgRef = reference.child("sample.jpg")
let localURL = URL(string: path)
let downloadTask = sampleImgRef.write(toFile: localURL) { url, error in
if let error = error {
print(error)
} else {
// 保存したローカルファイルURL
print(url)
}
}
オンラインのファイルを表すダウンロードURLを生成する
サーバーに保存してある画像ファイルを指すURLを取得したい場合はdownloadURL
メソッドを使用します。completionHandler
から以下のような形式のURLが取得できます。
let sampleImgRef = reference.child("sample.jpg")
sampleImgRef.downloadURL { url, error in
if let error = error {
print(error)
} else {
print(url) // https://firebasestorage.googleapis.com:443/v0/b/[プロジェクト名].appspot.com/o/sample.jpg?alt=media&token=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data)!
} catch let error {
print("Error : \(error.localizedDescription)")
}
}
}
ファイルを削除する
ファイルを削除したい場合は参照を取得してdelete
メソッドを実行するだけです。
let sampleImgRef = reference.child("sample.jpg")
do {
try await sampleImgRef.delete()
} catch let error {
print("Error : \(error.localizedDescription)")
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。