You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase注册时头像上传至Firestore失败问题求助

Firebase注册功能头像上传失败排查

在Firebase实现用户注册功能时,RegisterViewController页面包含用户名、邮箱、密码及头像字段,Firestore数据结构已配置完成。但选择头像、填写信息点击注册按钮后,头像无法上传至Firebase Storage,控制台报错:Firebase Storage 업로드 실패: Object profile_images/rRMJBmF4NlYnlOmVxawxGZqkwzu2.jpg does not exist


代码功能说明

用户注册(registerUser)

  • 通过Firebase Auth使用邮箱和密码创建用户账号
  • 创建成功后获取用户userUID

头像上传与Firestore保存(uploadProfileAndSaveUserToFirestore)

  • 将头像上传至Firebase Storage
  • 上传完成后获取下载URL
  • 将email、username、profileImageURL保存至Firestore的users集合对应userUID的文档中

Firestore用户信息保存(saveUserToFirestore)

  • 在Firestore中存储username、email、profileImageURL字段及createdAt时间戳

相关代码

RegisterUserRequest结构

import Foundation
import UIKit

struct RegisterUserRequest {
    
    let username: String
    let email: String
    let password: String
    
    var userImage: UIImage
    
}

AuthService类相关方法

func registerUser(request: RegisterUserRequest, completion: @escaping (Bool, Error?) -> Void) {
        // 1. Firebase Auth로 사용자 생성
        Auth.auth().createUser(withEmail: request.email, password: request.password) { authResult, error in
            if let error = error {
                print("Firebase Auth 사용자 생성 실패: \(error.localizedDescription)")
                completion(false, error)
                return
            }
            
            guard let userUID = authResult?.user.uid else {
                let uidError = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "사용자 UID를 가져올 수 없습니다."])
                completion(false, uidError)
                return
            }
            
            // 2. 이미지 업로드와 Firestore 데이터 저장 통합
            self.uploadProfileAndSaveUserToFirestore(userUID: userUID, request: request) { success, error in
                if success {
                    print("회원가입 성공")
                    completion(true, nil)
                } else {
                    print("회원가입 실패: \(error?.localizedDescription ?? "알 수 없는 에러")")
                    completion(false, error)
                }
            }
        }
    }

    private func uploadProfileAndSaveUserToFirestore(userUID: String, request: RegisterUserRequest, completion: @escaping (Bool, Error?) -> Void) {
        // 1. 이미지를 Firebase Storage에 업로드
        guard let imageData = request.userImage.jpegData(compressionQuality: 0.8) else {
            completion(false, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "이미지 데이터를 변환할 수 없습니다."]))
            return
        }
        
        let storageRef = Storage.storage().reference().child("profile_images/\(userUID).jpg")
        storageRef.putData(imageData, metadata: nil) { metadata, error in
            if let error = error {
                print("Firebase Storage 업로드 실패: \(error.localizedDescription)")
                completion(false, error)
                return
            }
            
            // 2. 다운로드 URL 가져오기
            storageRef.downloadURL { url, error in
                if let error = error {
                    print("Firebase Storage 다운로드 URL 가져오기 실패: \(error.localizedDescription)")
                    completion(false, error)
                    return
                }
                
                guard let profileImageURL = url?.absoluteString else {
                    completion(false, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "이미지 URL을 가져올 수 없습니다."]))
                    return
                }
                
                // 3. Firestore에 사용자 정보 저장
                self.saveUserToFirestore(userUID: userUID, username: request.username, email: request.email, profileImageURL: profileImageURL) { success, error in
                    completion(success, error)
                }
            }
        }
    }

    private func saveUserToFirestore(userUID: String, username: String, email: String, profileImageURL: String, completion: @escaping (Bool, Error?) -> Void) {
        let db = Firestore.firestore()
        
        let userData: [String: Any] = [
            "username": username,
            "email": email,
            "profileImageURL": profileImageURL,
            "createdAt": Timestamp()
        ]
        
        db.collection("users").document(userUID).setData(userData) { error in
            if let error = error {
                print("Firestore 저장 실패: \(error.localizedDescription)")
                completion(false, error)
            } else {
                print("Firestore 저장 성공")
                completion(true, nil)
            }
        }
    }

RegisterController的PHPickerViewControllerDelegate扩展

