🔷 StorageManager.swift
final class StorageManager {
// MARK: - Variable
static let shared = StorageManager()
let storage = Storage.storage()
// MARK: - Function
/// 프로필 이미지를 firestore에 저장하는 메서드
func uploadProfilePhoto(with userID: String, image: Data, metaData: StorageMetadata) -> AnyPublisher<StorageMetadata, Error> {
return storage
.reference()
.child("images/\(userID)/profileImage/profileImage.jpg")
.putData(image, metadata: metaData)
.print()
.eraseToAnyPublisher()
}
func getDownloadURL(for id: String?) -> AnyPublisher<URL, Error> {
guard let id = id else { return Fail(error: FireStorageError.invalidImageID).eraseToAnyPublisher() }
return storage
.reference(withPath: id)
.downloadURL()
.print()
.eraseToAnyPublisher()
}
}
enum FireStorageError: Error {
case invalidImageID
}
📌 func uploadProfilePhoto() 메서드 역할
https://explorer89.tistory.com/371
📍 Firebase 의 Storage 에 사진 업로드하는 방법
✅ Firebase 의 Storage 에 저장하는 메서드images/{userID}/profileImage/profileImage_{userID}.jpg 경로를 사용해서 사용자별로 폴더를 구분나중에 리뷰 이미지 등 다른 카테고리를 추가할 확장성도 고려이 메서
explorer89.tistory.com
📌 getDownloadURL(for:) 메서드의 역할
이 메서드는 Firebase Storage에 저장된 이미지의 다운로드 URL을 가져오는 역할
- id → 이미지가 저장된 Firebase Storage 내의 경로 (예: "images/user123/profile.jpg")
- .downloadURL() → Firebase Storage에서 해당 경로의 이미지 다운로드 URL을 가져옴.
- Fail(error: FireStorageError.invalidImageID).eraseToAnyPublisher() → id가 nil이면 즉시 실패하는 Publisher 반환.
즉, 업로드된 이미지의 경로를 받아서, 해당 이미지의 다운로드 URL을 반환하는 메서드
🔷 ProfileDataFormViewModel.swift
final class ProfileDataFormViewModel: ObservableObject {
// MARK: - @Published
@Published var username: String?
@Published var bio: String?
@Published var avatarPath: String?
@Published var imageData: UIImage?
@Published var isFormValid: Bool = false
@Published var error: String = ""
@Published var isOnboardingFinished: Bool = false
private var cancelable: Set<AnyCancellable> = []
// MARK: - Function
/// 프로필 유효성 검사
func validateUserProfileForm() {
guard let username = username, username.count > 2,
let bio = bio, bio.count > 2,
imageData != nil else {
isFormValid = false
return
}
isFormValid = true
}
func uploadAvatar() {
let userID = Auth.auth().currentUser?.uid ?? ""
guard let imageData = imageData?.jpegData(compressionQuality: 0.5) else { return }
let metaData = StorageMetadata()
metaData.contentType = "image/jpeg"
StorageManager.shared.uploadProfilePhoto(with: userID, image: imageData, metaData: metaData)
.flatMap { metaData in
StorageManager.shared.getDownloadURL(for: metaData.path)
}
.sink { [weak self] completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
self?.error = error.localizedDescription
case .finished:
self?.updateUserData()
}
} receiveValue: { [weak self] url in
self?.avatarPath = url.absoluteString
}
.store(in: &cancelable)
}
private func updateUserData() {
guard let username,
let bio,
let avatarPath,
let id = Auth.auth().currentUser?.uid else { return }
let updateFields: [String: Any] = [
"username": username,
"bio": bio,
"avatarPath": avatarPath,
"isUserOnboarded": true
]
DatabaseManager.shared.collectionUsers(updateFields: updateFields, for: id)
.sink { [weak self] completion in
if case .failure(let error) = completion {
print(error.localizedDescription)
self?.error = error.localizedDescription
}
} receiveValue: { [weak self] onboardingState in
self?.isOnboardingFinished = onboardingState
}
.store(in: &cancelable)
}
}
📌 uploadAvatar() 메서드 역할
이 함수는 사용자의 프로필 사진을 Firebase Storage에 업로드하고, 업로드된 이미지의 다운로드 URL을 가져와 저장하는 역할을 한다.
https://explorer89.tistory.com/372
🔥 Firebase Storage에 이미지 업로드, 이미지 주소 URL 가져오기
func uploadAvatar() { let userID = Auth.auth().currentUser?.uid ?? "" // ✅ 유저 ID 가져오기 guard let imageData = imageData?.jpegData(compressionQuality: 0.5) else { return } // ✅ 이미지 데이터 변환 let metaData = StorageMetadata() // Fir
explorer89.tistory.com
🔷 DatabaseManager.swift
class DatabaseManager {
// MARK: - Variable
static let shared = DatabaseManager()
let db = Firestore.firestore()
let userPath: String = "users"
...
func collectionUsers(updateFields: [String: Any], for id: String) -> AnyPublisher<Bool, Error> {
Future<Bool, Error> { promise in
self.db.collection(self.userPath).document(id).updateData(updateFields) { error in
if let error = error {
promise(.failure(error))
} else {
promise(.success(true))
}
}
}
.eraseToAnyPublisher()
}
}
📌 collectionUsers(updateFields: [String: Any], for id: String) 메서드 역할
- Firestore의 특정 문서(사용자 데이터)를 업데이트하는 역할을 한다.
- 입력값
- updateFields: [String: Any] → 업데이트할 데이터 (예: ["username": "newName"])
- id: String → 업데이트할 사용자의 Firebase UID
- 출력값
- AnyPublisher<Bool, Error> → 성공하면 true, 실패하면 Error를 반환하는 Combine Publisher
✅ 전체 흐름
1. ProfileDataFormViewController() 에서 프로필 작성 완료 후 "작성 완료" 버튼을 누른다.
@objc private func didTapSubmit() {
viewModel.uploadAvatar()
}
2. uploadAvatar() 메서드 실행하면, uploadProfilePhoto() 호출 후 업로드된 이미지의 다운로드 URL 요청
flatMap을 사용해 getDownloadURL() 호출한 뒤, sink에서 결과처리
실패 -> 에러 / 성공 -> 다운로드 URL을 avatarPath에 저장, updateUserData() 실행
3. collectionUsers(updateFields: [String: Any], for id: String)를 통해 사용자 정보 업데이트 진행
4. 사용자 정보 업데이트 완료 후 "isUserOnboarded" : true 가 됨
5. ProfileDataFormViewController 내에서 아래 bindView를 통해 MainTabBarController로 이동
private func bindViews() {
...
viewModel.$isOnboardingFinished
.sink { [weak self] success in
if success {
// ✅ 기존의 모든 화면을 닫고, OnboardingViewController로 이동
self?.view.window?.rootViewController?.dismiss(animated: true, completion: {
if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate {
sceneDelegate.window?.rootViewController = MainTabBarController()
}
})
}
}
.store(in: &cancelable)
}
6. MainTabBarController로 와서는 isUserOnboarded 값에 따라 화면 이동 결정
private func bindView() {
// ✅ 회원정보가 수정이 안되었다면 ➡️ ProfileDataFormViewController 로 이동
viewModel.$user
.sink { [weak self] user in
guard let user = user else { return }
if !user.isUserOnboarded{
dump(user.isUserOnboarded)
self?.completeUserOnboarding()
}
}
.store(in: &cancelable)
}
func completeUserOnboarding() {
let profileDataFormVC = ProfileDataFormViewController()
present(profileDataFormVC, animated: true)
}
'Project > MovieClip' 카테고리의 다른 글
✅ 프로필 수정하기! (기존 프로필 입력 창 사용하기) (0) | 2025.03.04 |
---|---|
❌ 문제 해결... ProfileItem이 Hashable 및 Equatable 프로토콜을 준수하지 않는다? (0) | 2025.03.01 |
👤 Firebase에 로그인, 회원정보를 FireStore 저장, 회원정보 불러오기 (0) | 2025.02.27 |
👤 Firebase에 이메일 & 비밀번호 회원가입 기능 (MVVM + Combine) (0) | 2025.02.26 |
🌟 회원 가입 화면 뷰 관리? 순서?는 어떻게 해야하나? (0) | 2025.02.26 |