본문 바로가기

프로젝트/Twitter Clone App (w∕Firebase)

18. 기능 구현 #6

기능 구현 #6
트윗 작성


트윗 작성
| NewTweetView

struct NewTweetView: View {
    @State private var caption = ""
    @Environment(\.dismiss) var dismiss

    var body: some View {
        VStack {
            HStack {
                Button {
                    dismiss()
                } label: {
                    Text("Cancel")
                        .foregroundColor(Color(.systemBlue))
                }

                Spacer()

                Button {
                    print("tweet")
                } label: {
                    Text("Tweet")
                        .bold()
                        .foregroundColor(.white)
                        .padding(.horizontal)
                        .padding(.vertical, 8)
                        .background(Color(.systemBlue))
                        .clipShape(Capsule())
                }
            }
            .padding()

            HStack(alignment: .top) {
                Circle()
                    .frame(width: 64, height: 64)

                TextArea("What's happening?", text: $caption)
            }
            .padding()
        }
    }
}

이전에 작성했던 NewTweetView는 아직도 임시 View를 프로필 사진 대신 표시하고 있다.
지금 트윗을 작성하려는 계정의 프로필 사진을 표시하도록 코드를 수정한다.

import SwiftUI
import Kingfisher

struct NewTweetView: View {
    @State private var caption = ""
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var authViewModel: AuthViewModel
    .
    .
    .
            HStack(alignment: .top) {
                if let user = authViewModel.currentUser {
                    KFImage(URL(string: user.profileImageUrl))
                        .resizable()
                        .scaledToFill()
                        .clipShape(Circle())
                        .frame(width: 64, height: 64)
                }
    .
    .
    .

로그인이 유효한지 확인하기 위해 FirebaseAuth를 사용해야 한다.
따라서 main에서 전달한 AuthViewModel을 사용하기 위해 EnvironmentObject 인스턴스를 선언한다.
Kingfisher를 import 하고, currentUser를 확인해 profileImageUrl을 KFImage 메서드로 전달한다.

트윗 작성
| TweetService

NewTweetView에서 작성한 글을 DB에 업로드할 메서드를 작성한다.

import Firebase

struct TweetService {
    func uploadTweet(caption: String, completion: @escaping(Bool) -> Void) {

    }
}

메서드의 이름은 uploadTweet이고, 내용을 caption 파라미터로 전달받아 결과를 CompletionHandler로 전달한다.

import Firebase

struct TweetService {
    func uploadTweet(caption: String, completion: @escaping(Bool) -> Void) {
        guard let uid = Auth.auth().currentUser?.uid else {
            return
        }
    }
}

 

guard-let으로 FirebaseAuth의 우선 트윗을 올릴 계정의  유효성을 검사한다.

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]
    }
}

이제 Firebase에 업로드하기 위한 데이터를 dictionary로 구성한다.
uid는 현재 접속중인 사용자의 uid,
caption은 파라미터로 전달받은 트윗의 내용,
likes는 좋아요의 카운트,
timestamp는 트윗을 작성하는 시간에 해당한다.

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) {
        
        }
    }
}

데이터의 저장을 위해서는 Firestore의 저장할 경로로 접근해 setData 메서드를 호출하면 된다.

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)
        }
    }
}

completionHandler로는 setData 메서드의 결과가 전달된다.
error로 바인딩해 값이 존재한다면 실패로 판단하고 completionHandler로 false를 전달하고 종료하며,
값이 존재하지 않는다면 성공으로 판단하고 completionHandler로 true를 전달한다.

트윗 작성
| UploadTweetViewModel

TweetService에서 정의한 메서드를 NewTweetView에서 사용할 수 있도록 UploadTweetViewModel에 기능을 구현한다.

class UploadTweetViewModel: ObservableObject {
    @Published var didUploadTweet = false
    let service = TweetService()
}

UploadTweetViewModel에서 Firebase를 사용할 수 있도록 TweetService 인스턴스를 생성하고,
TweetService에서 정의한 메서드 uploadTweet의 결과를 저장할 published 변수 didUploadTweet를 선언한다.

class UploadTweetViewModel: ObservableObject {
    @Published var didUploadTweet = false
    let service = TweetService()

    func uploadTweet(withCaption caption: String) {
        service.uploadTweet(caption: caption) { result in
            if result {
                self.didUploadTweet = true
            } else {
                //show error
            }
        }
    }
}

NewTweetView에서 호출할 수 있도록 uploadTweet 메서드를 재정의 한다.
caption을 파라미터로 전달받아, TweetService의 uploadTweet 메서드에 전달한다.
이후 uploadTweet 메서드의 결과에 따라 CompletionHandler에서 분기해 uploadTweet의 상태를 변경한다.

트윗 작성
| NewTweetView에 연결하기

이제 기능적인 부분이 완성 됐으므로 Tweet을 작성하는 화면인 NewTweetView에 연결하기만 하면 된다.

import SwiftUI
import Kingfisher

struct NewTweetView: View {
    @State private var caption = ""
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var authViewModel: AuthViewModel
    @ObservedObject var viewModel = UploadTweetViewModel()
    .
    .
    .

앞서 작성했던 UploadTweetViewModel 인스턴스를 생성한다.

    .
    .
    .
    var body: some View {
        VStack {
            HStack {
                Button {
                    dismiss()
                } label: {
                    Text("Cancel")
                        .foregroundColor(Color(.systemBlue))
                }

                Spacer()

                Button {
                    viewModel.uploadTweet(withCaption: caption)
                } label: {
                    Text("Tweet")
                        .bold()
                        .foregroundColor(.white)
                        .padding(.horizontal)
                        .padding(.vertical, 8)
                        .background(Color(.systemBlue))
                        .clipShape(Capsule())
                }
            }
            .padding()
    .
    .
    .

로그를 표시하던 버튼을 UploadTweetViewModel의 uploadTweet 메서드를 호출하도록 수정한다.

                .
                .
                .
                TextArea("What's happening?", text: $caption)
            }
            .padding()
        }
        .onReceive(viewModel.$didUploadTweet) { result in
            if result {
                dismiss()
            }
        }
    }
}

트윗 업로드가 끝나게 되면, flag로 사용하려고 생성했던 didUploadTweet이 변경된다.
해당 값이 변경되면 반응할 수 있도록 onReceive modifier를 사용해 dismiss를 호출하면
업로드가 완료될 시 자동으로 NewTweetView가 사라지고 이전의 화면으로 돌아가도록 구현할 수 있다.

'프로젝트 > Twitter Clone App (w∕Firebase)' 카테고리의 다른 글

20. 기능 구현 #8  (0) 2023.01.17
19. 기능 구현 #7  (0) 2023.01.14
17. 기능 구현 #5  (0) 2023.01.12
16. 코드 가독성 개선  (0) 2023.01.11
15. 기능구현 #4  (0) 2023.01.11