Firebase Storage Upload Process

Complete guide to uploading files and images to Firebase Storage with folder and file management

Firebase Storage Upload

Note:

This tutorial covers uploading files and images to Firebase Storage, including folder and file management. This is essential for storing user-generated content like profile photos, documents, and media files.

Overview

Hello!

I will show you the process of uploading images or files by creating folder and file names in Firebase Storage, which is one of the Firebase services.

Reference:

Step 1: Firebase Storage Setup

First, go to the Storage section in Firebase and start the process by accepting the read and write rules.

Firebase Storage Rules

In the current rules, read and write operations are permitted for users who have authorization from the Auth service.

Step 2: Firebase Storage Reference

Start by importing Firebase at the top of your project.

import Firebase

Assign Storage to a variable to use it in your project.

let storage = Storage.storage()

Define a variable to create a reference to Firebase Storage.

let storageReference = storage.reference()

Step 3: Creating Folders and Files

The Firebase Storage directory we accessed above (let storageReference = storage.reference()) we will use the child structure to create a new folder.

let mediaFolder = storageReference.child("media")

After defining our folder variable, let's convert the image coming from imageView to data and define the file name to a variable.

// Convert image to data
guard let imageData = imageView.image?.jpegData(compressionQuality: 0.8) else { return }

// Create file name with timestamp
let fileName = "image_\(Date().timeIntervalSince1970).jpg"
let imageReference = mediaFolder.child(fileName)

// Upload to Firebase Storage
imageReference.putData(imageData, metadata: nil) { metadata, error in
    if let error = error {
        print("Error uploading image: \(error)")
    } else {
        // Get download URL
        imageReference.downloadURL { url, error in
            if let downloadURL = url {
                print("Upload successful. Download URL: \(downloadURL)")
            }
        }
    }
}

The "putData" command on line 9 uploads the specified data (the compressed jpeg format of the image in imageView) to the specified location (imageReference) in FirebaseStorage.

If there's an error, it prints the error in debug, if there's no error, it prints the URL of the uploaded image.

Step 4: Complete Implementation

Here's the complete implementation for Firebase Storage upload:

import UIKit
import Firebase
import FirebaseStorage

class StorageViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var uploadButton: UIButton!
    @IBOutlet weak var progressView: UIProgressView!
    
    private let storage = Storage.storage()
    private let storageReference = Storage.storage().reference()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        uploadButton.setTitle("Upload Image", for: .normal)
        uploadButton.backgroundColor = .systemBlue
        uploadButton.setTitleColor(.white, for: .normal)
        uploadButton.layer.cornerRadius = 8
        
        progressView.isHidden = true
        progressView.progress = 0.0
    }
    
    @IBAction func uploadButtonTapped(_ sender: Any) {
        uploadImageToFirebase()
    }
    
    private func uploadImageToFirebase() {
        guard let image = imageView.image else {
            showAlert(title: "Error", message: "No image selected")
            return
        }
        
        // Show progress
        progressView.isHidden = false
        uploadButton.isEnabled = false
        
        // Convert image to data
        guard let imageData = image.jpegData(compressionQuality: 0.8) else {
            showAlert(title: "Error", message: "Failed to convert image to data")
            return
        }
        
        // Create folder and file structure
        let mediaFolder = storageReference.child("media")
        let fileName = "image_\(Date().timeIntervalSince1970).jpg"
        let imageReference = mediaFolder.child(fileName)
        
        // Create metadata
        let metadata = StorageMetadata()
        metadata.contentType = "image/jpeg"
        
        // Upload to Firebase Storage
        let uploadTask = imageReference.putData(imageData, metadata: metadata) { metadata, error in
            DispatchQueue.main.async {
                self.progressView.isHidden = true
                self.uploadButton.isEnabled = true
                
                if let error = error {
                    self.showAlert(title: "Upload Failed", message: error.localizedDescription)
                    return
                }
                
                // Get download URL
                imageReference.downloadURL { url, error in
                    if let downloadURL = url {
                        self.saveImageURL(downloadURL.absoluteString)
                        self.showAlert(title: "Success", message: "Image uploaded successfully!")
                    } else {
                        self.showAlert(title: "Error", message: "Failed to get download URL")
                    }
                }
            }
        }
        
        // Monitor upload progress
        uploadTask.observe(.progress) { snapshot in
            let progress = Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount)
            DispatchQueue.main.async {
                self.progressView.progress = Float(progress)
            }
        }
    }
    
    private func saveImageURL(_ url: String) {
        // Save URL to Firestore or UserDefaults
        UserDefaults.standard.set(url, forKey: "lastUploadedImageURL")
        
        // TODO: Save to Firestore if needed
        if let userID = Auth.auth().currentUser?.uid {
            let db = Firestore.firestore()
            db.collection("users").document(userID).updateData([
                "lastUploadedImageURL": url,
                "uploadedAt": FieldValue.serverTimestamp()
            ]) { error in
                if let error = error {
                    print("Error saving image URL: \(error)")
                }
            }
        }
    }
    
    private func showAlert(title: String, message: String) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
    }
}

Step 5: Advanced Storage Features

