DB와 연결하기 #4
좋아요 한 Tweet 표시하기
ProfileView의 Likes 탭에는 해당 계정이 작성했던 Tweet도, 답변을 달았던 Tweet도 아닌 좋아요를 눌렀던 Tweet을 표시해야 한다.
이전에 구현한 좋아요 기능에서 사용하는 user-likes 하위 collection 활용하면 굉장히 간단하게 구현할 수 있다.
좋아요 한 Tweet 표시하기
| TweetService, fetchLikedTweets
fetchTweets(foruid:)
func fetchTweets(foruid uid: String, completion: @escaping([Tweet]) -> Void) {
Firestore.firestore().collection("tweets").whereField("uid", isEqualTo: uid).getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {
return
}
let tweets = documents.compactMap({
try? $0.data(as: Tweet.self)
})
completion(tweets.sorted(by: { $0.timestamp.dateValue() > $1.timestamp.dateValue() }))
}
}
메서드의 원형은 앞서 구현한 fetTweets다.
해당 메서드는 uid를 사용해 Firebase의 일치하는 Tweet을 받아오게 되는데,
비슷한 방식으로 필터링 규칙만 조금 달라질 뿐이다.
func fetchLikedTweets(forUid uid: String, completion: @escaping([Tweet]) -> Void) {
var tweets = [Tweet]()
Firestore.firestore().collection("users").document(uid).collection("user-likes").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {
return
}
}
}
우선 profileView에 표시되고 있는 사용자의 user-likes 하위 collection을 전부 가져온다.
users collection에서 일치하는 uid의 Document를 찾고,
해당 Document 내에서 다시 한번 user-likes 하위 collection에 접근한다.
이후 user-likes에 저장된 모든 Documents를 받아와
snapshot으로 바인딩 한 뒤, 내용을 documents로 다시 한번 바인딩한다.
func fetchLikedTweets(forUid uid: String, completion: @escaping([Tweet]) -> Void) {
var tweets = [Tweet]()
Firestore.firestore().collection("users").document(uid).collection("user-likes").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {
return
}
documents.forEach { document in
let tweetID = document.documentID
Firestore.firestore().collection("tweets").document(tweetID).getDocument { snapshot, _ in
guard let tweet = try? snapshot?.data(as: Tweet.self) else {
return
}
tweets.append(tweet)
completion(tweets)
}
}
}
}
바인딩이 성공한 경우 documents를 열거하며 해당하는 Tweet을 Tweet 구조체로 변환한 뒤,
tweets 배열에 담아 completion에 전달한다.
좋아요 한 Tweet 표시하기
| ProfileViewModel
ProfileView에 표시할 데이터를 만들기 위해 ProfileViewModel을 수정한다.
class ProfileViewModel: ObservableObject {
@Published var tweets = [Tweet]()
@Published var likedTweets = [Tweet]()
private let service = TweetService()
let user: User
.
.
.
현재 ProfileView에 표시되고 있는 계정의 Tweet들은 tweets 배열에 저장되므로,
좋아요 한 Tweet을 저장하기 위해 likedTweets 배열을 새로 선언해 준다.
func fetchLikedTweets() {
guard let uid = user.id else {
return
}
service.fetchLikedTweets(forUid: uid) { tweets in
self.likedTweets = tweets
for i in 0 ..< tweets.count {
let uid = tweets[i].uid
self.userService.fetchUser(withUid: uid) { user in
self.likedTweets[i].user = user
}
}
}
}
새로 구현한 fetchLikedTweets를 재정의 해 준다.
해당 메서드는 좋아요 한 Tweet들의 배열을 반환하므로
completionHandler에서는 Tweet 표시에 필요한 계정 정보를 채워 새로 선언한 likedTweets 배열에 그대로 저장해 주면 된다.
init(user: User) {
self.user = user
self.fetchUserTweets()
self.fetchLikedTweets()
}
ProfileViewModel은 ProfileView가 처음 화면에 표시되는 시점에 호출된다.
따라서 생성자에 방금 재정의한 fetchLikedTweets를 호출해 likedTweets 배열에 데이터를 채울 수 있도록 한다.
좋아요 한 Tweet 표시하기
| ProfileView와 ProfileViewModel의 연동
ProfileView에는 TweetsFilterBar가 존재한다.
ProfileViewModel에는 해당하는 View에 알맞게 표시될 수 있도록 배열을 가지고 있는데,
이 둘을 연결하는 작업이 필요하다.
TweetView > tweetFilterBar
var tweetFilterBar: some View {
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectedFilter == item ? .semibold : .regular)
.foregroundColor(selectedFilter == item ? .black : .gray)
if selectedFilter == item {
Capsule()
.foregroundColor(Color(.systemBlue))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
} else {
Capsule()
.foregroundColor(Color(.clear))
.frame(height: 3)
}
}
.onTapGesture {
withAnimation(.easeOut) {
self.selectedFilter = item
}
}
}
}
.overlay {
Divider().offset(x: 0, y: 16)
}
TweetFilterBar는 TweetFilterViewModel을 열거하여 표시되고 있고,
선택된 tab은 selectedFilter에 저장돼 tab 간 전환이 동작한다.
TweetFilterViewModel
enum TweetFilterViewModel: Int, CaseIterable {
case tweets
case replies
case likes
var title: String {
switch self {
case .tweets: return "Tweets"
case .replies: return "Replies"
case .likes: return "Likes"
}
}
}
TweetFilterViewModel은 위와 같이 case로 각각의 tab을 구별하고 있다.
따라서 현재 선택한 tab이 사용하고 있는 TweetFilterViewModel의 case를 지표로 해당하는 배열을 표시하기만 하면 된다.
func tweets(forFilter filter: TweetFilterViewModel) -> [Tweet] {
switch filter {
case .tweets:
return tweets
case .replies:
return tweets
case .likes:
return likedTweets
}
}
forFilter 파라미터로 현재 선택된 tab의 TweetFilterViewModel의 case를 받아 분기하여 해당하는 배열을 반환한다.
ProfileView > tweetsView
before
var tweetsView: some View {
ScrollView {
LazyVStack {
ForEach(viewModel.tweets) { tweet in
TweetRowView(tweet: tweet)
.padding()
}
}
}
}
ProfileView > tweetsView
after
var tweetsView: some View {
ScrollView {
LazyVStack {
ForEach(viewModel.tweets(forFilter: self.selectedFilter)) { tweet in
TweetRowView(tweet: tweet)
.padding()
}
}
}
}
앞서 구현한 tweets(forFilter:)를 호출하고,
현재 선택된 tab을 전달하기 위해 selectedFilter를 그대로 전달하기만 하면 된다.
반환되는 배열은 completionHandler에서 TweetRowView에 전달해 표시하기만 하면 된다.
결과
'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글
26. 기본 UI 구현하기 #9 (0) | 2023.01.20 |
---|---|
24. 기능 구현 #10 (0) | 2023.01.20 |
23. 기능 구현 #9 (0) | 2023.01.20 |
22. 버그수정 #2 (0) | 2023.01.18 |
21. DB와 연결하기 #3 (0) | 2023.01.18 |