circlecircle
원형
circlecircle
전체 방문자
오늘
어제
  • 분류 전체보기 (11)
    • Flutter (2)
      • Firebase (1)
    • iOS (9)
      • swiftUI (6)
      • UIKit (0)
      • Firebase (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Kingfisher
  • SDWebImage
  • storage
  • AppStorage
  • userdefaults
  • propertyWrapper
  • Combine
  • ios
  • ObservableObject
  • Cloud FireStore
  • SwiftUI
  • LazyVStack
  • SPM
  • EnvironmentObject
  • Firebase
  • Stack
  • 다중이미지
  • StateObject

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
circlecircle

원형

iOS/Firebase

[SwiftUI] Firebase Storage Post Image upload & retrieve 단일이미지

2022. 12. 18. 23:28

Firestore database는 1.5mb 이하의 데이터만 저장이 가능하기때문에 이미지를 업로드 하지 않고 일반 하드인 Storage에 upload해야 한다.

retrive시에는 Storage에 저장된 UUID로 생성한 post.id, image.id의 url을 불러와 database에 저장한다.


1) 구조체 초기화

import Foundation
import UIKit

struct Post: Hashable, Codable, Identifiable {
    var id: String
    var title: String
    var contents: String
    var likeCounts: Int
    var imageName: String?
    let createdAt: Double
    
    var imagePath: String {
        return "images/\(id)/\(imageName ?? "")"
    }
    
    var createdDate: String {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "ko_kr")
        dateFormatter.timeZone = TimeZone(abbreviation: "KST")
        dateFormatter.dateFormat = "yyyy-MM-dd" // "yyyy-MM-dd HH:mm:ss"
        
        let dateCreatedAt = Date(timeIntervalSince1970: createdAt)
        print(dateCreatedAt)
        print(Date().timeIntervalSince1970)
        return dateFormatter.string(from: dateCreatedAt)
    }
    
}

struct PostImage: Identifiable, Hashable {
    var id: String
    var image: UIImage
    var imgName: String
}

Post 작성시 들어갈 변수들을 생성해주며 Post별 이미지를 첨부하지 않을 수도 있기에 nil 값 처리하며 

주의점으로 database에는 Storage의 imagePath를 넣어준다. 

 

그리고 각 post별로 생성기준으로 정렬하기위해 DateFormatter를 선언한다.


2) Post 작성

    @MainActor
    func addPost(_ post: Post, _ postImage: PostImage) async {
        
        self.postImage.append(postImage)
        do {
            uploadImage(postId: post.id, image: postImage.image, name: post.imageName ?? "")
            //        print("post.imagePath : \(post.imagePath)")
            
            try await database.collection("Post")
                .document(post.id)
                .setData([
                    "title": post.title,
                    "contents": post.contents,
                    "likeCounts": post.likeCounts,
                    "imagePath": post.imagePath,
                    "createdAt": post.createdAt
                ])
            
            fetchPost()
        } catch {
            print("\(error.localizedDescription)")
        }
        
    }

업로드 함수(fetchPost)를 만들고 함수 내에서 비동기 처리로 Post collection에 업로드할 data들을 생성한다.

이미지를 Storage에 저장하는 uploadImage함수는 image에 사진 데이터, name에는 사진 id를 넘겨준다.


 

3) Image 업로드

   private func uploadImage(postId: String, image: UIImage, name: String)  {
        let storageRef = storage.reference().child("images/\(postId)/\(name)")
        let data = image.jpegData(compressionQuality: 0.1)
        let metadata = StorageMetadata()
        metadata.contentType = "image/jpg"
        
        
        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 {
                }
            }
        }
    }

4) 게시글 불러오기: fetchPost

    func fetchPost() {
        self.posts.removeAll()
        self.uiImg.removeAll()
        database.collection("Post")
            .order(by: "createdAt", descending: true)
            .getDocuments { [self] (snapshot, error) in
            if let snapshot {
                for document in snapshot.documents {
                    
                    let id: String = document.documentID
                    
                    let docData = document.data()
                    let title: String = docData["title"] as? String ?? ""
                    let contents: String = docData["contents"] as? String ?? ""
                    let likeCounts: Int = docData["likeCounts"] as? Int ?? 0
                    let imagePath: String = docData["imagePath"] as? String ?? ""
                    
                    let createdAt: Double = docData["createdAt"] as? Double ?? 0.0
                    
                    let post = Post(id: id, title: title, contents: contents, likeCounts: likeCounts, imageName: imagePath, createdAt: createdAt)
                    
                    self.fetchImage(post: post)
                    self.posts.append(post)
                    
                }
            }
        }
    }

