[Xcode/SwiftUI] FirebaseStorageを使って画像をアップロード & 表示してみる

Firebaseのセットアップは完了している前提です 🙏

アーキテクチャはガン無視してます。

Firebaseコンソールで、Storage項目でimages/フォルダを作成しておいてください。

実装

ImagePicker.swift (画像選択用)

import SwiftUI

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var image: UIImage?

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        let parent: ImagePicker

        init(_ parent: ImagePicker) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                parent.image = uiImage
            }
            picker.dismiss(animated: true)
        }
    }
}

HomeView.swift (メインのView)

import SwiftUI
import FirebaseStorage

struct HomeView: View {
    @State private var selectedImage: UIImage?
    @State private var isImagePickerPresented = false
    @State private var imageUrls: [URL] = []
    @State private var showUploadView = false
    private let storage = Storage.storage()

    var body: some View {
        NavigationView {
            VStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack {
                        ForEach(imageUrls, id: \.self) { url in
                            AsyncImage(url: url) { phase in
                                if let image = phase.image {
                                    image
                                        .resizable()
                                        .scaledToFill()
                                        .frame(width: 240, height: 240)
                                } else {
                                    ProgressView("Loading Images...")
                                }
                            }
                        }
                    }
                }
                Button("画像を選択") {
                    isImagePickerPresented = true
                }
                .sheet(isPresented: $isImagePickerPresented) {
                    ImagePicker(image: $selectedImage)
                }
                // 画像が選択されたら、UploadImageViewに遷移する
                NavigationLink(
                    destination: UploadImageView(image: selectedImage ?? UIImage()),
                    isActive: $showUploadView
                ) {
                    EmptyView()
                }
                .onChange(of: selectedImage) { newImage in
                    if newImage != nil {
                        showUploadView = true
                    }
                }
            }
            .onAppear {
                fetchImageUrls()
            }
        }
    }

    // 画像のURLをFirebase Storageから取得
    private func fetchImageUrls() {
        let storageRef = storage.reference().child("images")

        storageRef.listAll { result, error in
            if let error = error {
                print("Error listing images: \(error)")
                return
            }

            guard let items = result?.items else { return }

            for item in items {
                item.downloadURL { url, error in
                    if let error = error {
                        print("Error getting download URL: \(error)")
                        return
                    }

                    // すでに同じURLがリストに存在しないかを確認
                    if let url = url, !imageUrls.contains(url) {
                        imageUrls.append(url)
                    }
                }
            }
        }
    }

}

UploadImageView.swift (画像アップロード用)

import SwiftUI
import FirebaseStorage

struct UploadImageView: View {
    var image: UIImage
    @Environment(\.presentationMode) var presentationMode
    @State private var isUploading = false
    @State private var uploadSuccess = false
    private let storage = Storage.storage()

    var body: some View {
        VStack(spacing: 24) {
            Image(uiImage: image)
                .resizable()
                .scaledToFill()
                .frame(width: 240, height: 240)
                .padding()
            if isUploading {
                ProgressView("Uploading...")
            } else {
                Button("アップロード") {
                    uploadImage()
                }
            }
        }
        .alert(isPresented: $uploadSuccess) {
            Alert(
                title: Text("アップロード成功"),
                message: Text("画像が正常にアップロードされました"),
                dismissButton: .default(Text("OK")) {
                    // アップロード成功後、HomeViewに戻る
                    presentationMode.wrappedValue.dismiss()
                }
            )
        }
    }

    private func uploadImage() {
        guard let imageData = image.jpegData(compressionQuality: 0.8) else { return }
        isUploading = true
        let storageRef = storage.reference().child("images/\(UUID().uuidString).jpg")

        storageRef.putData(imageData, metadata: nil) { metadata, error in
            isUploading = false
            if let error = error {
                print("Error uploading image: \(error)")
                return
            }
            storageRef.downloadURL { url, error in
                if let error = error {
                    print("Error getting download URL: \(error)")
                    return
                }
                if let _ = url {
                    uploadSuccess = true
                }
            }
        }
    }
}