기능 구현 #7
Tweet 가져오기
Tweet 가져오기
| Tweet Model
Firebase에 저장된 Tweet의 모습이다.
이 데이터들을 앱에서 보여줄 수 있도록 객체화가 필요하다.
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?
}
Identifiable과 Codable 프로토콜을 채용한 Tweet 구조체를 하나 정의했다.
Tweet의 id에 해당하는 DocumentID는 Identifiable의 필수 요소인 id로 사용된다.
기타 다른 속성도 데이터에 맞도록 변수를 선언해 준다.
Tweet 가져오기
| TweetRowView
HStack(alignment: .top, spacing: 12) {
Circle()
.frame(width: 56, height: 56)
.foregroundColor(Color(.systemBlue))
//user info & tweet caption
VStack(alignment: .leading, spacing: 4) {
//user info
HStack {
Text("Marcus")
.font(.subheadline).bold()
Group {
Text("@philosophy")
Text("2d")
}
.foregroundColor(.gray)
.font(.caption)
}
//tweet caption
Text("Who burned my beard?!")
.font(.subheadline)
.multilineTextAlignment(.leading)
}
}
기존의 TweetRowView는 DB의 데이터가 아닌 임시 데이터를 표시하고 있었다.
이제는 DB에 저장 돼 있는 Tweet을 표시하도록 수정할 차례다.
struct TweetRowView: View {
let tweet: Tweet
var body: some View {
VStack(alignment: .leading) {
//profile image & user info & tweet
HStack(alignment: .top, spacing: 12) {
Tweet 구조체를 받아 사용할 수 있도록 tweet 변수를 선언했다.
HStack(alignment: .top, spacing: 12) {
Circle()
.frame(width: 56, height: 56)
.foregroundColor(Color(.systemBlue))
//user info & tweet caption
VStack(alignment: .leading, spacing: 4) {
//user info
HStack {
Text("Marcus")
.font(.subheadline).bold()
Group {
Text("@philosophy")
Text("2d")
}
.foregroundColor(.gray)
.font(.caption)
}
//tweet caption
Text(tweet.caption)
.font(.subheadline)
.multilineTextAlignment(.leading)
}
}
이후엔 TextView에 tweet의 caption 속성을 전달하기만 하면 된다.
Source
struct TweetRowView: View {
let tweet: Tweet
var body: some View {
VStack(alignment: .leading) {
//profile image & user info & tweet
HStack(alignment: .top, spacing: 12) {
Circle()
.frame(width: 56, height: 56)
.foregroundColor(Color(.systemBlue))
//user info & tweet caption
VStack(alignment: .leading, spacing: 4) {
//user info
HStack {
Text("Marcus")
.font(.subheadline).bold()
Group {
Text("@philosophy")
Text("2d")
}
.foregroundColor(.gray)
.font(.caption)
}
//tweet caption
Text(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 {
// action
} label: {
Image(systemName: "heart")
}
Spacer()
Button {
// action
} label: {
Image(systemName: "bookmark")
}
}
.font(.subheadline)
.padding()
.foregroundColor(.gray)
Divider()
}
}
}
Tweet 가져오기
| TweetService
import Firebase
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("Failed to upload \(error.localizedDescription)")
completion(false)
return
}
completion(true)
}
}
}
TweetService는 Tweet에 관련된 기능을 모아 구현하는 구조체다.
이전에 업로드를 구현했으므로, 이번에는 반대로 받아오는 기능을 구현하면 된다.
func fetchTweets(completion: @escaping([Tweet]) -> Void) {
}
메서드의 이름은 fetchTweets으로 'fetch'는 DB에서 원하는 데이터를 받아 온다는 의미로 흔히 쓰인다.
해당 메서드는 받아온 Tweet들을 배열에 저장해 CompletionHandler에 전달하는 기능을 한다.
func fetchTweets(completion: @escaping([Tweet]) -> Void) {
Firestore.firestore().collection("tweets").getDocuments { snapshot, _ in
}
}
사용자들이 업로드한 tweet들은 DB의 tweets collection에 저장된다.
해당 collection에 저장된 모든 tweet을 getDocuments 메서드를 사용해 가져온다.
func fetchTweets(completion: @escaping([Tweet]) -> Void) {
Firestore.firestore().collection("tweets").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {
return
}
let tweets = documents.compactMap({
try? $0.data(as: Tweet.self)
})
completion(tweets)
}
}
가져 올 tweet이 없다면 그대로 종료하고,
가져올 tweet이 있다면 앞서 정의한 Tweet Model의 형태로 배열에 저장해 completion에 전달한다.
Source
import Firebase
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("Failed to upload \(error.localizedDescription)")
completion(false)
return
}
completion(true)
}
}
func fetchTweets(completion: @escaping([Tweet]) -> Void) {
Firestore.firestore().collection("tweets").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else {
return
}
let tweets = documents.compactMap({
try? $0.data(as: Tweet.self)
})
completion(tweets)
}
}
}
Tweet 가져오기
| FeedViewModel
class FeedViewModel: ObservableObject {
@Published var tweets = [Tweet]()
let service = TweetService()
init() {
fetchTweets()
}
func fetchTweets() {
service.fetchTweets { tweets in
self.tweets = tweets
}
}
}
FeedView는 Twitter 앱의 주요 화면으로, 사용자들이 올린 Tweet이 표시된다.
TweetService에서 정의한 fetchTweets 메서드를 호출하도록 메서드를 재정의한다.
이렇게 반환된 Tweet들은 Published 변수인 tweets에 저장된다.
Tweet 가져오기
| FeedView
struct FeedView: View {
@State private var showNewTweetView = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(0 ... 20, id: \.self) { _ in
TweetRowView()
.padding()
}
}
}
Button {
showNewTweetView.toggle()
} label: {
Image("tweet")
.resizable()
.renderingMode(.template)
.frame(width: 28, height: 28)
.padding()
}
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(Circle())
.padding()
.fullScreenCover(isPresented: $showNewTweetView) {
NewTweetView()
}
}
}
}
지금까지의 FeedView는 실제 Tweet를 가져올 수 없었기 때문에
ForEah를 사용해 임시로 생성한 TweetRowView를 호출할 뿐이었다.
struct FeedView: View {
@State private var showNewTweetView = false
@ObservedObject var viewModel = FeedViewModel()
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
앞서 정의한 FeedViewModel을 사용할 수 있도록 ObseredObject 변수로 FeedViewModel을 호출한다.
생성자에서 fetchTweets 메서드를 호출하도록 돼있으므로 이렇게 호출하는 순간 DB에서 Tweet을 받아오게 된다.
ScrollView {
LazyVStack {
ForEach(viewModel.tweets) { tweet in
TweetRowView(tweet: tweet)
.padding()
}
}
}
임시 데이터로 TweetRowView를 표시하던 ForEach문에 실제 데이터를 연결한다.
viewModel의 tweets 변수는 배열의 형식으로, 이들을 tweet으로 열거해 TweetRowView로 전달한다.
Source
struct FeedView: View {
@State private var showNewTweetView = false
@ObservedObject var viewModel = FeedViewModel()
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(viewModel.tweets) { tweet in
TweetRowView(tweet: tweet)
.padding()
}
}
}
Button {
showNewTweetView.toggle()
} label: {
Image("tweet")
.resizable()
.renderingMode(.template)
.frame(width: 28, height: 28)
.padding()
}
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(Circle())
.padding()
.fullScreenCover(isPresented: $showNewTweetView) {
NewTweetView()
}
}
}
}
'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글
21. DB와 연결하기 #3 (0) | 2023.01.18 |
---|---|
20. 기능 구현 #8 (0) | 2023.01.17 |
18. 기능 구현 #6 (0) | 2023.01.13 |
17. 기능 구현 #5 (0) | 2023.01.12 |
16. 코드 가독성 개선 (0) | 2023.01.11 |