extension RegisterController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        
        // PHPicker 닫기
        picker.dismiss(animated: true)
        
        // 첫 번째 선택된 아이템 가져오기
        let itemProvider = results.first?.itemProvider
        
        if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
            // UIImage 불러오기
            itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
                if let image = image as? UIImage { // 불러온 이미지 타입 확인
                    DispatchQueue.main.async {
                        // CustomImageView에 이미지 설정
                        self.profileImage = image
                        self.userPofileView.setImageType(.user(image))
                    }
                } else {
                    print("이미지가 올바르지 않습니다.")
                }
            }
        } else {
            print("이미지를 불러올 수 없습니다.")
        }
    }
}

RegisterController的注册按钮点击事件

// MARK: - Selectors
    @objc private func didTapSignUp() {
        let username = self.usernameField.text ?? ""
        let email = self.emailField.text ?? ""
        let password = self.passwordField.text ?? ""
        let userImage = profileImage ?? UIImage()
        
        let registerUserRequest = RegisterUserRequest(username: username,
                                                      email: email,
                                                      password: password,
                                                      userImage: userImage)
        
        print("userImage: \(userImage)")
        
        // Check Username
        if !Validator.isValidUsername(for: registerUserRequest.username) {
            AlertManager.showInvalidUsernameAlert(on: self)
            return
        }
        
        // Check Email
        if !Validator.isValidEmail(for: registerUserRequest.email) {
            AlertManager.showInvalidEmailAlert(on: self)
            return
        }
        
        // Check Password
        if !Validator.isValidPassword(for: registerUserRequest.password) {
            AlertManager.showInvalidPasswordAlert(on: self)
            return
        }
        
        AutheService.shared.registerUser(request: registerUserRequest) { wasRegistered, error in
            
            if let error = error {
                AlertManager.showSignInErrorAlert(on: self, with: error)
                return
            }
            
            if wasRegistered {
                if let sceneDelegate = self.view.window?.windowScene?.delegate as? SceneDelegate {
                    sceneDelegate.checkAuthentication()
                }
            } else {
                AlertManager.showSignInErrorAlert(on: self, with: NSError(domain: "", code: 500, userInfo: [NSLocalizedDescriptionKey: "회원가입 실패"]))
            }
        }
    }

问题排查与解决方案

1. 空UIImage导致上传失败

注册时若用户未选择头像,代码会使用UIImage()空实例,该空图片无法转换为有效JPEG数据,导致Storage中无实际文件,后续获取URL时提示对象不存在。

  • 修复:强制用户选择头像,或提供默认头像:
    guard let userImage = profileImage else {
        AlertManager.showErrorAlert(on: self, with: "프로필 이미지를 선택해주세요.")
        return
    }
    

2. Firebase Storage权限配置问题

默认Storage规则可能拒绝上传请求,导致文件未写入,后续获取URL时提示对象不存在。

  • 测试阶段临时调整规则为公开读写(上线需修改为安全规则):
    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write: if true;
        }
      }
    }
    
  • 上线时配置为仅认证用户可上传自己的头像:
    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /profile_images/{userId}.jpg {
          allow read: if request.auth != null;
          allow write: if request.auth != null && request.auth.uid == userId;
        }
      }
    }
    

3. 图片转换失败

部分图片格式(如带透明通道的PNG)转换为JPEG时可能失败,或图片本身损坏导致无法生成有效数据。

  • 修复:同时支持PNG和JPEG转换:
    guard let imageData = request.userImage.pngData() ?? request.userImage.jpegData(compressionQuality: 0.8) else {
        completion(false, NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "이미지 데이터를 변환할 수 없습니다."]))
        return
    }
    

4. PHPicker图片加载异常

PHPicker异步加载图片时可能失败,但原代码未处理该错误,导致profileImage仍为空。

  • 修复:添加加载错误处理:
    itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
        if let error = error {
            print("이미지 로드 실패: \(error.localizedDescription)")
            DispatchQueue.main.async {
                AlertManager.showErrorAlert(on: self, with: "이미지를 로드할 수 없습니다.")
            }
            return
        }
        if let image = image as? UIImage {
            DispatchQueue.main.async {
                self.profileImage = image
                self.userPofileView.setImageType(.user(image))
            }
        } else {
            print("이미지가 올바르지 않습니다.")
            DispatchQueue.main.async {
                AlertManager.showErrorAlert(on: self, with: "유효한 이미지를 선택해주세요.")
            }
        }
    }
    

内容的提问来源于stack exchange,提问作者권정근

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:25:54