이미지랑 비디오를 firebase storage에 저장해봅시다

테스트모드로 작성합시다!
이제 문서를 읽어볼까요
![]() | ![]() |
|---|


ProductsManager에서 했던 것처럼
Storage에 대한 참조를 선언해줘야합니다
우선은 StorageManager를 만들어줄까요

metadata가 없어도 업로드는 되지만 서버에서 어떤 타입의 데이터가 저장되는지 몰라서 metadata도 같이 올려주는 게 좋습니다
func saveImage(data: Data) async throws ->(path: String, name: String) {
let meta = StorageMetadata()
meta.contentType = "image/jpeg"
let path = "\(UUID().uuidString).jpeg"
let returnedMetaData = try await storage.child(path).putDataAsync(data, metadata: meta)
guard let returnedPath = returnedMetaData.path, let returnedName = returnedMetaData.name else {
throw URLError(.badServerResponse)
}
return (returnedPath, returnedName)
}
저장하는 과정을 폴더 구조로 생각하면 유사함!
폴더가 있고 그 폴더에 파일이 저장된다고 생각합시다

되게 최근에 추가된 모듈이 있음!
PhotosUI
이미지피커 SwiftUI버전인가봄
(UIKit ImagePicker 가져와서 변형시키던 거 안 써도 될듯?)





뷰모델에서 saveImage 메소드 작성해주고!
return된거 한번 print 찍어봅시다

뷰로와서 image가 선택 되었을 때 뭔가 처리해주면 되겠죠
selectedItem이 .onChange 일때 해주려고 하는데
newValue 타입이 Data타입이 아닙니다! 뷰모델에서 작성한 메소드에서 이것까지 Data로 변환하는 게 필요할 거 같네요
func saveProfileImage(item: PhotosPickerItem) {
Task {
guard let data = try await item.loadTransferable(type: Data.self) else { return }
let (path, name) = try await StorageManager.shared.saveImage(data: data)
print("Success!")
print(path)
print(name)
}
}
뷰모델 메소드 이렇게 수정해줌! (import PhotosUI, SwiftUI 필요했다)
그러니까 지금 보면 PhotosPickerItem을 파라미터로 받아서
data타입으로 변환 시키고 StorageManager가 이 데이터 타입을 가지고 처리해주고 있습니다.

시뮬레이터 실행해서 이미지 셀렉해보면~

지금 path랑 name이 둘다 같은 이름으로 나오고 있는데 이건 이따가 수정합시다

콘솔로 돌아오면 이미지가 저장됐고!
클릭해서 이미지 볼 수도 있고!
이 이미지에 대한 URL도 생성된 걸 볼 수 있음!
Storage에도 Rules 설정할수 있구요
수동으로 이미지 업로드할 수도 있어요

이렇게 여러개의 이미지가 업로드되어 있다고 할 때
이게 개발자 친화적으로 업로드 되어 있지는 않죠
index가 없으니까 순서대로 정렬하기도 힘듭니다
컴퓨터에서 파일 정리할 때처럼 폴더만들고 앨범 커버, 음악, 영상 정리하듯이 해주면 편할 것 같지 않음?
Storage에서도 마찬가지로 해줄 수 있어요

먼저 imagesReference라는 StorageReference 타입의 변수를 선언해주고 storage.child로 폴더의 이름을 만들어줍니다.
그리고 returnedMetaDatadp imagesReference를 제일 먼저 넣어주면

이젠 path랑 image가 달라진 걸 볼 수 있습니다~!

폴더도 생겼구요!
조금 더 정돈을 해봅시다

users라는 폴더에 userId를 가지고 또 폴더가 만들어지게끔 해줬습니다

save할 때도 마찬가지로 userId가 들어가게끔 해주고

그리고 다시 한번 이미지를 선택해봅시다!


오케이~!~!
이제 users 폴더 안에 user Id폴더가 있고 그 안에 이미지파일이 저장됩니다~!~!
Firestore의 콜렉션과 문서 그리고 subCollection 같은 느낌으로 저장하기!!
final class StorageManager {
func saveImage(image: UIImage, userId: String) async throws -> (path: String, name: String) {
}
}
이번엔 UIImage를 파라미터로 받아서 Data타입으로 변환한 다음에
저장하는 걸 구현해봅시다
이게 더 제네럴하게 사용되긴 함 (PhotosUI가 나온지 얼마 안되서)
func saveImage(image: UIImage, userId: String) async throws -> (path: String, name: String) {
// image.pngData() 이미지 Png면 이렇게 쓰기
guard let data = image.jpegData(compressionQuality: 1) else {
throw URLError(.backgroundSessionWasDisconnected)
}
return try await saveImage(data: data, userId: userId)
}

여러가지 방법이 있다! 이런식으로 코드 단에서 압축 로직을 정할 수도 있겠고

Firebase에 extensions를 추가해서 하는 방법도 있음!!
(이 방법쓰는 게 좋은데 과금이 필요함)

getData라는 메소드를 구성해줬고!
근데 업로드 될 때 자동생성된 uuid를 어떻게 알 수 있을까..?!
업로드 후에 user 프로필 같은 모델에도 저장을 해줘야 한다

이미지Path를 저장하게끔 UserManager에 메소드 구성해줌

ProfileImagePath를 이미지를 save할 때 호출되게 해주고

뷰에 와서 Data? 타입의 imageData를 선언해준 다음에
.task에서 StorageManager로 Data를 가져올 수 있게 해준 다음에
뷰에서 profile에 있던 path를 이용해서 imageData에 값을 가져올 수 있게 해줬습니다
지금 한 것들 더 최적화 해줄 수도 있어요!

StorageManager에서 Data를 get할 때 그냥 Data를 Image로 바꿔주는 메소드도 작성해주는겁니다!!

기존에 있었던 imageData 타입의 변수도 UIImage? 로 변경하고 .task를 처리한 다음에 바로 꽂아버리면 조금 더 코드가 줄어들었죠?

지금 이미지를 저장할 때보면 path랑 name을 따로 나눠 놨었잖아요
이미지name을 DBUser에 저장하는 게 아니라 path 자체를 fullPath로 저장해버리면 좀 더 쉽게 관리해볼 수 있습니다~!

getData할 때 storage레퍼런스에서 바로 path를 넣어주면~!
![]() | ![]() |
|---|
오른쪽으로 바꼈죠
Async Image 써서 로드해볼까요
어떤 Image 콘테이너가 있고 url로 다운받아오는 과정으로 보통 이미지처리를 많이 하는데
Nuke UI라는 이미지 패키지도 있다!
(나중에 한번 써봅시다)
지금 이렇게 구현하려고 할 때문제는
firestore에 저장된 path는 있지만 다운로드하는 url이 없다는거!

이렇게 하면 download 할 수 있는 url을 가져올 수 있습니다~!

url을 StorageManager를 써서 가져오고

AsyncImage를 써서 url을 가지고 이미지를 다운로드 해주면~!
한번 다운로드하고 나면 매번 fetch해서 가져올 필요 없잖아요

Save할 때 url 자체를 DBUser에다가 저장해버리는 거죠

그리고 Storage에 들려서 fetch를 하는 게 아니라
DBUser가 들고 있는 profileImagePath를 가지고 AsyncImage를 가져오게 해주면!!

Baaam!


이제 유저 모델에 저장할 때 Path랑 PathURL도 같이 저장해줘야겠죠

UserManager에 저장할 때 Path랑 같이 URL도 저장되게 해줬습니다!