포스트

Final (8)

리뷰 작성글을 보여지게 구현

1. 모델링

이젠 Firebase의 모델링이 중요해진다.

부리더인 미림님이 구현한 VC에서 유져가 작성한 글을 올리는 걸 해보려한다.

물론 모델링의 회의도 같이 해보았다.

Collection은 UserReview로 하기로했다.

우선 모델링은 둘이서 회의를 하면서 진행했고 다음과 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ReviewModel {
    
    var uid: String
    var title: String
    var storeAddress: String
    var content: String
    var rating: Float
    var imageURL: [String]
    var isActivate: Bool
    var createdAt: Timestamp
    var updatedAt: Timestamp
    
}

2. UserManager에 구현

1
2
3
func writeReview(userDict: [String: Any], completion: (((Error)?) -> Void)?) {
        reviewCollection.addDocument(data: userDict, completion: completion)
    }

이렇게 적었다.

3. ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ReviewViewModel {
    
    private let userManager: UserManager
    
    var reviewPublisher = PassthroughSubject<Void, Error>()
    
    func createReview(userDict: [String: Any]) {
        userManager.writeReview(userDict: userDict) { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                self.reviewPublisher.send(completion: .failure(error))
            }
        }
    }
    
}

일단은 이렇게 넘기기로 결정

4. VC작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@objc func submitButtonTapped() {
        guard
            let uid = Auth.auth().currentUser?.uid,
            let title = titleTextField.text,
            let content = contentTextView.text
        else {
            return
        }
        
        uploadImages(images: selectedImages)
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    let alert = UIAlertController(title: "에러 발생", message: "\(error.localizedDescription)이 발생했습니다.", preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "확인", style: .default))
                    self.present(alert, animated: true)
                }
            }, receiveValue: { [weak self] imageURLs in
                guard let self = self else { return }
                
                let dictionary: [String: Any] = [
                    "uid": uid,
                    "title": title,
                    "storeAddress": addressText,
                    "storeName": storeTitleText,
                    "content": content,
                    "rating": selectedRating,
                    "imageURL": imageURLs,
                    "isActivate": false,
                    "createdAt": Timestamp(date: Date())
                ]
                
                self.viewModel.createReview(userDict: dictionary)
                
            })
            .store(in: &cancellables)
        
        let alert = UIAlertController(title: "리뷰 저장", message: "리뷰가 등록 되었습니다.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: { [unowned self] _ in
            dismiss(animated: true)
        }))
        present(alert, animated: true)
    }

이미지를 여러개 등록

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func uploadImage(image: UIImage) -> AnyPublisher<String, Error> {
        Future<String, Error> { promise in
            let storageRef = Storage.storage().reference()
            guard let imageData = image.jpegData(compressionQuality: 0.5) else {
                return
            }
            
            let imageRef = storageRef.child("images/\(UUID().uuidString).jpg")
            imageRef.putData(imageData, metadata: nil) { metadata, error in
                if let error = error {
                    promise(.failure(error))
                    return
                }
                
                imageRef.downloadURL { url, error in
                    if let error = error {
                        promise(.failure(error))
                    } else if let downloadURL = url {
                        promise(.success(downloadURL.absoluteString))
                    }
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    func uploadImages(images: [UIImage]) -> AnyPublisher<[String], Error> {
        let publishers = images.map { uploadImage(image: $0) }
        return Publishers.MergeMany(publishers)
            .collect()
            .eraseToAnyPublisher()
    }
  1. uploadImage
    • Future를 사용하여 이미지를 Firebase Storage에 업로드하고, 업로드가 완료되면 다운로드 URL을 반환하는 AnyPublisher<String, Error>를 생성
    • Future 초기화: Future는 비동기 작업의 결과를 promise를 통해 리턴
    • 이미지 데이터 변환: 이미지를 JPEG 데이터로 변환합니다. 실패 시 promise를 반환하지 않고 종료
    • Firebase Storage 참조 생성: Storage.storage().reference()를 사용하여 Firebase Storage 참조를 생성
    • 이미지 업로드: imageRef.putData(imageData, metadata: nil)를 사용하여 이미지를 업로드. 업로드가 완료되면 다운로드 URL을 요청
    • 결과 반환: 다운로드 URL을 성공적으로 가져오면 promise(.success(downloadURL.absoluteString))을 호출하여 URL을 반환. 에러 발생 시 promise(.failure(error))를 호출
  2. uploadImages
    • 여러 uploadImage 호출을 Combine의 Publishers.MergeMany와 collect를 사용하여 결합한 AnyPublisher<[String], Error>를 반환
    • map 사용: 각 이미지를 uploadImage 함수로 매핑하여 AnyPublisher<String, Error>의 배열을 생성
    • Publishers.MergeMany: 이 연산자는 배열의 모든 퍼블리셔를 결합하여 하나의 퍼블리셔로 만듦
    • 각 퍼블리셔의 출력은 하나의 스트림으로 합쳐지게 된다. - collect: 모든 퍼블리셔가 완료될 때까지 대기한 후, 각 퍼블리셔의 출력값을 배열로 저장

Jun-08-2024 03-53-34

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.