Multiple File Types Support

enum FileType {
    case image
    case video
    case document
    
    var folder: String {
        switch self {
        case .image: return "images"
        case .video: return "videos"
        case .document: return "documents"
        }
    }
    
    var contentType: String {
        switch self {
        case .image: return "image/jpeg"
        case .video: return "video/mp4"
        case .document: return "application/pdf"
        }
    }
}

private func uploadFile(_ fileData: Data, type: FileType, fileName: String) {
    let folder = storageReference.child(type.folder)
    let fileReference = folder.child(fileName)
    
    let metadata = StorageMetadata()
    metadata.contentType = type.contentType
    
    let uploadTask = fileReference.putData(fileData, metadata: metadata) { metadata, error in
        if let error = error {
            print("Error uploading file: \(error)")
        } else {
            fileReference.downloadURL { url, error in
                if let downloadURL = url {
                    print("File uploaded successfully: \(downloadURL)")
                }
            }
        }
    }
}

Batch Upload

private func uploadMultipleImages(_ images: [UIImage]) {
    let group = DispatchGroup()
    var uploadedURLs: [String] = []
    
    for (index, image) in images.enumerated() {
        group.enter()
        
        guard let imageData = image.jpegData(compressionQuality: 0.8) else {
            group.leave()
            continue
        }
        
        let fileName = "image_\(index)_\(Date().timeIntervalSince1970).jpg"
        let imageReference = storageReference.child("images").child(fileName)
        
        imageReference.putData(imageData, metadata: nil) { metadata, error in
            if let error = error {
                print("Error uploading image \(index): \(error)")
            } else {
                imageReference.downloadURL { url, error in
                    if let downloadURL = url {
                        uploadedURLs.append(downloadURL.absoluteString)
                    }
                    group.leave()
                }
            }
        }
    }
    
    group.notify(queue: .main) {
        print("All images uploaded: \(uploadedURLs)")
    }
}

File Download

private func downloadImage(from url: String, completion: @escaping (UIImage?) -> Void) {
    guard let imageURL = URL(string: url) else {
        completion(nil)
        return
    }
    
    let imageReference = storage.reference(forURL: url)
    imageReference.getData(maxSize: 10 * 1024 * 1024) { data, error in
        if let error = error {
            print("Error downloading image: \(error)")
            completion(nil)
        } else if let data = data {
            let image = UIImage(data: data)
            completion(image)
        } else {
            completion(nil)
        }
    }
}

Step 6: Security Rules

Firebase Storage Rules

Configure your Firebase Storage rules in the Firebase Console:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // Allow authenticated users to read and write their own files
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    
    // Allow authenticated users to upload to public folders
    match /public/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null;
    }
    
    // Allow authenticated users to upload images
    match /images/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null 
        && request.resource.size < 5 * 1024 * 1024 // 5MB limit
        && request.resource.contentType.matches('image/.*');
    }
  }
}

Step 7: Error Handling

Comprehensive Error Handling

enum StorageError: Error {
    case noImageSelected
    case imageConversionFailed
    case uploadFailed(Error)
    case downloadFailed(Error)
    case invalidURL
    case networkError
    
    var localizedDescription: String {
        switch self {
        case .noImageSelected:
            return "No image selected for upload"
        case .imageConversionFailed:
            return "Failed to convert image to data"
        case .uploadFailed(let error):
            return "Upload failed: \(error.localizedDescription)"
        case .downloadFailed(let error):
            return "Download failed: \(error.localizedDescription)"
        case .invalidURL:
            return "Invalid download URL"
        case .networkError:
            return "Network connection error"
        }
    }
}

private func handleUploadError(_ error: StorageError) {
    DispatchQueue.main.async {
        self.progressView.isHidden = true
        self.uploadButton.isEnabled = true
        self.showAlert(title: "Upload Error", message: error.localizedDescription)
    }
}

Step 8: Performance Optimization

Image Compression and Resizing

private func compressAndResizeImage(_ image: UIImage, maxSize: CGFloat = 1024) -> Data? {
    let scale = min(maxSize / image.size.width, maxSize / image.size.height)
    let newSize = CGSize(width: image.size.width * scale, height: image.size.height * scale)
    
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
    image.draw(in: CGRect(origin: .zero, size: newSize))
    let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return resizedImage?.jpegData(compressionQuality: 0.8)
}

Upload Queue Management

class UploadQueue {
    private var uploadTasks: [StorageUploadTask] = []
    private let maxConcurrentUploads = 3
    
    func addUploadTask(_ task: StorageUploadTask) {
        uploadTasks.append(task)
        
        if uploadTasks.count <= maxConcurrentUploads {
            task.resume()
        }
    }
    
    func removeCompletedTask(_ task: StorageUploadTask) {
        uploadTasks.removeAll { $0 == task }
        
        // Start next task if available
        if let nextTask = uploadTasks.first(where: { $0.snapshot.state == .resumed }) {
            nextTask.resume()
        }
    }
}

Note:

Firebase Storage is a powerful service for file management in iOS apps. Remember to implement proper security rules, handle errors gracefully, and provide good user feedback during upload processes.