Firebase Storage Upload Process
Complete guide to uploading files and images to Firebase Storage with folder and file management
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:
- Swift — CocoaPods — Firebase Integration
- Firebase User Registration
- Firebase User Login
- Firebase User Redirect
- Profile Photo Selection
Step 1: Firebase Storage Setup
First, go to the Storage section in Firebase and start the process by accepting the read and write 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.