Firebase Storage & SwiftUI

Upload Immagini e Media Scalabile su Firebase Storage in SwiftUI: Compression e Progress

Il caricamento di media ad alta risoluzione da un'applicazione iOS verso il cloud richiede due attenzioni fondamentali: **ottimizzazione della dimensione dei file** prima dell'invio e **tracciamento del progresso** per offrire una UX fluida all'utente.

1. Compressione dell'Immagine lato Client in Swift

Inviare direttamente foto da 12 megapixel scattate da un iPhone spreca banda e spazio di archiviazione. Possiamo ridimensionare e comprimere l'immagine via jpegData(compressionQuality:):

import UIKit

func prepareImageForUpload(image: UIImage, maxDimension: CGFloat = 1200, compression: CGFloat = 0.7) -> Data? {
    let aspectWidth = maxDimension / image.size.width
    let aspectHeight = maxDimension / image.size.height
    let aspectRatio = min(aspectWidth, aspectHeight)
    
    var newSize = image.size
    if aspectRatio < 1.0 {
        newSize = CGSize(width: image.size.width * aspectRatio, height: image.size.height * aspectRatio)
    }
    
    let renderer = UIGraphicsImageRenderer(size: newSize)
    let resizedImage = renderer.image { _ in
        image.draw(in: CGRect(origin: .zero, size: newSize))
    }
    
    return resizedImage.jpegData(compressionQuality: compression)
}

2. Gestione UploadTask e Progress Bar in SwiftUI

Firebase Storage fornisce la classe StorageUploadTask che permette di osservare le fasi dell'upload ed aggiornare una variabile @Published in SwiftUI:

import SwiftUI
import FirebaseStorage

class ImageUploadViewModel: ObservableObject {
    @Published var uploadProgress: Double = 0.0
    @Published var isUploading: Bool = false
    @Published var downloadURL: String? = nil
    
    func uploadImage(data: Data, userId: String) {
        let storageRef = Storage.storage().reference()
        let fileRef = storageRef.child("users/\(userId)/avatar_\(UUID().uuidString).jpg")
        
        let metadata = StorageMetadata()
        metadata.contentType = "image/jpeg"
        
        self.isUploading = true
        let uploadTask = fileRef.putData(data, metadata: metadata)
        
        // Tracciamento avanzamento
        uploadTask.observe(.progress) { [weak self] snapshot in
            guard let progress = snapshot.progress else { return }
            DispatchQueue.main.async {
                self?.uploadProgress = Double(progress.completedUnitCount) / Double(progress.totalUnitCount)
            }
        }
        
        // Completamento
        uploadTask.observe(.success) { [weak self] _ in
            fileRef.downloadURL { url, error in
                DispatchQueue.main.async {
                    self?.isUploading = false
                    self?.downloadURL = url?.absoluteString
                }
            }
        }
    }
}

3. Security Rules per Firebase Storage

Assicurati che soltanto l'utente autenticato proprietario della cartella possa scrivere ed eliminare i file:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allPaths=**} {
      allow read: if request.auth != null;
      allow write: if request.auth != null && request.auth.uid == userId
                   && request.resource.size < 5 * 1024 * 1024 // Max 5 MB
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

Conclusione

La compressione preventiva associata al tracciamento in tempo reale assicura un upload veloce ed esente da errori. Per assistenza nell'architettura media per app SwiftUI, scrivi a diallooyunus@gmail.com.

Yunus Diallo (DialloDev)

Fondatore di Dywtal Digital a Parma (Italia), sviluppatore iOS freelance. Esperto nell'ottimizzazione delle risorse di rete e gestione media su cloud.