ObservableObject로 구현된 ViewModel이므로 fetch시 document에 data를 할당하는 것이 목적이 된다.


5) Image를 Storage에서 fetch하기

    func fetchImage(post: Post) {
        let ref = storage.reference(forURL: "gs://mzdoll-920ea.appspot.com/\(String(describing: post.imageName!))")
        
        
        ref.getData(maxSize: 20 * 1024 * 1024) { data, error in
            if let error = error {
                print("error while downloading image\n\(error.localizedDescription)")
                return
            } else {
                let image = UIImage(data: data!)
                
                self.uiImg.append(image!)
            }
        }
        
    }

6) 게시물 삭제하기: deletePost

 func deletePost(post: Post) {
        
        // remove from firebase db (firestore)
        database.collection("Post").document(post.id).delete() { err in
            if let err = err {
                print("Error removing document: \(err.localizedDescription)")
            } else {
                print("Document successfully removed!")
            }
        }
        
        // remove photos from storage
        let imagesRef = storage.reference().child("images/\(post.id)")
        imagesRef.delete { error in
          if let error = error {
            print("Error removing image from storage\n\(error.localizedDescription)")
          } else {
            print("images directory deleted successfully")
          }
        }

        // remove from posts list
        let index = posts.firstIndex { currentPost in
            return currentPost.id == post.id
        } ?? 0
            
            // remove post from posts variable
        posts.remove(at: index)
    }

uploadImage, fetchPost과 같이 reference 객체를 만든다. database의 document 참조 객체를 만들어서 delete() 함수를 이용하면 참조하고 있는 데이터가 삭제된다. Storage에는 이미지 data가 저장되어 있고 Post 객체의 images에는 UUID로 저장된 post.id, images.id의 url경로가 저장되어 있으므로 참조 객체를 만들고 이미지도 삭제한다. ViewModel 내부의 posts의 해당 객체를 제거해 주면 끝이다. 


View: PhotoPicker & 

 PhotosPicker(selection: $selectedItems,  matching: .any(of: [.images, .not(.videos)])) {
                    Label("Pick Photo", systemImage: "photo.artframe")
                }
                .onChange(of: selectedItems) { newValues in
                    Task {
                        selectedImages = []
                        for value in newValues {
                            if let imageData = try? await value.loadTransferable(type: Data.self), let image = UIImage(data: imageData) {
                                selectedImages.append(image)
                                photosName = image
                                
                            }
                        }
                    }
                }
                TextField("제목", text: $title)
                TextField("내용", text: $content)
                Button {
                    let id = UUID().uuidString
                    let photoName = UUID().uuidString
                    
                    let post = Post(id: id, title: title, contents: content, likeCounts: 0, imageName: photoName, createdAt: Date().timeIntervalSince1970)
                    let postImage = PostImage(id: id, image: photosName, imgName: photoName)
                    
                    Task {
                        await postStore.addPost(post, postImage)
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                            isPresented.toggle()
                        }
                    }
                    
                } label: {
                    Text("등록")
                }

            }
        }

PhotoPicker를 사용해 다중 이미지를 선택할 수 있고 

'등록' 버튼을 누를시 firebase에 저장된 이미지 경로에서 Storage data를 불러오는 시간차이를 두기 위해 1sec delay 준다.

'iOS > Firebase' 카테고리의 다른 글

[SwiftUI] Firebase ViewModel 'Combine' snapShot Listener(파이어베이스 스냅샷 리스너)  (1) 2023.02.05
[SwiftUI] Firebase Storage 다중이미지 upload & retrieve, 별점 리뷰 글 posting  (0) 2023.01.29
    'iOS/Firebase' 카테고리의 다른 글
    • [SwiftUI] Firebase ViewModel 'Combine' snapShot Listener(파이어베이스 스냅샷 리스너)
    • [SwiftUI] Firebase Storage 다중이미지 upload & retrieve, 별점 리뷰 글 posting
    circlecircle
    circlecircle

    티스토리툴바