【iOS】Firebaseど素人のFirebase入門 認証編に続き、今回はFirestoreを学びながらサンプルアプリ作りに挑戦してみました。
Firestore上のデータがTableView上に表示されるアプリです。
・Set Button
を押すと、Firestoreに情報が書き込み or 更新(setdata)されます。
・Add Button
を押すと、Firestoreに情報が追加(addDocument)されます。
・Firestoreからドキュメントを取得を押す
を押すと、Firestoreに保存されている情報を取得(getDocuments)して、TableViewに反映します。
・リアルタイムアップデート
スイッチをOnにすると、Firestore上の情報が更新される度に自動的にTextViewを更新します。
##まずはFirebaseに登録
1.Firebase公式ドキュメントに沿って、Firebase登録とFirebase SDKをアプリに追加する。
Firebase を iOS プロジェクトに追加する
2.Firebaseに追加したアプリのコンソールからCloud Firestoreを使用できるように設定する
##AppDelegateでFirestoreの初期化
AppDelegate.swiftにFirebaseをインポート
import Firebase
didFinishLaunchingWithOptions
内でFirestoreの初期化
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let db = Firestore.firestore()
print(db)
return true
}
##ViewControllerにもFirebaseをインポート
作成を進めるファイルにもFirebaseをインポートする。
import Firebase
##データの書き込み or 更新
データの書き込み or 更新にはsetData(documentData: [String : Any], completion:)
を使用します。
db.collection("users").document("HiroshiTachi").setData([
"name": name,
"age": age,
]) { error in
if let error = error {
print("ドキュメントの書き込みに失敗しました:", error)
} else {
print("ドキュメントの書き込みに成功しました")
}
}
setData
での書き込みの場合は、ドキュメント内に何もデータがない場合は新規作成を行い、すでにデータがある場合はそのデータの中身を更新します。
##データの追加
データの追加にはaddDocument(data: , completion: )
を使用します。
var ref: DocumentReference? = nil
ref = db.collection("users").addDocument(data:[
"name": name,
"age": age,
]) { error in
if let error = error {
print("ドキュメントの追加に失敗しました:", error)
} else {
print("ドキュメントの追加に成功しました:", ref?.documentID as Any)
}
}
こちらはsetData
とは違い、指定したcollection
内に問答無用でどんどん新規追加してくれます。
なので、データを更新したりするのには使用出来ません。
##データを1回取得
データを1回取得するには、getDocuments()
を使用します。
db.collection("users").getDocuments() { (querySnapshot, error) in
if let error = error {
print("ドキュメントの取得に失敗しました:", error)
} else {
print("ドキュメントの取得に成功しました")
for document in querySnapshot!.documents {
let data = document.data()
//取得したデータに対しての処理を書く
print(data["name"])
}
}
}
}
##データをリアルタイムで取得する
データが更新される度にリアルタイム取得をするには、addSnapshotListener(listener:)
を使用します。
###1.グローバル変数としてリスナーListenerRegistration
を定義
var listener: ListenerRegistration?
###2.リスナーをアタッチ
listener = db.collection("users").addSnapshotListener { documentSnapshot, error in
if let error = error {
print("ドキュメントの取得に失敗しました", error)
} else {
self.queriedDataArray = []
if let documentSnapshots = documentSnapshot?.documents {
for document in documentSnapshots {
let data = document.data()
//アップデートされた際に行いたい処理を書く
print(data["name"])
}
}
}
}
}
}
###3.リスナーを切り離し
リスナーは必要がなくなったら切り離し(デタッチ)しましょう。
listener?.remove()
公式ドキュメントより
データをリッスンする必要がなくなったら、イベント コールバックが呼び出されないようにリスナーをデタッチしなければなりません。これにより、クライアントは更新を受信するための帯域幅の使用を停止できます。
##まとめ
公式ドキュメントの情報は本当に充実しているので、この記事より遥かに充実して間違いのない情報が得られますので、参考にする際は是非とも公式ドキュメントを参考にしていただければと思います。笑
また、セキュリティルールについてはまだまだなので知識を深めていきたい。
何か間違いがありましたら、優しく訂正していただけると幸いです🙇♂️
##参考
Cloud Firestore を使ってみる
Cloud Firestore にデータを追加する
Cloud Firestore でデータを取得する
Cloud Firestore でリアルタイム アップデートを入手する
##サンプルアプリのコード全体
import UIKit
import Firebase
class FirestoreViewController: UIViewController {
@IBOutlet weak var queryDataTableView: UITableView!
@IBOutlet weak var setTextLabel: UILabel!
@IBOutlet weak var setAgeTextField: UITextField!
@IBOutlet weak var setButton: UIButton!
@IBOutlet weak var addTextField: UITextField!
@IBOutlet weak var addAgeTextField: UITextField!
@IBOutlet weak var addButton: UIButton!
@IBOutlet weak var getButton: UIButton!
@IBOutlet weak var realTimeQuerySwitch: UISwitch!
let db = Firestore.firestore()
var queriedDataArray = [String()]
var listener: ListenerRegistration?
override func viewDidLoad() {
super.viewDidLoad()
queryDataTableView.dataSource = self
addButton.layer.cornerRadius = 15
setButton.layer.cornerRadius = 15
getButton.layer.cornerRadius = 15
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
listener?.remove()
}
@IBAction func didTapButton(_ sender: UIButton) {
switch sender.currentTitle {
//SetButton Action
case "Set":
guard let name = setTextLabel.text, let age = setAgeTextField.text else { return }
db.collection("users").document("HiroshiTachi").setData([
"name": name,
"age": age,
]) { error in
if let error = error {
print("ドキュメントの書き込みに失敗しました:", error)
} else {
print("ドキュメントの書き込みに成功しました")
}
}
//AddButton Action
case "Add":
guard let name = addTextField.text, let age = addAgeTextField.text else { return }
var ref: DocumentReference? = nil
ref = db.collection("users").addDocument(data:[
"name": name,
"age": age,
]) { error in
if let error = error {
print("ドキュメントの追加に失敗しました:", error)
} else {
print("ドキュメントの追加に成功しました:", ref?.documentID as Any)
}
}
//GetButton Action
default:
queriedDataArray = []
db.collection("users").getDocuments() { (querySnapshot, error) in
if let error = error {
print("ドキュメントの取得に失敗しました:", error)
} else {
for document in querySnapshot!.documents {
let data = document.data()
guard let name = data["name"] as? String, let age = data["age"] as? String else {
return
}
let nameAndAge = name + " " + age + "歳"
self.queriedDataArray.append(nameAndAge)
DispatchQueue.main.async {
self.queryDataTableView.reloadData()
}
}
}
}
}
}
@IBAction func didChangeRealTimeQueryState(_ sender: UISwitch) {
if sender.isOn {
print("リアルタイムアップデートOn")
listener = db.collection("users").addSnapshotListener { documentSnapshot, error in
if let error = error {
print("ドキュメントの取得に失敗しました", error)
} else {
self.queriedDataArray = []
if let documentSnapshots = documentSnapshot?.documents {
for document in documentSnapshots {
let data = document.data()
if let name = data["name"] as? String, let age = data["age"] as? String {
let nameAndAge = name + " " + age + "歳"
self.queriedDataArray.append(nameAndAge)
DispatchQueue.main.async {
self.queryDataTableView.reloadData()
}
}
}
}
}
}
} else {
print("リアルタイムアップデートOff")
listener?.remove()
}
}
}
extension FirestoreViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return queriedDataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = queryDataTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = queriedDataArray[indexPath.row]
return cell
}
}