본문 바로가기

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

23. 기능 구현 #9

기능 구현 #9
좋아요 기능 추가하기


SNS에서 빠질 수 없는 좋아요 기능을 구현해 본다.

TweetRow의 하트 버튼을 누르면 붉은색으로 표시되고, 이를 모아서 볼 수도 있어야 한다.
기본적인 UI는 이미 존재하는 상태이므로 기능만 구현하면 된다.

좋아요 기능 추가하기
| Tweet model

import FirebaseFirestoreSwift
import Firebase

struct Tweet: Identifiable, Codable {
    @DocumentID var id: String?
    let caption: String
    let timestamp: Timestamp
    let uid: String
    var likes: Int

    var user: User?
    var didLike: Bool? = false
}

좋아요 기능을 위해 TweetModel에 새로운 속성을 하나 추가한다.
didLike 속성은 사용자가 좋아요를 한 상태인지 표시하는 데 사용된다.

좋아요 기능 추가하기
| TweetService

Tweet에 관련된 기능들은 TweetService에 정의된다.
이미 uploadTweet, fetchTweets, fetTweets(foruid:)를 구현했고, 새로운 메서드인 likeTweet을 추가한다.

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

현재 접속 중인 사용자의 유효성을 FirebaseQuth의 currentUser.uid 속성을 통해 판단하고,
좋아요를 진행할 Tweet을 판단하기 위해 파라미터로 전달된 Tweet의 id를 바인딩한다.

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")
}

uid를 사용해 해당 사용자의 collection으로 이동하여 user-likes라는 하위 collection을 하나 생성하거나 접근할 수 있도록 경로를 설정하고,

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()
        }
    }
}

해당 Tweet의 likes를 1 증가 시킴과 동시에 userLikeRef의 하위 Collection 경로로 이동해
좋아요를 진행한 tweetId를 저장하고, 완료되면 CompletionHandler를 호출한다.

좋아요 기능 추가하기
| TweetRowViewModel

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

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

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

TweetRowView에 표시할 데이터를 저장할 TweetRowViewModel을 정의한다.
likeTweet에 좋아요를 진행할 tweet을 전달하고, 동작이 완료되면 Tweet의 좋아요 상태를 갱신한다.

좋아요 기능 추가하기
| TweetRowView

HStack {
    Button {
        // action
    } label: {
        Image(systemName: "bubble.left")
    }

    Spacer()

    Button {
        // action
    } label: {
        Image(systemName: "arrow.2.squarepath")
    }

    Spacer()

    Button {
        // action
    } label: {
        Image(systemName: "heart")
    }

    Spacer()

    Button {
        // action
    } label: {
        Image(systemName: "bookmark")
    }
}
.font(.subheadline)
.padding()
.foregroundColor(.gray)

TweetRowView의 아래에는 각각의 기능에 대한 버튼들이 존재한다.
그중 'heart'를 표시하는 세 번째 버튼이 좋아요 기능을 담당하게 된다.

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

Button은 TweetRowViewModel의 likeTweet 메서드를 호출하고,
표시하려는 Tweet의 didLike 속성에 따라 빈 하트나 빨간색 하트를 표시한다.

더보기

Source

import SwiftUI
import Kingfisher

struct TweetRowView: View {
    @ObservedObject var viewModel: TweetRowViewModel

    init(tweet: Tweet) {
        self.viewModel = TweetRowViewModel(tweet: tweet)
    }


    var body: some View {
        VStack(alignment: .leading) {
            //profile image & user info & tweet
            if let user = viewModel.tweet.user {
                HStack(alignment: .top, spacing: 12) {
                    KFImage(URL(string: user.profileImageUrl))
                        .resizable()
                        .scaledToFill()
                        .clipShape(Circle())
                        .frame(width: 56, height: 56)

                    //user info & tweet caption
                    VStack(alignment: .leading, spacing: 4) {
                        //user info
                        HStack {
                            Text(user.fullname)
                                .font(.subheadline).bold()

                            Group {
                                Text("@\(user.username)")

                                Text("2d")
                            }
                            .foregroundColor(.gray)
                            .font(.caption)
                        }

                        //tweet caption
                        Text(viewModel.tweet.caption)
                            .font(.subheadline)
                            .multilineTextAlignment(.leading)
                    }
                }
            }

            //action buttons
            HStack {
                Button {
                    // action
                } label: {
                    Image(systemName: "bubble.left")
                }

                Spacer()

                Button {
                    // action
                } label: {
                    Image(systemName: "arrow.2.squarepath")
                }

                Spacer()

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

                Spacer()

                Button {
                    // action
                } label: {
                    Image(systemName: "bookmark")
                }
            }
            .font(.subheadline)
            .padding()
            .foregroundColor(.gray)

            Divider()
        }
    }
}

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

25. DB와 연결하기 #4  (0) 2023.01.20
24. 기능 구현 #10  (0) 2023.01.20
22. 버그수정 #2  (0) 2023.01.18
21. DB와 연결하기 #3  (0) 2023.01.18
20. 기능 구현 #8  (0) 2023.01.17