【Swift】セル上でLabelを動的可変しながら、ImageViewを固定表示する

以前、”【Swift】セルとLabelを動的に可変させる方法”という名の記事を投稿した。

nekokichi2yos2.hatenablog.com

 

もしチャットアプリを作るなら、Label以外にも解決すべき問題がある。

 

それは、アイコン、だ。

 

ImageViewを動的に可変させる設定を施したLabelの横に配置すればいいと思っているのだろうが、ただ設定してしまうと、セルの高さがImageViewの高さに適応してしまい、Labelの大きさに適応しなくなる。

 

つまり、

  • ImageViewは固定
  • Labelは動的に可変

させるはずが、セルの高さがLabelに適応しないので、Labelが途中で途切れたように表示されてしまう。

 

これを防ぐ方法を説明していく。

 

 

完成図

 

f:id:nekokichi_yos2:20181108231132p:plain 

 

ストーリーボード

  

f:id:nekokichi_yos2:20181108231149p:plain

 

ソースコード

 

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を可変させるのは考えただけでも難しい。

 

でも、意外と簡単なんだよ。

 

とりあえず、説明していく。

 

完成図

 

f:id:nekokichi_yos2:20181108230245p:plain

 

ストーリーボード

 

f:id:nekokichi_yos2:20181108230312p:plain

f:id:nekokichi_yos2:20181108230306p:plain

 

コード

 

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

というバラバラな順番になってしまう。

 

このような事態を防ぐ方法は、下記の記事を参照くだされ。

nekokichi2yos2.hatenablog.com

【Swift】FirebaseRealtimeDatabaseにデータを保存する

 

手順 

  1. データを保存するための階層(参照URL)(リファレンス)を指定
  2. 保存したいデータを用意
  3. データを保存

 

ソースコード

//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を設定しているから..だと思う。

・関数内でしかデータベースへの保存は実行できない

・タップや何らかの機能が実行された時に保存したいデータを保存するには、データベース保存用の関数の引数にデータを指定し、関数内で保存処理を実行すればいい