Edited at

SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた


はじめに

今回はFirebaseがとても使いやすいと聞いたので試しに簡単にアプリを作ってみました。

備忘録作成アプリというのは「このエラー前も出たけどなんだっけ」だったり「このライブラリのこの関数どうやって使うんだっけ」と言ったときもう一度調べ直すのは面倒臭いので、それを備忘録として作成できるアプリです。この記事ではどのように開発していったか説明していきます。まだ初心者ですので指摘等あったらお願いします。PythonもやっているのでPythonの備忘録を例としてやっていきます。


ユーザー管理


ログイン画面

スクリーンショット 2019-11-01 20.14.06.png

@IBAction func logInButtonPushed(_ sender: Any) {

let mail = mailField.text!
let password = passwordField.text!
Auth.auth().signIn(withEmail: mail, password: password) { (result, error) in
if error == nil, let result = result, result.user.isEmailVerified {
self.performSegue(withIdentifier: "toMainView", sender: result.user)
} else if error != nil {
let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}

これはログインボタンを押したときの処理です。実際にログインするところはAuth.auth().signIn(withEmail: mail, password: password)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてresult.user.isEmailVerifiedでユーザーのメール認証が終わっているかを確認します。

else if error != nilでパスワードとメールアドレスが正しいかを確認し、正しくない場合はアラートを出すようにしています。


登録画面

スクリーンショット 2019-11-01 20.14.52.png

@IBAction func registerButtonPushed(_ sender: Any) {

let userMail = mailField.text!
let userPassword = passwordField.text!
if userPassword.count < 6 {
let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
auth.createUser(withEmail: userMail, password: userPassword) { (result, error) in
if error == nil, let result = result {
result.user.sendEmailVerification { (error) in
if error == nil {
let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
} else if error != nil {
let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}

これは登録ボタンを押したときの処理です。Firebaseはパスワードが6文字以上でないとエラーになってしまうのでif userPassword.count < 6とすることでユーザーに何がエラーかをわかるようにしています。

ユーザーを作成するところはauth.createUser(withEmail: userMail, password: userPassword)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてメールを送る処理はresult.user.sendEmailVerificationになっています。

エアーが出た場合はアラートを出すことでユーザーに知らせます。


ログインしているか確認

これは起動したとき最初に出てくる画面です。

@IBAction func startButtonPushed(_ sender: Any) {

if auth.currentUser != nil{
auth.currentUser?.reload(completion: { error in if error == nil{
if self.auth.currentUser?.isEmailVerified == true{
self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
}else if self.auth.currentUser?.isEmailVerified == false{
let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}})

}else if auth.currentUser == nil{
self.performSegue(withIdentifier: "toLogInView", sender: nil)
}
}

auth.currentUser != nilで端末でユーザーが存在しているかをチェックしていて存在していなければ、ログイン画面に、ログインしていたら普通の画面に飛ばします。また、存在してもメール認証が完了していない場合があるので、self.auth.currentUser?.isEmailVerified == falseとすることでメール認証が完了していなかったときは、アラートを出して、メールを確認するように促します。


ログアウト

@IBAction func logOut(_ sender: Any) {

let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
try? Auth.auth().signOut()
let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
self.navigationController?.pushViewController(StartViewController, animated: true)
}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)

}

try? Auth.auth().signOut()で実際にログアウトします。ログインする前にアラートで確認します。このボタンは登録画面とログイン画面とスタート画面以外に全て配置しました。

alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {

(action: UIAlertAction!) -> Void in

この部分でアラートのOKボタンが押されたときの処理を書きます。そしてログアウトし終わったときself.navigationController?.pushViewController(StartViewController, animated: true)でスタート画面に戻るようにします。これをしないとそのまま操作ができてしまうからです。


備忘録画面

スクリーンショット 2019-11-01 10.54.57.png スクリーンショット 2019-11-01 11.03.21.png

左が備忘録一覧で、右が備忘録追加画面になっています。


データの読み込み

override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)
database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
for document in snapshot.documents{
let data = document.data()
let dictionaryName = data["dictionaryName"]
let dictionaryWord = data["dictionaryWord"]
self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
let keys = Array(self.dictionary.keys)
self.dictionaries = []
for key in keys{
self.dictionaries.append(key)
}
}
self.dictionaryList.reloadData()
}}
}

データベースから備忘録を読み込む処理です。コレクションの名前をユーザーIDにすることでそれぞれのユーザの備忘録を表示できます。

ここで忘れてはいけないのが、viewWillAppearよりもTableViewの読み込みが行われてしまうので、dictionariesを定義するときvar dictionaries: Array<String>!としまうと最初に読み込む時にcellの数を渡すときにdictionaries.countのところでnilとなってしまいエラーが起きてしまいます。それを防ぐためには、var dictionaries: Array<String> = []と最初に空のリストを代入し、nilが出ないようにします。そしてデータベースから取得した後dictionaryList.reloadData()とTableViewを更新することで最初に起動したときにDatabaseの中身を表示できます。


検索機能

次に検索機能について説明します。


func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.view.endEditing(true)
searchBar.showsCancelButton = true
self.searchResult = dictionaries.filter{
$0.lowercased().contains(searchBar.text!.lowercased())
}

self.dictionaryList.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
self.view.endEditing(true)
searchBar.text = ""
self.dictionaryList.reloadData()
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
searchBar.showsCancelButton = true
return true
}

$0.lowercased().contains(searchBar.text!.lowercased()) でリストの中から大文字小文字どちらも検索できます。検索したものをsearchResultに追加します。

searchBarCancelButtonClickedでキャンセルボタンの処理を、searchBarShouldBeginEditingでキャンセルボタンを表示させる処理を書きます。


func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if dictionarySearchBar.text! == ""{
return dictionaries.count
}
else{
return searchResult.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
if dictionarySearchBar.text! == ""{
cell.dictionaryName.text = self.dictionaries[indexPath.row]
}else{
cell.dictionaryName.text = self.searchResult[indexPath.row]
}
return cell
}

重要なのがこの部分です。検索しない時はdatabaseから取得したdictionariesをTableViewに表示させればいいのですが、検索したとき表示させたいものはsearchResultというリストなのでdictionarySearchBar.text! == ""で検索欄の中身に何もテキストがない時はdictionariesを表示し検索欄にテキストが入っている時はsearchResultの方を表示させます。


削除機能

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {

if editingStyle == UITableViewCell.EditingStyle.delete{
let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
if self.dictionarySearchBar.text == ""{
self.dictionaries.remove(at: indexPath.row)
let keys = Array(self.dictionary.keys)
self.dictionary[keys[indexPath.row]] = nil
self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
}else{
if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
self.dictionaries.remove(at: index)
}
self.dictionary[self.searchResult[indexPath.row]] = nil
self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
self.searchResult.remove(at: indexPath.row)
}
tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}

削除するときにも検索欄にテキストが入っているかによって処理を分けます。

検索欄にテキストがない場合は、

1:self.dictionaries.remove(at: indexPath.row)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

2:self.dictionary[keys[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

3:self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()でDatabase内のデータを削除

4:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除

検索欄にテキストがある時は、

1:self.searchResult[indexPath.row])で検索結果のリストから削除

2:self.dictionaries.remove(at: index)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

3:self.dictionary[self.searchResult[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

4:self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()でDatabase内のデータを削除

5:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除


備忘録追加画面

@IBAction func addDictionary(_ sender: Any) {

let dictionaryName = dictionaryNameField.text!
dictionaryNameField.text! = ""
database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
self.allData = []
print(snapshot.documents)
for documet in snapshot.documents{
let data = documet.data()
self.allData.append(data["dictionaryName"] as! String)
}
if self.allData.contains(dictionaryName) || dictionaryName == ""{ let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}else{
let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
saveDocument.setData([
"dictionaryName": dictionaryName,
"dictionaryWord": [:] as! Dictionary<String, String>
]){ error in if error == nil{
self.navigationController?.popViewController(animated: true)
}}
}
}

}
}

ここで重要なのが同じ名前がすでに存在しているときや空欄の時は追加できないようにすることです。

self.allData.contains(dictionaryName) || dictionaryName == ""allDataというすべての備忘録の名前が入っているリストの中にtextFieldから取得したdictionaryNameが入っている時と何もない時にアラートを出すようにしています。


言葉追加画面

ここでは備忘録内に実際に言葉を追加する画面です。

ここの処理は備忘録を追加する処理と似ているので最後にコードを載せるだけにしておきます。

スクリーンショット 2019-11-01 18.29.35.pngスクリーンショット 2019-11-01 18.29.22.png


デモ

画面収録-2019-11-01-18.35.20.gif

画面収録-2019-11-01-18.33.09.gif


参考

FirebaseでiOS版簡易SNSを作成する。(メール認証編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 前編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 後編)

@fummicc1-subさんの記事はとても参考になりました。とても感謝してます!


これから

今考えているのは備忘録を他の人に共有できるような機能もつけたいと考えています。

最後まで読んでいただきありがとうございました!

指摘、修正等あったらコメントでお願いします!


コード

StartViewController.swift(最初の画面)

import UIKit

import Firebase

class StartViewController: UIViewController {

var auth: Auth!

override func viewDidLoad() {
super.viewDidLoad()
auth = Auth.auth()
}
@IBAction func startButtonPushed(_ sender: Any) {
if auth.currentUser != nil{
auth.currentUser?.reload(completion: { error in if error == nil{
if self.auth.currentUser?.isEmailVerified == true{
self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
}else if self.auth.currentUser?.isEmailVerified == false{
let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}})

}else if auth.currentUser == nil{
self.performSegue(withIdentifier: "toLogInView", sender: nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainView"{
let user = sender as! User
let MainViewController = segue.destination as! ViewController
MainViewController.me = AppUser(data: ["userId": user.uid])
}
}

}


LogInViewController.swift(登録画面)

import UIKit

import Firebase

class LogInViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var mailField: UITextField!
@IBOutlet weak var passwordField: UITextField!
@IBOutlet weak var logInButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
mailField.delegate = self
passwordField.delegate = self
}
@IBAction func logInButtonPushed(_ sender: Any) {
let mail = mailField.text!
let password = passwordField.text!
Auth.auth().signIn(withEmail: mail, password: password){ (result, error) in if error == nil, let result = result, result.user.isEmailVerified{
self.performSegue(withIdentifier: "toMainView", sender: result.user)
}else if error != nil{
let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toMainView"{
let user = sender as! User
let MainViewController = segue.destination as! ViewController
MainViewController.me = AppUser(data: ["userId": user.uid])

}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}


RegisterViewController.swift(登録画面)

import UIKit

import Firebase
import FirebaseAuth

class RegisterViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var mailField: UITextField!
@IBOutlet weak var passwordField: UITextField!
@IBOutlet weak var registerButton: UIButton!
var auth: Auth!
var me: AppUser!

override func viewDidLoad() {
super.viewDidLoad()
auth = Auth.auth()
mailField.delegate = self
passwordField.delegate = self
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
@IBAction func registerButtonPushed(_ sender: Any) {
let userMail = mailField.text!
let userPassword = passwordField.text!
if userPassword.count < 6{
let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
auth.createUser(withEmail: userMail, password: userPassword){ (result, error) in
if error == nil, let result = result {
result.user.sendEmailVerification(completion: { (error) in if error == nil{
let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}})
}else if error != nil{
let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}


ViewController.swift(備忘録一覧画面)

import UIKit

import GoogleSignIn
import Firebase
import FirebaseFirestore

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UINavigationControllerDelegate, UISearchBarDelegate {

var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
@IBOutlet weak var dictionaryList: UITableView!
var me: AppUser!
var database: Firestore!
var searchResult: [String]!
var dictionaries: Array<String> = []
let dictionarySearchBar: UISearchBar = UISearchBar()

override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
database = Firestore.firestore()
dictionarySearchBar.delegate = self
dictionaryList.delegate = self
dictionaryList.dataSource = self
dictionaryList.register(UINib(nibName: "DictionaryTableViewCell", bundle: nil), forCellReuseIdentifier: "DictionaryTableViewCell")

// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
for document in snapshot.documents{
let data = document.data()
let dictionaryName = data["dictionaryName"]
let dictionaryWord = data["dictionaryWord"]
self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
let keys = Array(self.dictionary.keys)
self.dictionaries = []
for key in keys{
self.dictionaries.append(key)
}
}
self.dictionaryList.reloadData()
}}

}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.view.endEditing(true)
searchBar.showsCancelButton = true
self.searchResult = dictionaries.filter{
$0.lowercased().contains(searchBar.text!.lowercased())
}

self.dictionaryList.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
self.view.endEditing(true)
searchBar.text = ""
self.dictionaryList.reloadData()
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
searchBar.showsCancelButton = true
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if dictionarySearchBar.text! == ""{
return dictionaries.count
}
else{
return searchResult.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
if dictionarySearchBar.text! == ""{
cell.dictionaryName.text = self.dictionaries[indexPath.row]
}else{
cell.dictionaryName.text = self.searchResult[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
dictionarySearchBar.placeholder = "辞書を検索"
return dictionarySearchBar
}
@IBAction func goToAddView(_ sender: Any) {
performSegue(withIdentifier: "toAddView", sender: dictionary)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddView"{
let AddViewController = segue.destination as! AddDictionaryViewController
AddViewController.me = me
}else if segue.identifier == "toDictionaryView"{
let DictionaryViewController = segue.destination as! DictionaryViewController
DictionaryViewController.dictionaryName = (sender as! String)
DictionaryViewController.me = me

}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete{
let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
if self.dictionarySearchBar.text == ""{
self.dictionaries.remove(at: indexPath.row)
let keys = Array(self.dictionary.keys)
self.dictionary[keys[indexPath.row]] = nil
self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
}else{
if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
self.dictionaries.remove(at: index)
}
self.dictionary[self.searchResult[indexPath.row]] = nil
self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
self.searchResult.remove(at: indexPath.row)
}
tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}
@IBAction func logOut(_ sender: Any) {
let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
try? Auth.auth().signOut()
let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
self.navigationController?.pushViewController(StartViewController, animated: true)
}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)

}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let keys = Array(self.dictionary.keys)
if dictionarySearchBar.text == ""{
performSegue(withIdentifier: "toDictionaryView", sender: keys[indexPath.row])
}else{
performSegue(withIdentifier: "toDictionaryView", sender: searchResult[indexPath.row])
}

}

}


AddDictionaryViewController.swift(備忘録追加画面)

import UIKit

import Firebase
import FirebaseFirestore

class AddDictionaryViewController: UIViewController, UITextFieldDelegate {

var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
var database: Firestore!
var me: AppUser!

@IBOutlet weak var dictionaryNameField: UITextField!
@IBOutlet weak var dictionaryLabel: UILabel!
@IBOutlet weak var addButton: UIButton!
var allData: Array<String> = []

override func viewDidLoad() {
super.viewDidLoad()
database = Firestore.firestore()
dictionaryNameField.delegate = self
}

@IBAction func addDictionary(_ sender: Any) {
let dictionaryName = dictionaryNameField.text!
dictionaryNameField.text! = ""
database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
self.allData = []
print(snapshot.documents)
for documet in snapshot.documents{
let data = documet.data()
self.allData.append(data["dictionaryName"] as! String)
}
if self.allData.contains(dictionaryName) || dictionaryName == ""{ let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}else{
let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
saveDocument.setData([
"dictionaryName": dictionaryName,
"dictionaryWord": [:] as! Dictionary<String, String>
]){ error in if error == nil{
self.navigationController?.popViewController(animated: true)
}}
}
}

}

}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
@IBAction func logOut(_ sender: Any) {
let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
try? Auth.auth().signOut()
let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
self.navigationController?.pushViewController(StartViewController, animated: true)
}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)

}
}


