먼저 게시글을 위한 데이터 모델을 다음과 같이 생성했다:
struct Post: Identifiable, Codable {
@DocumentID var id: String? // Firestore 문서 ID (자동 생성)
let nickName: String
let postType: String // "팀원 모집" 또는 "팀 합류"
let title: String
let detail: String
let position: [String]
let techStack: [String]
let ideaStatus: String
let meetingStyle: String
let numberOfRecruits: String
let createdAt: Date
// 팀원 모집 전용 필드 (옵셔널)
var urgency: String?
var experience: String?
// 팀 합류 전용 필드 (옵셔널)
var available: String?
var currentStatus: String?
}
게시글 CRUD 작업을 위한 Service 클래스는 다음과 같이 구현했다:
class PostService {
static let shared = PostService()
private let db = Firestore.firestore()
private init() {}
// Create
func uploadPost(post: Post, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let data = try Firestore.Encoder().encode(post)
db.collection("posts").addDocument(data: data) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
} catch {
completion(.failure(error))
}
}
// Read
func getPostList(type: String, completion: @escaping (Result<[Post], Error>) -> Void) {
db.collection("posts")
.whereField("postType", isEqualTo: type)
.order(by: "createdAt", descending: true)
.getDocuments { snapshot, error in
if let error = error {
completion(.failure(error))
return
}
guard let documents = snapshot?.documents else { return }
let posts = documents.compactMap { document -> Post? in
try? document.data(as: Post.self)
}
completion(.success(posts))
}
}
// Update
func updatePost(id: String, post: Post, completion: @escaping (Result<Void, Error>) -> Void) {
do {
let data = try Firestore.Encoder().encode(post)
db.collection("posts").document(id).updateData(data) { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
} catch {
completion(.failure(error))
}
}
// Delete
func deletePost(id: String, completion: @escaping (Result<Void, Error>) -> Void) {
db.collection("posts").document(id).delete { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
}
}
@objc private func submitButtonTapped() {
// 입력값 검증
guard !selectedPositions.isEmpty,
!selectedAvailable.isEmpty,
let titleInput = uploadView.titleSection.textField.text,
!titleInput.isEmpty else {
basicAlert(title: "입력 필요", message: "빈칸을 채워주세요")
return
}
// Post 객체 생성
let post = Post(
id: nil,
nickName: "test",
postType: postType.rawValue,
title: titleInput,
createdAt: Date()
)
// 서버에 업로드
PostService.shared.uploadPost(post: post) { [weak self] result in
switch result {
case .success:
self?.navigationController?.popViewController(animated: true)
case .failure(let error):
self?.basicAlert(title: "업로드 실패", message: "\(error)")
}
}
}
@objc private func editButtonTapped() {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 수정 액션
let editAction = UIAlertAction(title: "수정하기", style: .default) { [weak self] _ in
guard let self = self,
let post = self.post else { return }
let uploadVC = JoinTeamUploadVC()
uploadVC.isEditMode = true
uploadVC.editPostId = post.id
// 기존 데이터 설정
uploadVC.selectedPositions = post.position
uploadVC.selectedAvailable = post.available ?? ""
self.navigationController?.pushViewController(uploadVC, animated: true)
}
// 삭제 액션
let deleteAction = UIAlertAction(title: "삭제하기", style: .destructive) { [weak self] _ in
guard let self = self,
let post = self.post,
let postId = post.id else { return }
let alert = UIAlertController(title: "삭제 확인",
message: "정말 삭제하시겠습니까?",
preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "삭제", style: .destructive) { [weak self] _ in
PostService.shared.deletePost(id: postId) { result in
switch result {
case .success:
DispatchQueue.main.async {
self?.navigationController?.popViewController(animated: true)
}
case .failure(let error):
self?.basicAlert(title: "삭제 실패", message: "\(error)")
}
}
}
alert.addAction(confirmAction)
alert.addAction(UIAlertAction(title: "취소", style: .cancel))
self.present(alert, animated: true)
}
alert.addAction(editAction)
alert.addAction(deleteAction)
alert.addAction(UIAlertAction(title: "취소", style: .cancel))
present(alert, animated: true)
}
이러한 CRUD 기능을 통해 사용자들은 게시글을 작성하고, 수정하고, 삭제할 수 있으며, 다른 사용자들의 게시글도 볼 수 있도록 구현했다.