포스트

Final (9)

내가쓴 리뷰를 확인 할 수 있는 기능 구현

이건 그냥 uid에 해당하는 documents만 가져오면 된다.

UserReview의 컬렉션에서 field가 uid에서 유져의 값과 일치하는 녀석만 가져오게 하면된다.

1. userManager 작성

1
2
3
func getMyReview(uid: String, completion: @escaping(QuerySnapshot?, (Error)?) -> Void) {
        reviewCollection.whereField("uid", isEqualTo: uid).order(by: "createdAt").getDocuments(completion: completion)
    }

함수는 다음과 같이 구현.

2. VM 작성

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
func getUserReview(uid: String) {
        userManager.getMyReview(uid: uid) { [weak self] querySnapshot, error in
            if let error = error {
                self?.reviewPublisher.send(completion: .failure(error))
            }
            
            if let snapshotDocuments = querySnapshot?.documents {
                if !snapshotDocuments.isEmpty {
                    for doc in snapshotDocuments {
                        let data = doc.data()
                        guard
                            let uid = data["uid"] as? String,
                            let title = data["title"] as? String,
                            let storeAddress = data["storeAddress"] as? String,
                            let content = data["content"] as? String,
                            let rating = data["rating"] as? Float,
                            let imageURL = data["imageURL"] as? [String],
                            let isActivate = data["isActivate"] as? Bool,
                            let createdAt = data["createdAt"] as? Timestamp,
                            let updatedAt = data["updatedAt"] as? Timestamp
                        else {
                            return
                        }
                        let reviewData = ReviewModel(uid: uid, title: title, storeAddress: storeAddress, content: content, rating: rating, imageURL: imageURL, isActivate: isActivate, createdAt: createdAt, updatedAt: updatedAt)
                        self?.userReview.append(reviewData)
                        self?.reviewPublisher.send(())
                    }
                }
            }
        }
    }

3. VC 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private func getData() {
        viewModel.getUserReview()
    }
    
    private func bind() {
        viewModel.$userReview
            .print()
            .sink { _ in
            self.collectionView.reloadData()
        }.store(in: &cancellables)
        
        viewModel.reviewPublisher.sink { completion in
            switch completion {
            case .finished:
                return
            case .failure(let error):
                print(error)
            }
        } receiveValue: { _ in
            print("received")
        }.store(in: &cancellables)

    }

하지만 에러가 발생

뭐가 문제일까 해서 보니 updatedAt을 이전에 사용하지 않아서 생긴 문제

즉 guard문에서 return되면서 종료되었던것

그래서 필드를 추가해주고 글을 작성할때도 updatedAt을 넣게 해주었다.

image

성공.

리뷰 수정 페이지 구현.

작성과 수정을 같이 하기위해서, 굳이 또 VC를 만들어야하나에 대한 의구심이 생겼다.

var isEditMode: Bool = false 이녀석을 만들어서

새로작성할때는 false 유지,

수정할때만 true로 값을 넘겨서 하려고한다.

1
2
3
4
5
6
7
8
9
10
@objc func submitButtonTapped() {
        
        if isEditMode == false {
            writeReview()
        } else {
            editReview()
        }
        
       
    }

1. 데이터를 전달하기 위한 코드 수정

아무래도 indexPath가 필요하여 프로토콜부분과 관련된 모든 부분이 고쳐져야할 필요성을 느꼈다.

우선 프로토콜에 indexpath를 추가한다.

1
2
3
4
protocol ReviewCellDelegate: AnyObject {
    func editReview(_ review: ReviewModel, indexPath: IndexPath)
    func deleteReview(_ review: ReviewModel, indexPath: IndexPath)
}

관련 함수 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension MyReviewViewController: ReviewCellDelegate {
    func editReview(_ review: ReviewModel, indexPath: IndexPath) {
        let writeVC = WriteViewController()
        let item = viewModel.userReview[indexPath.row]
        writeVC.isEditMode = true
        writeVC.isNavagtion = true
        writeVC.review = item
        
        navigationController?.pushViewController(writeVC, animated: true)
    }
    
    func deleteReview(_ review: ReviewModel, indexPath: IndexPath) {
        let item = viewModel.userReview[indexPath.row]
        print(item)
    }
}

데이터를 ui로 전달하는 함수도 만들고, Kingfisher를 사용하여 이미지를 가져오는것도 했다.

Kingfisher가 이미지를 uiimage로 바꿀수 있는건 첨알았다.

보통은 이미지 뷰에서 url을 가져와서 바로 적용을 했지 해당 메서드 사용은 처음이다.

출처

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
private func setDataForEdit() {
        if review != nil {
            titleTextField.text = review?.title
            contentTextView.text = review?.content
            selectedRating = Int(review!.rating)
            updateStarButtons()
            getImages()
        }
    }
    
