π΄ Let's Build Twitter with SwiftUI (iOS 15, Xcode 13, Firebase, SwiftUI 3.0)
import Foundation
class UploadTweetViewModel: ObservableObject {
let service = TweetService()
@Published var caption: String = ""
@Published var didUploadTweet: Bool = false
func uploadTweet() {
service.uploadTweet(caption: caption) { [weak self] success in
self?.didUploadTweet = success
}
}
}
import Foundation
import SwiftUI
import FirebaseFirestoreSwift
import Firebase
struct TweetModel: Codable, Identifiable {
@DocumentID var id: String?
let uid: String
let caption: String
let likes: Int
let timestamp: Timestamp
var user: UserModel?
}
@DocumentID
νλ‘ν μ½μ λ°λ¦μΌλ‘μ¨ μλμΌλ‘ λμ½λ© μ λΆμ¬λ°μstruct TweetService {
func uploadTweet(caption: String, completion: @escaping(Bool) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let data = [
"uid": uid,
"caption": caption,
"likes": 0,
"timestamp": Timestamp(date: Date())
] as [String: Any]
Firestore.firestore().collection("tweets").document()
.setData(data) { error in
if let error = error {
print(error.localizedDescription)
completion(false)
} else {
print("Upload Tweet Did Succeed")
completion(true)
}
}
}
func fetchTweets(completion: @escaping(Result<[TweetModel], Error>) -> Void) {
Firestore.firestore().collection("tweets")
.order(by: "timestamp", descending: true)
.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
}
let tweets = documents.compactMap({try? $0.data(as: TweetModel.self)})
completion(.success(tweets))
}
}
func fetchTweets(for uid: String, completion: @escaping(Result<[TweetModel], Error>) -> Void) {
Firestore.firestore().collection("tweets")
.whereField("uid", isEqualTo: uid)
.getDocuments { snapshot, error in
guard
let documents = snapshot?.documents,
error == nil else {
if let error = error {
print(error.localizedDescription)
completion(.failure(error))
} else {
completion(.failure(URLError(.badURL)))
}
return
}
let tweets = documents.compactMap({ try? $0.data(as: TweetModel.self)}).sorted(by: { $0.timestamp.dateValue() > $1.timestamp.dateValue() })
completion(.success(tweets))
}
}
}
import Foundation
class FeedViewModel: ObservableObject {
@Published var tweets: [TweetModel] = []
private let service = TweetService()
private let userService = UserService()
init() {
fetchTweets()
}
func fetchTweets() {
service.fetchTweets { [weak self] result in
switch result {
case .success(let tweets):
self?.tweets = tweets
for idx in 0..<tweets.count {
let uid = tweets[idx].uid
self?.userService.fetchUser(with: uid, completion: { result in
switch result {
case .failure(let error): print(error.localizedDescription)
case .success(let user): self?.tweets[idx].user = user
}
})
}
case .failure(let error): print(error.localizedDescription)
}
}
}
}
ScrollView {
LazyVStack {
ForEach(viewModel.tweets) { tweet in
TweetRowView(tweet: tweet)
.padding()
}
}
}
.refreshable {
viewModel.fetchTweets()
}
refreshable
μ ν΅ν΄ pullToRefresh
λ₯Ό ν λλ§λ€ μλμΌλ‘ λ·° λͺ¨λΈμ ν¨μλ₯Ό μ¬μ©νλλ‘ μ μΈ@ObservedObject private var viewModel: TweetRowViewModel
init(tweet: TweetModel) {
_viewModel = ObservedObject(wrappedValue: TweetRowViewModel(tweet: tweet))
}
ObserableObject
λ₯Ό μ΄λμ
λΌμ΄μ¦νκΈ° μν΄ μ¬μ©import Foundation
import SwiftUI
class TweetRowViewModel: ObservableObject {
let tweet: TweetModel
@Published var profileImage: UIImage?
init(tweet: TweetModel) {
self.tweet = tweet
if let urlString = tweet.user?.profileImageURL {
downloadProfileImage(with: urlString)
}
}
private func downloadProfileImage(with urlString: String) {
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let response = response as? HTTPURLResponse,
response.statusCode >= 200,
response.statusCode < 400,
error == nil,
let data = data,
let image = UIImage(data: data) else { return }
DispatchQueue.main.async { [weak self] in
self?.profileImage = image
}
}
.resume()
}
}
@Published
λ‘ λ°κ³ μλ κΉλμ ν΄λΉ λ·°μμ μ΄λ―Έμ§ ν¨μΉ μ¬λΆμ λ°λΌ μ΄λ―Έμ§λ₯Ό μλ‘κ² λ λλ§ κ°λ₯