I am developing an iOS app using Swift that requires users to upload profile images to Firebase Storage. Despite the image selection seemingly working fine, I keep encountering the following error during the upload process:
DEBUG: Profile photo is not correctly loaded.
DEBUG: User upload failed with error: The operation couldn’t be completed. (Flixter.ImageUploaderError error 0.)
Additionally, the following warning appears in the logs:
CGImageProviderGetContentHeadroom: Bad headroom value 0.000000 for SDR, returning 1.0
The image is being loaded with the correct dimensions, but the upload still fails with “Invalid image data”. I have implemented resizing of large images to a more manageable size before uploading, but the issue persists.
Steps to Reproduce:
User selects an image from the photo library.
The image is displayed on the profile setup screen.
Upon confirming the selection, the app attempts to upload the image to Firebase Storage.
The upload fails with the mentioned error.
Relevant Code:
ImagePicker.swift
This file ensures that the image is correctly selected from the user’s photo library.
import SwiftUI
import PhotosUI
struct ImagePicker: UIViewControllerRepresentable {
@Binding var image: UIImage?
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider else {
print("DEBUG: No provider found.")
return
}
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
if let error = error {
print("DEBUG: Failed to load image with error: (error.localizedDescription)")
return
}
guard let self = self, let uiImage = image as? UIImage else {
print("DEBUG: Failed to cast image to UIImage.")
return
}
DispatchQueue.main.async {
print("DEBUG: Image loaded with size: (uiImage.size)")
self.parent.image = uiImage
}
}
} else {
print("DEBUG: Provider cannot load UIImage.")
}
}
}
}
SelectProfileImageView.swift
Ensures the selected image is passed correctly to the OnboardingManager for uploading.
import SwiftUI
struct SelectProfileImageView: View {
@EnvironmentObject var manager: OnboardingManager
@State private var showImagePicker = false
@State private var inputImage: UIImage?
var body: some View {
GeometryReader { geometry in
VStack {
VStack(alignment: .center, spacing: 15) {
Text("PROFILE IMAGE")
.font(.custom("samble", size: 34))
.padding(.top)
Text("Please select a profile image to continue.")
.font(.custom("samble", size: 26))
.foregroundStyle(ColorUtils.darkPurple)
}
.padding(.horizontal)
Spacer()
ProfileImageButton(profileImage: $inputImage, showImagePicker: $showImagePicker, geometry: geometry)
Spacer()
Button(action: {
if let newImage = inputImage {
print("DEBUG: New image selected with size: (newImage.size)")
manager.profilePhoto = newImage
Task {
do {
let imageUrl = try await manager.service.uploadUserPhoto(newImage)
DispatchQueue.main.async {
manager.profileImageURL = imageUrl
print("DEBUG: Image URL: (imageUrl)")
manager.navigate() // Proceed to the next step or complete onboarding
}
} catch {
print("DEBUG: Failed to upload photo with error: (error.localizedDescription)")
}
}
} else {
print("DEBUG: No image selected.")
}
}) {
NextButton(formIsValid: formIsValid)
}
.padding()
}
.background(Image(.backgroundAuthView).resizable().edgesIgnoringSafeArea(.all))
}
.sheet(isPresented: $showImagePicker) {
ImagePicker(image: $inputImage)
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
BackButton()
}
}
}
}
extension SelectProfileImageView: FormValidatorProtocol {
var formIsValid: Bool {
return inputImage != nil
}
}
#Preview {
SelectProfileImageView()
}
ImageUploader.swift
Handles the image conversion and upload to Firebase Storage.
import UIKit
import FirebaseStorage
enum ImageUploaderError: Error {
case invalidData
case uploadFailed
}
struct ImageUploader {
// Resize the image to a more manageable size
private func resizeImage(_ image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(origin: .zero, size: newSize)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage ?? image
}
func uploadImage(_ image: UIImage) async throws -> String {
print("DEBUG: Original image dimensions - width: (image.size.width), height: (image.size.height)")
// Resize image if it's too large
let resizedImage = resizeImage(image, targetSize: CGSize(width: 1024, height: 1024))
print("DEBUG: Resized image dimensions - width: (resizedImage.size.width), height: (resizedImage.size.height)")
// Check for both JPEG and PNG data
guard let imageData = resizedImage.jpegData(compressionQuality: 0.4) ?? resizedImage.pngData() else {
print("DEBUG: Invalid image data.")
throw ImageUploaderError.invalidData
}
let filename = UUID().uuidString + ".jpg"
let ref = Storage.storage().reference(withPath: "/profile_images/(filename)")
do {
print("DEBUG: Uploading image data...")
let _ = try await ref.putDataAsync(imageData)
let url = try await ref.downloadURL()
print("DEBUG: Image uploaded successfully. URL: (url.absoluteString)")
return url.absoluteString
} catch {
print("DEBUG: Image upload failed with error: (error.localizedDescription)")
throw ImageUploaderError.uploadFailed
}
}
}
OnboardingManager.swift
Handles the process of navigating through the onboarding steps and uploading user data.
import Foundation
import SwiftUI
import FirebaseAuth
class OnboardingManager: ObservableObject {
@Published var navigationPath = [OnboardingSteps]()
@Published var fullname: String = ""
@Published var age: Int = 0
@Published var profilePhoto = UIImage()
@Published var profileImageURL: String = ""
@Published var school: String? = nil
@Published var likedEvents: [Event] = []
@Published var pendingEvents: [Event] = []
@Published var email: String = ""
@Published var birthday: Date = Date()
@Published var zodiacSign: String? = nil
@Published var zodiacDescription: String? = nil
@Published var currentUser: User?
let service: OnboardingService
init(service: OnboardingService) {
self.service = service
}
private var currentStep: OnboardingSteps?
func start() {
DispatchQueue.main.async {
guard let initialStep = OnboardingSteps(rawValue: 0) else { return }
self.navigationPath.append(initialStep)
}
}
func navigate() {
self.currentStep = self.navigationPath.last
guard let index = self.currentStep?.rawValue else { return }
guard let nextStep = OnboardingSteps(rawValue: index + 1) else {
Task { await uploadUserData() }
return
}
self.navigationPath.append(nextStep)
}
func uploadUserData() async {
do {
guard let id = Auth.auth().currentUser?.uid,
let email = Auth.auth().currentUser?.email else { return }
let ageComponents = Calendar.current.dateComponents([.year], from: birthday, to: Date())
guard let age = ageComponents.year else { return }
// Check if the profilePhoto is correctly loaded
if profilePhoto.size.width == 0 || profilePhoto.size.height == 0 {
print("DEBUG: Profile photo is not correctly loaded.")
throw ImageUploaderError.invalidData
}
// Upload the profile photo and get the URL
print("DEBUG: Uploading user photo...")
let profileImageURL = try await service.uploadUserPhoto(profilePhoto)
let user = User(
id: id,
fullname: fullname,
age: age,
profileImageURL: profileImageURL,
school: school ?? "",
email: email
)
// Upload the user data
print("DEBUG: Uploading user data...")
self.currentUser = try await service.uploadUserData(user, photo: profilePhoto)
DispatchQueue.main.async {
// Navigate to MainTabView
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
if let window = windowScene.windows.first {
window.rootViewController = UIHostingController(rootView: MainTabView())
window.makeKeyAndVisible()
}
}
}
} catch {
print("DEBUG: User upload failed with error: (error.localizedDescription)")
}
}
}
Question:
What could be causing the Invalid image data error, and how can I fix the issue to successfully upload the selected image to Firebase Storage? Any insights or suggestions on handling large image uploads in Swift would be greatly appreciated.
Alkis Toutziaridis is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
2