[SwiftUI Firebase] Storage

Woozoo·2023년 4월 26일

[SwiftUI Firebase]

목록 보기
14/14

이미지랑 비디오를 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를 처리한 다음에 바로 꽂아버리면 조금 더 코드가 줄어들었죠?


Dynamic Storage Reference


지금 이미지를 저장할 때보면 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도 저장되게 해줬습니다!

profile
우주형

0개의 댓글