private func getImages() {
        review?.imageURL.forEach { url in
            guard let imageURL = URL(string: url) else { return }
            KingfisherManager.shared.retrieveImage(with: imageURL) { result in
                switch result {
                case .success(let image):
                    DispatchQueue.main.async {
                        self.addImageToStackView(image: image.image)
                        self.selectedImages.append(image.image)
                    }
                case .failure(let error):
                    print("Error: \(error)")
                }
            }
        }
    }

Jun-08-2024 23-59-39

완료.

Document 수정 기능 구현

이제 메인기능이 남았다.

UserManager에는 다음과 같이 구현했다.

1
2
3
func editReview(uid: String, storeAddress: String, title: String, completion: @escaping(QuerySnapshot?, (Error)?) -> Void) {
        reviewCollection.whereField("uid", isEqualTo: uid).whereField("storeAddress", arrayContains: storeAddress).whereField("title", arrayContains: title).getDocuments(completion: completion)
    }

유져의 uid, 가게주소, 작성한 제목을 바탕으로 수정하는 작업을 하게 된다.

우선 VM은 다음과 같이 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func editUserReview(uid: String, storeAddress: String, title: String, userDict: [String: Any]) {
        userManager.editReview(uid: uid, storeAddress: storeAddress, title: title) { [weak self ] querySnapshot, error in
            if let error = error {
                self?.reviewPublisher.send(completion: .failure(error))
            }
            
            if let documents = querySnapshot?.documents {
                for doc in documents {
                    let id = doc.documentID
                    reviewCollection.document(id).setData(userDict, merge: true)
                }
            }
        }
    }

VC 수정

isEditMode를 기준으로 삼항연산자를 사용하여 다음과 같이 구현

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
46
47
48
49
50
51
52
53
54
55
private func reviewTapped() {
        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": self.addressText!,
                    "storeName": self.storeTitleText!,
                    "content": content,
                    "rating": self.selectedRating,
                    "imageURL": imageURLs,
                    "isActive": false,
                    "createdAt": self.isEditMode ? self.review!.createdAt : Timestamp(date: Date()),
                    "updatedAt": Timestamp(date: Date())
                ]
                
                if self.isEditMode {
                    self.viewModel.editUserReview(uid: uid, storeAddress: self.addressText!, title: self.storeTitleText!, userDict: dictionary)
                } else {
                    self.viewModel.createReview(userDict: dictionary)
                }
            })
            .store(in: &cancellables)
        
        let alertTitle = isEditMode ? "리뷰 수정" : "리뷰 저장"
        let alertMessage = isEditMode ? "리뷰가 수정 되었습니다." : "리뷰가 등록 되었습니다."
        let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "확인", style: .default, handler: { [unowned self] _ in
            if self.isNavagtion {
                self.navigationController?.popViewController(animated: true)
            } else {
                self.dismiss(animated: true, completion: nil)
            }
        }))
        present(alert, animated: true)
    }

문제점 확인.

옵셔널 문제가 발생해서 생각해보니, addressText, storeTitleText를 넘기지 않은게 원인이다.

1
2
3
4
5
6
7
8
9
10
11
private func setDataForEdit() {
        if review != nil {
            titleTextField.text = review?.title
            contentTextView.text = review?.content
            selectedRating = Int(review!.rating)
            addressText = review?.storeAddress
            storeTitleText = review?.storeName
            updateStarButtons()
            getImages()
        }
    }

하지만 에러가 발생

1
Error Domain=FIRFirestoreErrorDomain Code=3 "A maximum of 1 'ARRAY_CONTAINS' filter is allowed per disjunction." UserInfo={NSLocalizedDescription=A maximum of 1 'ARRAY_CONTAINS' filter is allowed per disjunction.}

어디가 문제있나 봤더니 자동완성으로 생긴 문제로 보인다.

1
2
3
func editReview(uid: String, storeAddress: String, title: String, completion: @escaping(QuerySnapshot?, (Error)?) -> Void) {
        reviewCollection.whereField("uid", isEqualTo: uid).whereField("storeAddress", isEqualTo: storeAddress).whereField("title", isEqualTo: title).getDocuments(completion: completion)
    }

isEqualToarrayContains이걸로 되어있었다.

수정이 되지않아 뭐가 문제일까 봤더니

self.viewModel.editUserReview(uid: uid, storeAddress: self.addressText!, title: title, userDict: dictionary)

여기 title이 가게명으로 되어있었다.

또한 documents를 못찾는 문제가 발생했다.

생각해보니 title을 바꾸면서 쿼리를 못찾는것같다.

1
viewModel.editUserReview(uid: uid, storeAddress: self.addressText!, title: review!.title, userDict: dictionary)

