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을 넣게 해주었다.
성공.
리뷰 수정 페이지 구현.
작성과 수정을 같이 하기위해서, 굳이 또 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)")
}
}
}
}
완료.
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)
}
isEqualTo
가 arrayContains
이걸로 되어있었다.
수정이 되지않아 뭐가 문제일까 봤더니
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을 변경된 값이 아닌 기존에 들고온 리뷰 제목을 해야 그에 맞는게 수정이 된다.
너무 안일하게 생각해서 생긴 문제.
수정은 되었으나 너무 빨리 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(())
}
}
}
}
}
이렇게 로드하기전 배열을 비우기로했다.
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를 사용해야할듯싶다.
그건 내일 하는걸로…