【Swift】セル上でLabelを動的可変しながら、ImageViewを固定表示する
以前、”【Swift】セルとLabelを動的に可変させる方法”という名の記事を投稿した。
もしチャットアプリを作るなら、Label以外にも解決すべき問題がある。
それは、アイコン、だ。
ImageViewを動的に可変させる設定を施したLabelの横に配置すればいいと思っているのだろうが、ただ設定してしまうと、セルの高さがImageViewの高さに適応してしまい、Labelの大きさに適応しなくなる。
つまり、
- ImageViewは固定
- Labelは動的に可変
させるはずが、セルの高さがLabelに適応しないので、Labelが途中で途切れたように表示されてしまう。
これを防ぐ方法を説明していく。
完成図
ストーリーボード
ソースコード
import UIKit class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var textField: UITextField! //セルに表示するテキストを格納した配列 var array = [String]() func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) //labelを生成 let label = cell.viewWithTag(1) as! UILabel //labelの行を無制限に label.numberOfLines = 0 //labelに角丸をつける label.layer.cornerRadius = 5.0 label.clipsToBounds = true //labelにテキストを代入 label.text = array[indexPath.row] return cell } @IBAction func button(_ sender: Any) { //入力したテキストを配列に格納 array.append(textField.text!) //セルをリロード tableView.reloadData() } }
注意
Labelの制約は、
- 上
- 下
- 右
- 左
- 幅
の5つ。
ImageVIewの制約は、
- 上
- 下
- 右
- 左
- 幅
- 高さ
の6つ。
以前の記事で述べたが、
- Labelの下
- ImageVIewの下
のRelationを
- Equal → Greater Than or Equal
に変更。
上記を設定しないと、だーめ。
【Swift】セルとLabelを動的に可変させる方法
LINEのようなチャットアプリを作る際、どうやってあの吹き出しを実装するかが問題。
(チャットアプリを作る際、とても苦労しました。)
動的に可変とは、LINEのメッセージみたいに、入力された内容に応じてLabelの大きさを変化させること。
つまり、その時々に応じて、柔軟にLabelを可変させること。
ただLabelを表示させるならまだいいが、セル内でLabelを可変させるのは考えただけでも難しい。
でも、意外と簡単なんだよ。
とりあえず、説明していく。
完成図
ストーリーボード
コード
import UIKit class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var textField: UITextField! //セルに表示するテキストを格納した配列 var array = [String]() func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) //labelを生成 let label = cell.viewWithTag(1) as! UILabel //labelの行を無制限に label.numberOfLines = 0 //labelに角丸をつける label.layer.cornerRadius = 5.0 label.clipsToBounds = true //labelにテキストを代入 label.text = array[indexPath.row] return cell } @IBAction func button(_ sender: Any) { //入力したテキストを配列に格納 array.append(textField.text!) //セルをリロード tableView.reloadData() } }
注意
Labelに必要な制約は、
- 上(top)
- 下(bottom)
- 右(right)
- 左(left)
- 幅(width)
の5つ。
そして、Labelの下にある設定を施す。
- ユーティリティ - 左から4つ目 - Relation
を
- Equal → Greater Than or Equal
に変更。
上記を設定しないと、動的に可変しないので注意。
【Swift】Firebaseからデータを取得する際のfor文について
下記のコードはFirebaseにデータを保存するコードである。
↓
//データベースの参照URL let ref = Database.database().reference() //Firebaseからデータを取り出す ref.child("post").observeSingleEvent(of: .value) { (snap,error) in let snapData = snap.value as? [String:NSDictionary] if snapData == nil { return } for (_,post) in snapData! { if let username = post["username"] as? String { } } }
上記の
for (_, post) in snapData! { }
が何を表しているのかを説明していく。
[“userName”:”user1”, “userName”,”user2”]をchildByAutoId()の階層に保存したとする
↓
post - fjaigejo3n3 - userName: “user1”
- ooemtlm2 - userName: “user2”
もし最初に記述したコード通りなら、
//[String : String]型の辞書 snapData = [fjaigejo3n3 : ["userName": “user1”], ooemtlm23 : ["userName": “user2”]]
という構造になる。
そして、
for (_, post) in snapData! { }
が
for (k, v) in snapData! { }
なら、
- ループ1回目:k = fjaigejo3n3, v = [“userName”: “user1”]
- ループ2回目:k = ooemtlm23, v = [“userName”: “user2”]
つまり、
- ループ1回目:_ = fjaigejo3n3, post = [“userName”: “user1”]
- ループ2回目:_ = ooemtlm23, post = [“userName”: “user2”]
というように取り出される。
そして、
if let username = post["username"] as? String { }
と書くことで、usernameに”userName”のキーに関連づけられた”user1”と”user2”を変数などに代入できる。
正直、階層名であるfjaigejo3n3とooemtlm23は使用しないので、for (_, post)と書くことで、不必要な階層名である2つを無視できる。
ただし、このforには、
- 取り出すデータの順番がバラバラ
という欠点がある。
例えば、1,2,3,4、という4つの数字が、1,2,3,4、の順番でFirebaseに保存されているとする。
もしfor文で取り出せば、
- 2,3,1,4
- 4,1,3,2
- 1,3,4,2
というバラバラな順番になってしまう。
このような事態を防ぐ方法は、下記の記事を参照くだされ。
↓
【Swift】FirebaseRealtimeDatabaseにデータを保存する
手順
- データを保存するための階層(参照URL)(リファレンス)を指定
- 保存したいデータを用意
- データを保存
ソースコード
//Firebaseにデータを保存する関数 func saveData_Firebase(_ username:String, _ message:String) { //データベースの階層URL let ref = Database.database().reference() .child("post") .child("messages") .child(roomPath).childByAutoId() //データを保存するときの辞書 let data = ["username":username, "message": message] //データベースにデータを保存 ref.setValue(data) }
・保存する階層は、"post" - "messages" - roomPath(文字列の変数) - "乱数"
・保存するのは、変数usernameと変数message
・アプリ起動時などのアクション時にデータを受け取るには、Firebaseへの保存処理を関数でまとめておくと使い回せるので便利。
補足
・refのreference()にFirebaseRealtimeDatabaseのURLを指定しなくてもいい
・なぜなら、Firebaseでプロジェクトを新規作成した時、XcodeプロジェクトのトップページにあるBundleIDを設定しているから..だと思う。
・関数内でしかデータベースへの保存は実行できない
・タップや何らかの機能が実行された時に保存したいデータを保存するには、データベース保存用の関数の引数にデータを指定し、関数内で保存処理を実行すればいい