質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

325閲覧

い【Swift,Xcode】Firebaseのデータベースに自分で命名した階層を生成できない

nekokichi

総合スコア54

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2018/11/04 05:00

編集2018/11/04 05:01

チャットアプリとFirebaseデータベースをうまく連携できずに困っています。

TextFieldにメッセージを入力し、EnterまたはButtonを押すとFirebaseにメッセージが保存され、Firebaseに保存されている今までのメッセージ履歴を表示するようなチャットアプリを作っています。

イメージ説明

Firebase間でのデータ送受信は実装できるのですが、
・TableViewに表示されるメッセージの順番がバラバラ
という問題に直面しています。

当初は、childByAutoId()で乱数で命名された階層に保存されるようにしました。

しかし、Firebaseからデータを取り出す場合、Firebaseに保存された順番でデータを取り出すことができないのです。

そこで、指定した数字に命名された階層を作り、データを取り出す際、ソートを利用して正しい順番でデータを取り出そうと試みました。

そのために、
・次にデータを保存する場合、階層名は"データ数+1"と命名する
・階層名が小さい順に取り出す
を実装しようと考えました。

ですが、データを保存するとき&データを取り出すとき、Firebaseのデータ数を変数で取得し、次にデータを保存するときにその変数名をchild(変数)として設定したのですが、
・新たな階層が作られない
・すでに作られている階層に代入される
という事態に直面し、悩んでいます。

イメージ説明
イメージ説明
イメージ説明

上記の場合、
・1回目:aaあああ、と入力して保存
・2回目:あああああああああ、と入力して保存したら、1回目のmessageに上書き
・3回目:げwgじぇあwl;gじゃwlgじゃwl;gk、と入力して保存したら、2回目のmessageに上書き
という流れになっています。

どこが原因なのか、ご指摘願います!!

swift

1import UIKit 2import Firebase 3 4 5 6class ChatViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate { 7 8 @IBOutlet weak var tableView: UITableView! 9 @IBOutlet weak var textField: UITextField! 10 11 //Firebase関連 12 //Firebaseに保存する変数を持つクラスインスタンス 13 var Post = Object() 14 //Objectクラスのインスタンスを持つ配列 15 var Posts = [Object]() 16 17 //階層名 18 var childNumber = 0 19 20 //UDからユーザーネームを取得 21 let username = UserDefaults.standard.object(forKey: "userName") as! String 22 23 override func viewDidLoad() { 24 super.viewDidLoad() 25 26 //デリゲート 27 textField.delegate = self 28 //カスタムセルを登録 29 tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "customcell") 30 //データを取得 31 loadData_Firebase() 32 //メッセの数を取得 33 childNumber = Posts.count 34 //リロード 35 tableView.reloadData() 36 37 } 38 39 //セルの数 40 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 41 return Posts.count 42 } 43 44 //セルを読み込む 45 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 46 guard let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as? TableViewCell else { return UITableViewCell() } 47 //TableViewCellのtalk関数にユーザー名とメッセを渡す 48 cell.talk(Posts[indexPath.row].username, Posts[indexPath.row].message) 49 return cell 50 } 51 52 //メッセを投稿 53 @IBAction func send(_ sender: Any) { 54 55 //メッセが入力されてれば 56 if let _ = textField.text { 57 //Firebaseに[ユーザー名:本文]を保存 58 saveData_Firebase(username, textField.text!) 59 loadData_Firebase() 60 } else {return} 61 62 } 63 64 //Enterを押したとき 65 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 66 67 68 //メッセが入力されてれば 69 if let _ = textField.text { 70 //Firebaseに[ユーザー名:本文]を保存 71 saveData_Firebase(username, textField.text!) 72 loadData_Firebase() 73 self.childNumber = self.Posts.count 74 } else {return true} 75 76 77 return true 78 79 } 80 81 //Firebaseにデータを保存する関数 82 func saveData_Firebase(_ username:String, _ message:String) { 83 //TextFieldの値をnil 84 textField.text = "" 85 //データベースの階層URL 86 let ref = Database.database().reference(fromURL: "https://realtimechat-e6e96.firebaseio.com/").child("post").child("(childNumber)") 87 //データを保存するときの辞書 88 let data = ["username":username, "message": message] 89 //データベースにデータを保存 90 ref.setValue(data) 91 } 92 93 94 //Firebaseからデータを取得する関数 95 func loadData_Firebase() { 96 //データベースの参照URL 97 let ref = Database.database().reference(fromURL: "https://realtimechat-e6e96.firebaseio.com/") 98 //データを初期化 99 self.Post = Object() 100 self.Posts = [Object]() 101 ref.child("post").observeSingleEvent(of: .value) { (snap,error) in 102 let snapdata = snap.value as? [String:NSDictionary] 103 if snapdata == nil { 104 return 105 } 106 for (_, snap) in snapdata! { 107 self.Post = Object() 108 if let username = snap["username"] as? String, let message = snap["message"] as? String { 109 self.Post.username = username 110 self.Post.message = message 111 } 112 self.Posts.append(self.Post) 113 } 114 115 self.tableView.reloadData() 116 } 117 118 } 119 120 121 122 123} 124

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

・新たな階層が作られない
・すでに作られている階層に代入される

printすれば分かると思いますが、childNumberが更新されていません。
viewDidLoadのときしかchildNumberが更新されていないので、ずっと0のkeyで書き込みをしています。

childNumberにdidsetとかを書いてみて値の動きを見守ってみると分かりやすいかと思います。
今回の場合、Firebaseからデータをフェッチしてソースを更新したあと、self.childNumber = self.Posts.countすればいいと思います。

少し質問と少しそれますが、キーをchildByAutoIdにして、queryOrderedByKeyすれば時間順にになると思います(childByAutoId によって生成される一意のキーはタイムスタンプに基づいている)。

将来的に時間を表示したり・管理することも考慮するなら、そもそもValueの中にmessage/usernameの他にDateのタイムスタンプをIntとかで保存しておくと良いかと思います。その場合は、queryOrderedByChildでそのタイムスタンプで並び替えることもできます。

あと、現状メッセージを追加するたび全件取得して差し替えていますが、最初だけSingleEventで取得して、それ以降はobserve(.childAdded)で追加分だけAppendしていくといいと思います。

公式のリファレンスを見ながら色々試してみてください。

投稿2018/11/05 08:32

taka_jun

総合スコア160

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問