AddDictioaryWordViewController.swift(備忘録内言葉追加画面)

import UIKit

import Firebase

class AddDictionaryWordViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var wordField: UITextField!
@IBOutlet weak var explanationView: UITextView!
var dictionaryName: String!
var me: AppUser!
var database: Firestore!
var dictionaryWords: Dictionary<String, String>!
var allData: Array<String>!
override func viewDidLoad() {
super.viewDidLoad()
database = Firestore.firestore()
wordField.delegate = self
explanationView.layer.cornerRadius = 5
explanationView.layer.masksToBounds = true
setupTextView()
}
@IBAction func addButtonPushed(_ sender: Any) {
let wordName = wordField.text!
let wordExplanation = explanationView.text!
database.collection(me.userId).document(dictionaryName).getDocument{ (document, error) in if error == nil, let document = document{
let data = document.data()!
let words = data["dictionaryWord"] as! Dictionary<String, String>
self.allData = Array(words.keys)
}
if self.allData.contains(wordName) || wordName == ""{
let alert = UIAlertController(title: "同じ名前の辞書は追加できません。", message: "その名前の辞書は既に存在しています。", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}else{
let saveDocument = self.database.collection(self.me.userId).document(self.dictionaryName)
self.dictionaryWords.updateValue(wordExplanation, forKey: wordName)
saveDocument.setData([
"dictionaryName": self.dictionaryName!,
"dictionaryWord": self.dictionaryWords!
]){error in if error == nil{
self.navigationController?.popViewController(animated: true)
}}

}
}

}
func setupTextView() {
let toolBar = UIToolbar()
let flexibleSpaceBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
toolBar.items = [flexibleSpaceBarButton, doneButton]
toolBar.sizeToFit()
explanationView.inputAccessoryView = toolBar
}
@objc func dismissKeyboard() {
explanationView.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
@IBAction func logOut(_ sender: Any) {
let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
try? Auth.auth().signOut()
let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
self.navigationController?.pushViewController(StartViewController, animated: true)
}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}


DictionaryWordViewController.swift(備忘録内言葉画面)

import UIKit

import Firebase

class DictionaryWordViewController: UIViewController {

@IBOutlet weak var wordNameLabel: UILabel!
@IBOutlet weak var wordExplanationView: UITextView!
var wordName = ""
var wordExplanation = ""
override func viewDidLoad() {
super.viewDidLoad()
wordNameLabel.text! = wordName
wordExplanationView.text! = wordExplanation

}
@IBAction func logOut(_ sender: Any) {
let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
(action: UIAlertAction!) -> Void in
try? Auth.auth().signOut()
let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
self.navigationController?.pushViewController(StartViewController, animated: true)
}))
alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}

}


User.swift(ユーザーのモデル作成)

import Foundation

struct AppUser {
let userId: String

init(data: [String: Any]) {
userId = data["userId"] as! String
}
}