FireStore와 다이어리 iOS App을 연동하며 드는 의문점들과 솔루션
다이어리 데이터 FireStore CRUD 방법
모델 구조
실시간 업데이트하는 법
Timestamp to NSDate 타입변경
1) FireStore에 어떤 모델 구조로 저장해야하는지와 데이터 추가 방법
FireStore Datamodel
Collection - 폴더(Users)
Document - 문서(Dahae)
Data - 문서안의 내용(name, bornDate, address...)
Each Individual Data를 만들어줌
name, sender, content of message, timestamp
Dictionary와 Json을 사용할 수 있다.
아래와 같이 Diary 모델 구조를 짜보았다.
//
// Diary.swift
// cocoBaby_2021
//
// Created by DaHae Kim on 2021/11/17.
//
import Foundation
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Diary {
var content: String
var timeStamp: Date
var emoji: String
// Key(Strin(g)-Value(Any)
var dictionary: [String:Any] {
return [
"content": content,
"timeStamp": timeStamp,
"emoji": emoji
]
}
}
extension Diary : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date,
let emoji = dictionary["emoji"] as? String else { return nil }
self.init(content: content, timeStamp: timeStamp, emoji: emoji)
}
}
키값은 timeStamp로 사용해도 되겠는데..?(밑에 더 읽어보면 보면 파이어스토어에서 자동생성해주는 ID로 정함)
정리하면 모델 구조는 이런식이다 Diaries 컬렉션으로 들어가서 자신의 UID값으로 다이어리를 조회해 온다. 그 이후에 Diary덩어리로 여러개가 쌓여야 한다는 것이지..
FireStore의 sub-collection으로 데이터 계층화할 수 있음 -> 여태까지 적용해온 데이터 모델과는 다름!
컬렉션 -> 문서 -> 컬렉션 -> 문서 이런식으로 구현해야함.
위와 같은 구조로 아래 소스코드처럼 [ Diaries -> UID -> Diary -> 데아터 ]방식으로 구현했다.
자신의 UID로 들어간 이후 문서ID는 난수로 자동생성된다. 자동으로 생성되는 문서 ID값을 키값으로 두면 될 것 같다.
let newDiary = Diary(content: note, timeStamp: Date(), emoji: "")
self.ref = self.db
.collection("Diaries").document(self.userUid!)
.collection("Diary").addDocument(data: newDiary.dictionary)
위 소스코드를 실행하면 FireStore에 Diaries 컬렉션으로 UID(검정색박스로 가린값) 문서들이 생기고
그 문서안에서 컬렉션을 Diary로 만들고, 다이어리 데이터를 저장한다.
Diary 컬렉션을 누르면 아래와 같이 데이터가 저장된 것을 확인할 수 있다.
오케이 굳..( '-')b
2) FireStore에 저장된 다이어리 데이터 가져오기
그렇다면 저장된 데이터를 어떻게 불러오고, 불러온 데이터를 테이블 뷰에 차곡차곡 뿌려줄 수 있을까?
[모든Diaries -> 나의UID -> Diary -> 데이터] 에서 Diary가 가진 모든 데이터 개별 다이어리들을 먼저 가져와야한다.
즉, we need to get all the documents from Diary
a. Get data from firestore
b. Store received data to local array => 여기서 flatMap을 사용하여 우리가 원하는 데이터 모델(위에서 설정해놓은 Diary Struct대로)로 데이터를 받아올 수 있다. (flatMap을 잘 모르고 사용하다보니 오류가나서 그냥 for in으로 Array에 append해주는 방법을 사용했다. 다음에 해보는 것으로 ㅠ..(이렇게 기술부채가!!!!!))
아래와 같이 구현했다.
func loadDiaryData() {
db.collection("Diaries").document(userUid!)
.collection("Diary").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
if let stamp = document.data()["timeStamp"] as? Timestamp {
self.diaryArray.append(Diary(content: document.data()["content"] as! String, timeStamp: stamp.dateValue() as! Date, emoji: document.data()["emoji"] as! String))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
* 중간에 났던 오류
Could not cast value of type 'FIRTimestamp' (0x1e4483570) to 'NSDate'
=> FireStore의 TimeStamp가 Swift의 Date타입과 다르기 때문에 캐스팅할 수 없다는 오류가 나타난다.
TimeStamp를 NSDate으로 변경해줘야 한다. 둘은 엄연히 다른 데이터 타입임!
아래방법으로 TimeStamp데이터 타입으로 먼저 받아와준 뒤에 Swift5부터 생긴 것으로 보이는(?) dateValue를 해주면 Date()타입으로 변경해줄 수 있다.
import FirebaseFirestore
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let stamp = data["timeStamp"] as? Timestamp else {
return nil
}
let date = stamp.dateValue()
}
3) 다이어리에 데이터 추가한 후 테이블뷰에 바로 반영되어 나타나게 하기
FireStore가 리얼타임 데이터베이스이기 때문에 실시간 업데이트를 listen하게 할 수 있음
It's kind of a listener. It listens app for whole time while app is running and until we terminate the app.
조건(데이터를 불러올 때 조건쿼리를 쓸 수 있다.)
- whereField를 사용하여, 우리가 업데이트하는 timeStamp가 현재 Date보다 많을때만 쿼리한다.
- 컬렉션의 변화를 알아차리고 유저인터페이스에 알려주는 역할이 addSnapshotLister { querySnapshop~
- documentChange.forEatch를 통해 업뎃됐을때, 삭제 됐을때 등의 경우에서 어떤 실행을 할지 정할 수 있다.
- 오직 달라진 부분만 업뎃한다.
func CheckForUpdate() {
db.collection("Diaries").document(self.userUid!)
.collection("Diary").whereField("timeStamp", isGreaterThan: Date())
.addSnapshotListener {
querySnapshopt, error in
print("CheckForUpdate Date: \(Date())")
guard let snapshot = querySnapshopt else { return }
snapshot.documentChanges.forEach {
diff in
if diff.type == .added {
if let stamp = diff.document.data()["timeStamp"] as? Timestamp {
self.diaryArray.append(Diary(content: diff.document.data()["content"] as! String, timeStamp: stamp.dateValue() as! Date, emoji: diff.document.data()["emoji"] as! String))
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
+ 추가지식
=> FireStore 인터넷이 없어도 기존에 받아온 데이터는 볼 수 있다. 그리고 인터넷이 다시 연결되면 파이어스토어가 자동으로 데이터를 fetch해준다.
결과 화면
참고로 아래 뷰에서 날짜 데이터는 UILabel 디폴트값을 보여주고 실제 TimeStamp로 넣어주는 것은 안한 상태다.
*출처: FireStore 공식문서