본문 바로가기

프로젝트/Twitter Clone App (w∕Firebase)

24. 기능 구현 #10

기능 구현 #10
좋아요 취소(unLike) 기능 추가하기


좋아요 취소(unLike) 기능 추가하기
| TweetService, unLikeTweet

이미 좋아요가 된 글을 취소할 수 있는 기능이 있어야 한다.
Tweet에 관련된 기능인 TweetService LikeTweet, uploadTweet, fetchTweets(), fetchTweets(foruid:)
모두 TweetService에 정의돼 있으므로 unLikeTweet도 해당 파일에 구현하면 된다.

likeTweet

func likeTweet(_ tweet: Tweet, completion: @escaping() -> Void) {
    guard let uid = Auth.auth().currentUser?.uid else {
        return
    }
    guard let tweetId = tweet.id else {
        return
    }

    let userLikesRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")

    Firestore.firestore().collection("tweets").document(tweetId).updateData(["likes": tweet.likes + 1]) {_ in
        userLikesRef.document(tweetId).setData([:]) { _ in
            completion()
        }
    }
}

unLikeTweet

func unlikeTweet(_ tweet: Tweet, completion: @escaping() -> Void) {
    guard let uid = Auth.auth().currentUser?.uid else {
        return
    }
    guard let tweetId = tweet.id else {
        return
    }
    guard tweet.likes >= 0 else {
        return
    }

    let userLikesRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")


    Firestore.firestore().collection("tweets").document(tweetId).updateData(["likes": tweet.likes - 1]) { _ in
        userLikesRef.document(tweetId).delete() { _ in
            completion()
        }
    }
}

구현 자체는 likeTweet 메서드와 동일하다.
다만 좋아요 카운트에 해당하는 likes 속성의 크기를 확인해 -1이 되지 않도록 조건문만 하다 더 추가한다.

좋아요 취소(unLike) 기능 추가하기
| TweetService, checkingLike

좋아요 기능과 취소 기능은 같은 위치의 UI만 다르게 표시되도록 구현돼 있다.
조건에 따라 버튼 자체를 교대해 표시할 수도 있지만 그리 현명한 방식은 아니다.

func likeTweet(_ tweet: Tweet, completion: @escaping() -> Void) {
    guard let uid = Auth.auth().currentUser?.uid else {
        return
    }
    guard let tweetId = tweet.id else {
        return
    }

    let userLikesRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")

    Firestore.firestore().collection("tweets").document(tweetId).updateData(["likes": tweet.likes + 1]) {_ in
        userLikesRef.document(tweetId).setData([:]) { _ in
            completion()
        }
    }
}

좋아요 기능에 해당하는 likeTweet 메서드는 해당 Tweet의 likes 속성을 증가시키는 역할도 하지만
좋아요를 누른 계정의 하위 collection인 user-likes에 해당 Tweet의 id를 저장하는 기능도 한다.

func checkingLike(_ tweet: Tweet, completion: @escaping(Bool) -> Void) {
    guard let uid = Auth.auth().currentUser?.uid else {
        return
    }
    guard let tweetId = tweet.id else {
        return
    }

    Firestore.firestore().collection("users").document(uid).collection("user-likes").document(tweetId).getDocument { snapshot, _ in
        guard let snapshot = snapshot else {
            return
        }

        completion(snapshot.exists)
    }
}

따라서 지금 좋아요를 누르고 있는 계정의 하위 colelction인 user-likes를 확인하면
해당 Tweet이 이미 좋아요를 누른 상태인지, 아닌지를 판단할 수 있게 된다.
user-likes에 존재한다면 snapshot을 바인딩하고, 존재하지 않는다면 무시한다.
이후 exists를 사용해 nil인지, 데이터를 가지고 있는지를 boolean으로 반환하게 된다.
이미 좋아요를 누른 경우 binding을 성공해 true를, 누르지 않는 경우 binding을 실패해 false를 completion에 전달하게 된다.

좋아요 취소(unLike) 기능 추가하기
| TweetRowViewModel

class TweetRowViewModel: ObservableObject {
    private let service = TweetService()
    @Published var tweet: Tweet

    init(tweet: Tweet) {
        self.tweet = tweet
        checkingLike()
    }

    func likeTweet() {
        service.likeTweet(tweet) {
            self.tweet.didLike = true
        }
    }

    func unlikeTweet() {
        service.unlikeTweet(tweet) {
            self.tweet.didLike = false
        }
    }

    func checkingLike() {
        service.checkingLike(tweet) { didlike in
            if didlike {
                self.tweet.didLike = true
            }
        }
    }
}

구현한 두 가지의 메서드를 TweetRowView에서 사용할 수 있도록 재정의한다.
checkingLike 메서드의 경우 Completion으로 전달되는 boolean을 바인딩해 이전의 likeTweet 기능이 담당했던
초기화 시점의 인디케이터 활성화를 진행한다.

좋아요 취소(unLike) 기능 추가하기
| TweetRowView

before

Button {
    viewModel.likeTweet()
} label: {
    Image(systemName: viewModel.tweet.didLike ?? false ? "heart.fill" : "heart")
        .foregroundColor(viewModel.tweet.didLike ?? false ? .red : .gray)
}

after

Button {
    viewModel.tweet.didLike ?? false ? viewModel.unlikeTweet() : viewModel.likeTweet()
} label: {
    Image(systemName: viewModel.tweet.didLike ?? false ? "heart.fill" : "heart")
        .foregroundColor(viewModel.tweet.didLike ?? false ? .red : .gray)
}

TweetRowView는 likeTweet 메서드를 항상 실행하는 대신
해당 Tweet이 이미 좋아요를 진행한 Tweet인지를 확인하고, unLikeTweet과 likeTweet을 호출한다.

결과

'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글

26. 기본 UI 구현하기 #9  (0) 2023.01.20
25. DB와 연결하기 #4  (0) 2023.01.20
23. 기능 구현 #9  (0) 2023.01.20
22. 버그수정 #2  (0) 2023.01.18
21. DB와 연결하기 #3  (0) 2023.01.18