Search code examples
swiftuiuiimagefirebase-storageimagepicker

Upload SwiftUI Image to Firebase with Data


I'm creating simple CRUD operations in an app and I came across a hairy quirk, given SwiftUI's Image design.

I'm trying to upload an image to Firebase, except I need to convert an Image to a UIImage, and further into Data.

Here's my code:

Image Uploader

import UIKit
import Firebase
import FirebaseStorage
import SwiftUI

struct ImageUploader {
    static func uploadImage(with image: Data, completion: @escaping(String) -> Void) {
        let imageData = image
        let fileName = UUID().uuidString
        let storageRef = Storage.storage().reference(withPath: "/profile_images/\(fileName)")
        storageRef.putData(imageData, metadata: nil) { (metadata, error) in
            if let error = error {
                print("Error uploading image to Firebase: \(error.localizedDescription)")
                return
            }
            print("Image successfully uploaded to Firebase!")
        }
        
        storageRef.downloadURL { url, error in
            guard let imageUrl = url?.absoluteString else { return }
            completion(imageUrl)
        }
    }
}


View Model

import SwiftUI
import Firebase

 func uploadProfileImage(with image: Data) {
        guard let uid = tempCurrentUser?.uid else { return }
        let imageData = image
        ImageUploader.uploadImage(with: imageData) { imageUrl in
            Firestore.firestore().collection("users").document(uid).updateData(["profileImageUrl":imageUrl]) { _ in
                print("Successfully updated user data")
            }
        }
    }

ImagePicker

import SwiftUI
import PhotosUI

@MainActor
class ImagePicker: ObservableObject {
    @Published var image: Image?
    
    @Published var imageSelection: PhotosPickerItem? {
        didSet {
            if let imageSelection {
                Task {
                    try await loadTransferrable(from: imageSelection)
                }
            }
        }
    }
    
    func loadTransferrable(from imageSelection: PhotosPickerItem?) async throws {
        do {
            if let data = try await imageSelection?.loadTransferable(type: Data.self) {
                if let uiimage = UIImage(data: data) {
                    self.image = Image(uiImage: uiimage)
                }
            }
        } catch {
            print("Error: \(error.localizedDescription)")
        }
    }
}


View

 if let image = imagePicker.image {
                Button {
                    viewModel.uploadProfileImage(with: image)
                } label: {
                    Text("Continue")
                        .font(.headline)
                        .foregroundColor(.white)
                        .frame(width: 340.0, height: 50.0)
                        .background(.blue)
                        .clipShape(Capsule())
                        .padding()
                }
                .shadow(radius: 10.0)
                .padding(24.0)
            }

As you can see, I have a problem in the view: Cannot convert value of type 'Image' to expected argument type 'Data', which makes sense. The images are all of type Data.

How can I do it?


Solution

  • don't use @Published var image: Image? in your class ImagePicker: ObservableObject, Image is a View and is for use in other Views.

    Use @Published var image: UIImage? and adjust your code accordingly.

    Then use image.pngData() to get the Data to upload.