[SwiftUI] TwitterClone: Update Tweets

Junyoung Park·2022년 11월 18일
0

SwiftUI

목록 보기
110/136
post-thumbnail

🔴 Let's Build Twitter with SwiftUI (iOS 15, Xcode 13, Firebase, SwiftUI 3.0)

TwitterClone: Update Tweets

구현 목표

  • 트위터 피드 좋아요 업데이트

구현 태스크

  • 유저 별 좋아요 정보를 데이터베이스에 저장
  • 좋아요 증감 로직 구현
  • 유저의 좋아요 상황에 따라 UI 패치

핵심 코드

func likeTweet(_ tweet: TweetModel, completion: @escaping(Result<Bool, Error>) -> Void) {
        guard
            let uid = Auth.auth().currentUser?.uid,
            let tweetId = tweet.id else {
            completion(.failure(URLError(.badURL)))
            return
        }
        let userLikesRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")
        let tweetRef = Firestore.firestore().collection("tweets").document(tweetId)

        tweetRef.getDocument { snapshot, error in
            guard
                let currentTweet = try? snapshot?.data(as: TweetModel.self),
                error == nil else {
                if let error = error {
                    completion(.failure(error))
                } else {
                    completion(.failure(URLError(.badURL)))
                }
                return
            }
            let updatedCount = currentTweet.likes + 1
            tweetRef.updateData(["likes": updatedCount]) { error in
                if let error = error {
                    completion(.failure(error))
                } else {
                    userLikesRef.document(tweetId).setData([:]) { error in
                        if let error = error {
                            completion(.failure(error))
                        } else {
                            completion(.success(true))
                        }
                    }
                }
            }
        }
    }
  • 특정 트위터 피드에 대한 특정 유저의 좋아요 액션이 이루어질 때의 로직
  • 해당 트위터의 좋아요 카운트를 1 증가, 유저 정보 내 좋아하는 트위터 피드 정보 업데이트
  • 좋아요 트위터를 1 업데이트할 때 서버 상의 데이터를 기준으로 해야 하므로 getDocument뼟 통해 현 시점 파이어베이스 내 존재하는 트위터 데이터를 패치
 func dislikeTweet(_ tweet: TweetModel, completion: @escaping(Result<Bool, Error>) -> Void) {
        guard
            let uid = Auth.auth().currentUser?.uid,
            let tweetId = tweet.id else {
            completion(.failure(URLError(.badURL)))
            return
        }
        
        let userLikesRef = Firestore.firestore().collection("users").document(uid).collection("user-likes").document(tweetId)
        let tweetRef = Firestore.firestore().collection("tweets").document(tweetId)
        tweetRef.getDocument { snapshot, error in
            guard
                let currentTweet = try? snapshot?.data(as: TweetModel.self),
                error == nil else {
                if let error = error {
                    completion(.failure(error))
                } else {
                    completion(.failure(URLError(.badURL)))
                }
                return
            }
            let updatedCount = currentTweet.likes - 1
            tweetRef.updateData(["likes": updatedCount]) { error in
                if let error = error {
                    completion(.failure(error))
                } else {
                    userLikesRef.delete { error in
                        if let error = error {
                            completion(.failure(error))
                        } else {
                            completion(.success(true))
                        }
                    }
                }
            }
        }
    }
  • 좋아요 로직과 유사하지만 카운트 1 감소 이후 해당 트위터에 대한 정보를 유저 정보로부터 삭제하는 것이 유일한 차이
func checkIfUserLikedTweet(_ tweet: TweetModel, completion: @escaping(Result<Bool, Error>) -> Void) {
        guard
            let uid = Auth.auth().currentUser?.uid,
            let tweetId = tweet.id else {
            completion(.failure(URLError(.badURL)))
            return
        }
        let userLikesRef = Firestore.firestore().collection("users").document(uid)
            .collection("user-likes").document(tweetId)
        userLikesRef
            .getDocument { snapshot, error in
                guard
                    let snapshot = snapshot,
                    error == nil else {
                    if let error = error {
                        completion(.failure(error))
                    } else {
                        completion(.failure(URLError(.badURL)))
                    }
                    return
                }
                completion(.success(snapshot.exists))
            }
    }
  • 해당 트위터 롰뼟 처음 로드할 때 사용되는 함수
  • 현재 데이터베이스 내 유저가 해당 트위터에 이전 좋아요를 한 기록이 있는지 확인해 리턴
func fetchLikedTweets(for uid: String, completion: @escaping(Result<[TweetModel], Error>) -> Void) {
        var tweets: [TweetModel] = []
        Firestore.firestore().collection("users").document(uid).collection("user-likes").getDocuments { snapshot, error in
            guard
                let documents = snapshot?.documents,
                error == nil else {
                if let error = error {
                    completion(.failure(error))
                } else {
                    completion(.failure(URLError(.badURL)))
                }
                return
            }
            documents.forEach { document in
                let tweetId = document.documentID
                
                Firestore.firestore().collection("tweets")
                    .document(tweetId)
                    .getDocument { snapshot, error in
                        guard
                            let tweet = try? snapshot?.data(as: TweetModel.self) else { return }
                        tweets.append(tweet)
                        completion(.success(tweets))
                    }
            }
        }
    }
  • 유저 정보 내 입력된 트위터 아이디를 바탕으로 트위터 모델을 실제 패치
func fetchLikedTweets() {
        guard let uid = user.id else { return }
        service.fetchLikedTweets(for: uid) { [weak self] result in
            switch result {
            case .success(var tweets):
                tweets = tweets.map({ [weak self] tweet in
                    var tweet = tweet
                    tweet.user = self?.user
                    return tweet
                })
                self?.likedTweets = tweets
            case .failure(let error): print(error.localizedDescription)
            }
        }
    }
  • 특정 유저의 입장에서 좋아요를 기록한 모든 트위터를 패치하는 함수
  • 퍼블리셔를 뷰가 구독하고 있기 때문에 해당 롰뼟 사용 가능
func filterTweets(with filter: TweetFilterViewModel) -> [TweetModel] {
        switch filter {
        case .tweets:
            return tweets
        case .replies:
            return tweets
        case .likes:
            return likedTweets
        }
    }
  • 리스트에서 선택되는 데이터의 종류를 결정하는 함수
  • 리턴 값을 통해 리스트의 아이템을 구성
 private var heartButton: some View {
        Button {
            viewModel.didLikeTweet ? viewModel.dislikeTweet() : viewModel.likeTweet()
        } label: {
            Image(systemName: viewModel.didLikeTweet ? "heart.fill" : "heart")
                .font(.subheadline)
                .foregroundColor(viewModel.didLikeTweet ? .red : .gray)
        }
    }
  • 특정 트위터의 롰 모델로부터 받아온 정보, 즉 현재 유저가 해당 트위터에 좋아료를 누른 기록이 있는지를 통해 버튼을 누를 때의 액션 및 현재 UI뼟 결정

구현 화면


ObservableObject 및 @Published를 활용한 깔끔한 데이터 처리가 가능한 SwiftUI의 저력! 애니메이션은 별도로 덤이었다.

profile
JUST DO IT

0개의 댓글