title을 변경된 값이 아닌 기존에 들고온 리뷰 제목을 해야 그에 맞는게 수정이 된다.

너무 안일하게 생각해서 생긴 문제.

simulator_screenshot_E0BB79FB-61EA-4C61-A7B4-4FA2BB7001A5

수정은 되었으나 너무 빨리 dismiss가 되어 서버에 변경되기전 VC가 이미 호출되어 리뷰가 이전께 보여지는 문제가 발생.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ProgressHUD.animate()
        let alertTitle = isEditMode ? "리뷰 수정" : "리뷰 저장"
        let alertMessage = isEditMode ? "리뷰가 수정 되었습니다." : "리뷰가 등록 되었습니다."
        let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
        DispatchQueue.main.asyncAfter(deadline: .now() + 4) { [unowned self] in
            alert.addAction(UIAlertAction(title: "확인", style: .default, handler: { [unowned self] _ in
                if isNavagtion {
                    navigationController?.popViewController(animated: true)
                } else {
                    dismiss(animated: true, completion: nil)
                }
            }))
            ProgressHUD.remove()
            present(alert, animated: true)
        }

임시방편으로 약간의 로딩시간을 주었다.

그리고 vc재 호출시 수정이 안되는거같아서 확인했보니 append로 계속 누적이 되는걸 확인

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
func getUserReview() {
        guard let uid = Auth.auth().currentUser?.uid else { return }
        
        userManager.getMyReview(uid: uid) { [weak self] querySnapshot, error in
            self?.userReview.removeAll() // added
            if let error = error {
                self?.reviewPublisher.send(completion: .failure(error))
            }
            
            if let snapshotDocuments = querySnapshot?.documents {
                if !snapshotDocuments.isEmpty {
                    for doc in snapshotDocuments {
                        let data = doc.data()
                        guard
                            let uid = data["uid"] as? String,
                            let title = data["title"] as? String,
                            let storeName = data["storeName"] as? String,
                            let storeAddress = data["storeAddress"] as? String,
                            let content = data["content"] as? String,
                            let rating = data["rating"] as? Float,
                            let imageURL = data["imageURL"] as? [String],
                            let isActive = data["isActive"] as? Bool,
                            let createdAt = data["createdAt"] as? Timestamp,
                            let updatedAt = data["updatedAt"] as? Timestamp
                        else {
                            print("error")
                            return
                        }
                        let reviewData = ReviewModel(uid: uid, title: title, storeAddress: storeAddress, storeName: storeName, content: content, rating: rating, imageURL: imageURL, isActive: isActive, createdAt: createdAt, updatedAt: updatedAt)
                        self?.userReview.append(reviewData)
                        self?.reviewPublisher.send(())
                    }
                }
            }
        }
    }

이렇게 로드하기전 배열을 비우기로했다.

Jun-09-2024 02-22-58

Document 삭제 기능 구현

이건 크게 어려운 문제가 아니다.

1. UserManager작성

1
2
3
func getSpecificReview(uid: String, storeAddress: String, title: String, completion: @escaping(QuerySnapshot?, (Error)?) -> Void) {
        reviewCollection.whereField("uid", isEqualTo: uid).whereField("storeAddress", isEqualTo: storeAddress).whereField("title", isEqualTo: title).getDocuments(completion: completion)
    }

생각해보니 특정 필드에 대한 값을 가져와서 삭제하므로 이름을 변경해주었다.

2. VM 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func removeUserReview(uid: String, storeAddress: String, title: String) {
        userManager.getSpecificReview(uid: uid, storeAddress: storeAddress, title: title) { [weak self ] querySnapshot, error in
            if let error = error {
                self?.reviewPublisher.send(completion: .failure(error))
            }
            
            if let documents = querySnapshot?.documents {
              
                for doc in documents {
                    let id = doc.documentID
                    reviewCollection.document(id).delete()
                }
            }
        }
    }

그냥 delete만 쳐주면 된다.

3. VC 작성

1
2
3
4
5
6
7
8
9
10
11
12
func deleteReview(_ review: ReviewModel, indexPath: IndexPath) {
        let item = viewModel.userReview[indexPath.row]
        let alert = UIAlertController(title: "삭제 확인", message: "삭제하시면 복원 할 수 없습니다.", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "확인", style: .destructive, handler: { [unowned self] _ in
            viewModel.removeUserReview(uid: item.uid, storeAddress: item.storeAddress, title: item.title)
            
            getData()
            bind()
        }))
        alert.addAction(UIAlertAction(title: "취소", style: .default))
        present(alert, animated: true)
    }

우선 삭제는 되나 collectionview에 바로 보이지는 않는다.

아무래도 DiffableDatasource를 사용해야할듯싶다.

그건 내일 하는걸로…

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