게시글 작성에 대한 이야기를 해보고자 한다. 구글에서 찾아봐도 의외로 게시글을 작성하고 (본인은 파이어베이스) 서버에 저장한 후, 새로 불러오고 삭제하고 하는 기능을 설명한 곳을 찾기가 꽤 힘들었던 거 같다.
그래서 대부분 게시글 작성 코드를 구성할 때, 아주 어려운 난이도는 아니기에 생각해서 하거나 일정 코드만 블로그 같은 곳에서 부분 부분 참고했다.
그렇기에 로직들이 완벽하지 않고 허술한 부분이 많을 수 있다..
우선 단계를 나눠 보자면,
대강 이런 단계로 나뉘는데, 이 외에도 마커를 클릭할 시 게시글 정보가 표시되어야하며 '내 글 목록'도 구현했다.
게시글을 작성하는 곳의 UI는
이런식으로 구성했다. (수 차례 수정했다..)
기능을 구현하며 가장 어려웠던 부분은 어떤 식으로 게시글을 구분할 것이며, FireStore에 Collection, Document, Data Field 를 어떤 식으로 구분하고 어떠한 정보들을 담을 것인가 였다.
해당 앱에서는
여러 번 시행착오를 겪으며 결론적으론 이런식으로 구분했다.
가장 핵심이 되는 함수는
func SetData() {
// Firestore 참조 생성
let db = Firestore.firestore()
// 컬렉션에 대한 참조 생성
let collectionRef = db.collection("게시글")
// 문서에 대한 참조 생성
let documentRef = collectionRef.document(self.titleLabel?.text ?? "")
// 컬렉션 존재 여부 확인
collectionRef.getDocuments { (snapshot, error) in
if let error = error {
print("Error getting collection documents: \(error)")
return
}
if snapshot?.isEmpty == true {
// 컬렉션이 비어있으면 컬렉션 생성 후 문서 업데이트 작업 수행
let data: [String: Any] = ["내용": self.mainTextLabel.text ?? "", "유저": UserDefaults.standard.string(forKey: "UserEmailKey")!, "date": Date().timeIntervalSince1970]
documentRef.setData(data) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Collection created, document added and updated")
}
}
} else {
// 컬렉션이 존재하면 문서 존재 여부 확인 후 업데이트 작업 수행
documentRef.getDocument { (document, error) in
if let document = document, document.exists {
// 문서가 존재하면 업데이트 작업 수행
documentRef.updateData(["내용": self.mainTextLabel.text ?? "", "유저": UserDefaults.standard.string(forKey: "UserEmailKey")!, "date": Date().timeIntervalSince1970]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document updated")
}
}
} else {
// 문서가 존재하지 않으면 문서 생성 후 업데이트 작업 수행
let data: [String: Any] = ["내용": self.mainTextLabel.text ?? "", "유저": UserDefaults.standard.string(forKey: "UserEmailKey")!, "date": Date().timeIntervalSince1970]
documentRef.setData(data) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added and updated")
}
}
}
}
}
}
let storyboard = UIStoryboard(name: "MapMarkerRegister", bundle: nil)
guard let MapMarkerViewControllerVC = storyboard.instantiateViewController(withIdentifier: "MapMarkerViewController") as? MapMarkerViewController else { return }
MapMarkerViewControllerVC.titleLabel = self.titleLabel.text!
MapMarkerViewControllerVC.modalPresentationStyle = .fullScreen
present(MapMarkerViewControllerVC, animated: true)
}
문제는 이미지인데,
//이미지 파이어베이스로 업로드
func uploadimage(img: UIImage, completion: @escaping (Bool) -> Void) {
guard let referenceImage = UIImage(named: "free-icon-picture-5639854.png"),
let referenceImageData = referenceImage.jpegData(compressionQuality: 0.1),
let imageData = img.jpegData(compressionQuality: 0.1) else {
// 이미지 데이터를 가져오지 못한 경우 또는 기준 이미지를 가져오지 못한 경우 업로드하지 않음
completion(false)
return
}
// 이미지 데이터가 같은지 비교
if referenceImageData == imageData {
// 이미지가 기준 이미지와 같다면 업로드하지 않음
completion(false)
return
}
let fileName = generateUniqueFileName() // 중복되지 않는 파일 이름 생성
let metaData = StorageMetadata()
metaData.contentType = "image/png"
storage.reference().child(fileName).putData(imageData, metadata: metaData) { (metaData, error) in
if let error = error {
print(error.localizedDescription)
completion(false)
} else {
print("Image uploaded successfully")
self.downloadAndStoreURL(fileName: fileName)
completion(true)
}
}
}
//storage로 저장
func downloadAndStoreURL(fileName: String) {
let fileRef = storage.reference().child(fileName)
fileRef.downloadURL { url, error in
if let error = error {
print(error.localizedDescription)
} else if let downloadURL = url {
self.documentRef = self.db.collection("게시글").document(self.titleLabel.text ?? "")
self.documentRef?.updateData(["분실물 사진": FieldValue.arrayUnion([downloadURL.absoluteString])]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with profile image")
}
}
}
}
}
이렇게 이미지를 FireStorage에 저장하고, URL로 변환해 FireStore에 저장하고 URL을 다시 이미지화 하는 함수들을 추가해주어야한다. 그리고 앨범을 열 수 있게끔 권한 설정도 받아야한다.
(가장 머리가 아팠음)
또, 컬렉션이 있을 경우, 없을경우 나누고 문서가 있을경우 , 없을경우 또 나눈다. 이렇게 알맞게 데이터들을 분류해서 저장한다.
"게시글" 컬렉션 하위에 이런식으로 저장된다.
Firestore에 성공적으로 저장했다면 액션 메소드에 추가하면 된다.
@IBAction func completeBtn(_ sender: Any) {
let cameraImage = UIImage(named: "free-icon-photo-camera-4653765.png")
if titleLabel.text != "" && mainTextLabel.text != "" && !imagesAreEqual(lostItemPhoto.image, cameraImage) { //내용, 제목, 이미지 중 하나라도 비어있다면 게시글 작성 실패
SetData()
let storyboard = UIStoryboard(name: "Register", bundle: nil)
guard let RegisterViewControllerVC = storyboard.instantiateViewController(withIdentifier: "RegisterViewController") as? RegisterViewController else { return }
RegisterViewControllerVC.mainTableView?.reloadData()
//이미지파일 가지고 오기
imagesToUpload = [lostItemPhoto.image, lostItemPhoto2.image, lostItemPhoto3.image]
uploadImagesSequentially(index: 0)
} else {
// 알림 창 표시
let alertController = UIAlertController(title: "게시글 작성 오류", message: "내용을 확인해주세요.", preferredStyle: .alert)
// 확인 버튼 추가
let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
alertController.addAction(okAction)
// 알림 창 표시
present(alertController, animated: true, completion: nil)
}
}
//이미지를 비교하는 함수
func imagesAreEqual(_ image1: UIImage?, _ image2: UIImage?) -> Bool {
if let data1 = image1?.pngData(), let data2 = image2?.pngData() {
return data1 == data2
}
return false
}
제목, 내용, 사진이 모두 빠지지 않게끔 설정한다면 게시글 작성은 완성된 셈이다.
(좌표를 저장하는 부분은 전 포스팅에서 다룸)
이렇게 작성한 글을 다시 로드하는 과정이 필요하다.
해당 앱에서는 마커를 이용해서 불러오기, 내 글 목록에서 게시글을 선택해 불러오기 로 두 가지 루트로 나뉜다.
이 과정은 다음 포스팅에서 다루겠다.