Combine 사용전인 전 글 '[SwiftUI] Firebase Storage 다중이미지 upload & retrieve, 별점 리뷰 글 posting' 을 보면 비교를 쉽게 확인할 수 있다.
먼저 등록할 가게정보의 Store 구조체를 만들어준다.
import Foundation
import SwiftUI
import UIKit
import FirebaseFirestore
import FirebaseFirestoreSwift
struct Store: Codable, Hashable, Identifiable {
@DocumentID var id: String?
var storeName: String
var storeAddress: String
var coordinate: GeoPoint
var storeImages: [String]
var menu: [String : String]
var description: String
var countingStar: Double
var foodType: [String] //국밥 타입: ex:순대,돼지국밥
// var viewCount: Int// 장소 조회수
static func == (lhs : Store, rhs: Store) -> Bool{
lhs.id == rhs.id
}
}
extension Store {
static var test: Store = .init(storeName: "test", storeAddress: "test", coordinate: GeoPoint(latitude: 37, longitude: 125), storeImages: [], menu: [:], description: "test", countingStar: 0.5, foodType: ["순대국밥"])
}
StoreViewModel
firebase 'Store' 컬렉션에, Storage ' 등록, 불러오는 ViewModel을 작성한다.
View에서 사용하는 PhotosPicker ViewModel에서 함수로 빼 주었다.
import Foundation
import SwiftUI
import Combine
import PhotosUI
import Firebase
import FirebaseFirestore
import FirebaseStorage
final class StoreRegistrationViewModel: ObservableObject {
@Published var store: Store
@Published var latitude: String = ""
@Published var longitude: String = ""
@Published var selectedImages: [PhotosPickerItem] = []
@Published var selectedImageData: [Data] = []
@Published var convertedImages: [UIImage] = []
@Published var stores: [Store] = []
@Published var storeImages: [String : UIImage] = [:]
@Published var modified = false
private var cancellables = Set<AnyCancellable>()
private var database = Firestore.firestore()
private var storage = Storage.storage()
init(store: Store = Store(storeName: "",
storeAddress: "",
coordinate: GeoPoint(latitude: 0, longitude: 0),
storeImages: [],
menu: [:],
description: "",
countingStar: 0.0,
foodType: ["순대국밥"]
)) {
self.store = store
self.$store
.dropFirst()
.sink { [weak self] store in
self?.modified = true
}
.store(in: &self.cancellables)
}
private func convertToUIImages() {
if !selectedImageData.isEmpty {
for imageData in selectedImageData {
if let image = UIImage(data: imageData) {
convertedImages.append(image)
}
}
}
}
private func makeImageName() -> [String] {
var imgNameList: [String] = []
// iterate over images
for img in convertedImages {
let imgName = UUID().uuidString
imgNameList.append(imgName)
uploadImage(image: img, name: (store.storeName + "/" + imgName))
}
return imgNameList
}
private func addStoreInfo() {
do {
self.convertToUIImages()
self.store.storeImages = makeImageName()
//위도 경도값을 형변환해서 넣어주기
self.store.coordinate = GeoPoint(latitude: Double(self.latitude) ?? 0.0, longitude: Double(self.longitude) ?? 0.0)
let _ = try database.collection("Store")
.addDocument(from: self.store)
}
catch {
print(error)
}
}
private func updateStoreInfo(_ store: Store) {
if let documentId = store.id {
do {
try database.collection("Store")
.document(documentId)
.setData(from: store)
}
catch {
print(error)
}
}
}
private func removeStoreInfo() {
if let documentId = self.store.id {
database.collection("Store").document(documentId).delete { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
private func updateOrAddStoreInfo() {
if let _ = store.id {
self.updateStoreInfo(self.store)
}
else {
addStoreInfo()
}
}
private func uploadImage(image: UIImage, name: String) {
let storageRef = storage.reference().child("storeImages/\(name)")
let data = image.jpegData(compressionQuality: 0.1)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
// uploda data
if let data = data {
storageRef.putData(data, metadata: metadata) { (metadata, err) in
if let err = err {
print("err when uploading jpg\n\(err)")
}
if let metadata = metadata {
print("metadata: \(metadata)")
}
}
}
}
// MARK: - UI 핸들러
func handleDoneTapped() {
self.updateOrAddStoreInfo()
}
func handleDeleteTapped() {
self.removeStoreInfo()
}
}//StoreViewModel
StoresViewModel
Store 구조체의 정보를 모두 가져오는 ViewModel을 구분지어 따로 만들어준다.
StoreViewModel을 확인하면 firebase에 등록하는 함수만 있고 불러오는 함수는 없다.
firebase에서 데이터를 한번 가져오는 것 이외도 Cloud Firestore는 소위 스냅샷 리스너를 사용하여 앱에 업데이트를 제공하는 것도 지원한다. 컬렉션 or 쿼리에 스냅샷 리스너를 등록 할 수 있으며, Cloud Firesotre는 있을 때마다 리스너를 호출한다.
매핑된 문서를 포함하는 로컬 배열을 업데이트할 필요가 없다. X
스냅샷 수신기의 코드에서 처리되기 때문이다.
import Foundation
import UIKit
import Firebase
import FirebaseFirestore
import FirebaseStorage
// 스토어의 정보를 모두 가져오는 뷰모델
@MainActor
final class StoresViewModel: ObservableObject {
@Published var stores: [Store] = []
@Published var storeTitleImage: [String : UIImage] = [:]
private var database = Firestore.firestore()
private var storage = Storage.storage()
private var listenerRegistration: ListenerRegistration?
//Store정보 구독취소
//Store정보가 필요한 뷰에서
//.onDisappear { viewModel.unsubscribeStores() } 하면 실행됨
func unsubscribeStores() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
//Store정보 구독
//Store정보가 필요한 뷰에서
//.onAppear { viewModel.subscribeStores() } 하면 실행됨
func subscribeStores() {
if listenerRegistration == nil {
listenerRegistration = database.collection("Store")
.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("There are no documents")
return
}
//FirebaseFireStoreSwift 를 써서 @Document 프로퍼티를 썼더니 가능
self.stores = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: Store.self) }
switch result {
case .success(let store):
for imageName in store.storeImages {
Task.init{
do{
try await self.fetchImages(storeId: store.storeName, imageName: imageName)
}
catch{
}
}
}
return store
case .failure(let error):
print(#function, "\(error.localizedDescription)")
return nil
}
}
}
}
}
func fetchImages(storeId: String, imageName: String) async throws -> UIImage {
let ref = storage.reference().child("storeImages/\(storeId)/\(imageName)")
let data = try await ref.data(maxSize: 1 * 1024 * 1024)
let image = UIImage(data: data)
self.storeTitleImage[imageName] = image
return image!
}
}
마지막 View에서 ViewModel을 사용할 때
View에서 StateObject로 ViewModel을 불러오며 onAppear & onDisappear시 호출만 하면 된다.
.onAppear {
Task{
storesViewModel.subscribeStores()
}
}
.onDisappear {
storesViewModel.unsubscribeStores()
}
'iOS > Firebase' 카테고리의 다른 글
[SwiftUI] Firebase Storage 다중이미지 upload & retrieve, 별점 리뷰 글 posting (0) | 2023.01.29 |
---|---|
[SwiftUI] Firebase Storage Post Image upload & retrieve 단일이미지 (0) | 2022.12.18 |