🟨 구현 화면

🟨 Tweet.swift
import Foundation
struct Tweet: Codable, Identifiable {
var id = UUID().uuidString
let author: TwitterUser
let authorID: String
let tweetContent: String
var likesCount: Int
var likers: [String]
let isReply: Bool
let parentReference: String?
}
🟨 DatabaseManager.swift
- collectionTweets(retreiveTweets ...) 메서드 생성
- firebase database 내에서 author.id를 통해 얻은 id를 기준으로 Tweet 타입의 데이터를 불러온다.
import Foundation
import Firebase
import FirebaseFirestoreSwift
import FirebaseFirestoreCombineSwift
import Combine
class DatabaseManager {
static let shared = DatabaseManager()
let db = Firestore.firestore()
let usersPath: String = "users"
let tweetsPath: String = "tweets"
...
func collectionTweets(retreiveTweets forUserID: String) -> AnyPublisher<[Tweet], Error> {
db.collection(tweetsPath).whereField("author.id", isEqualTo: forUserID)
.getDocuments()
.tryMap(\.documents)
.tryMap { snapshots in
try snapshots.map({
try $0.data(as: Tweet.self)
})
}
.eraseToAnyPublisher()
}
🟥 tryMap?
- map이랑 비슷한데 앞에 try가 붙었다.
- tryMap은 제공된 error-throwing closure를 사용하여 upstream publisher의 모든 요소를 변환한다.
- tryMap같은 경우, 클로져가 오류를 발생하면 publish를 종료한다.
🟨 HomeViewViewModel.swift
- fetchTweets() 메서드 생성
- userID에 맞는 Tweet 타입의 데이터를 호출한다.
- retreiveUser() 메서드 내에 handleEvents를 통해 fetchTweets() 를 호출한다.
import Foundation
import Combine
import FirebaseAuth
final class HomeViewViewModel: ObservableObject {
@Published var user: TwitterUser?
@Published var error: String?
@Published var tweets: [Tweet] = []
private var subscriptions: Set<AnyCancellable> = []
func retreiveUser() {
guard let id = Auth.auth().currentUser?.uid else { return }
DatabaseManager.shared.collectionUsers(retreive: id)
.handleEvents(receiveOutput: { [weak self] user in
self?.user = user
self?.fetchTweets()
})
.sink { [weak self] completion in
if case .failure(let error) = completion {
self?.error = error.localizedDescription
}
} receiveValue: { [weak self] user in
self?.user = user
}
.store(in: &subscriptions)
}
func fetchTweets() {
guard let userID = user?.id else { return }
DatabaseManager.shared.collectionTweets(retreiveTweets: userID)
.sink { [weak self] completion in
if case .failure(let error) = completion {
self?.error = error.localizedDescription
}
} receiveValue: { [weak self] retreivedTweets in
self?.tweets = retreivedTweets
}
.store(in: &subscriptions)
}
}
🟨 HomeViewController.swift
- bindView() 메서드 내에 $tweets 값을 연결하여, 변동이 있을 때마다 timelineTableView를 새로고침하게 한다.
import UIKit
import FirebaseAuth
import Combine
class HomeViewController: UIViewController {
private var viewModel = HomeViewViewModel()
private var subscriptions: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
...
bindViews()
}
@objc private func didTapSignOut() {
try? Auth.auth().signOut()
handleAuthentication()
}
...
// 프로필뷰에서 네비게이션 숨기기에 따라 홈 -> 프로필 -> 홈으로 돌아오는 과정에서 홈에도 네비게이션이 숨기기로 나오는 것을 고침
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = false
// 로그인 여부 확인
handleAuthentication()
viewModel.retreiveUser()
}
...
func bindViews() {
...
viewModel.$tweets.sink { [weak self] _ in
DispatchQueue.main.async {
self?.timelineTableView.reloadData()
}
}
.store(in: &subscriptions)
}
...
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.tweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TweetTableViewCell.identifier,
for: indexPath) as? TweetTableViewCell
else {
return UITableViewCell()
}
let tweetModel = viewModel.tweets[indexPath.row]
cell.configureTweet(with: tweetModel.author.displayName,
username: tweetModel.author.username,
tweetTextContent: tweetModel.tweetContent,
avatarPath: tweetModel.author.avatarPath)
cell.delegate = self
return cell
}
}
...
🟨 TweetTableViwCell.swift
- configureTweet() 메서드 생성
- 각 UI에 연결하여 값을 보여준다.
import UIKit
// 데이터 전달 목적으로 델리게이트 패턴 사용 - 프로토콜 선언 - 1
protocol TweetTableViewCellDelegate: AnyObject {
func tweetTableViewCellDidTapReply()
func tweetTableViewCellDidTapRetweet()
func tweetTableViewCellDidTapLike()
func tweetTableViewCellDidTapShare()
}
class TweetTableViewCell: UITableViewCell {
// 대리자 선언 - 2
weak var delegate: TweetTableViewCellDelegate?
static let identifier = "TweetTableViewCell"
private let actionSpacing: CGFloat = 60
...
func configureTweet(with displayName: String, username: String, tweetTextContent: String, avatarPath: String) {
displayNameLabel.text = displayName
userNameLabel.text = "@\(username)"
tweetTextContentLabel.text = tweetTextContent
avatarImageView.sd_setImage(with: URL(string: avatarPath))
}
}
'Clone App > Twitter' 카테고리의 다른 글
[Twitter Clone] Add Search Bar (0) | 2024.06.14 |
---|---|
[Twitter Clone] Connect to profileView (0) | 2024.06.12 |
[Twitter Clone] Load Profile Data to profileView (0) | 2024.06.12 |
[Twitter Clone] Connect to Firebase Storage (0) | 2024.06.12 |
[Twitter Clone] Add ProfileDataFormView (0) | 2024.06.11 |