Loading
BLOG 開発者ブログ

2023年9月20日

複数のカスタムしたUITableViewCellをUITableViewで表示させる

UITableViewについて考える人

表と言ってもさまざまな形のものがありますよね。

今回はそんな表をiOSで扱った時のお話です。

目次

 

はじめに

こんにちは、自称SwiftUI愛好家、クラウドソリューショングループのtakinami.sです。

今回はSwiftUIではなく、UIKitに関する記事です。

iOSアプリ開発において、表と言えばUITableViewですよね。

iPhoneの設定画面のようないくつも項目が並んだ画面を簡単に作成することができる素晴らしいクラスです。

しかし、今回はどうしてもデフォルトのままでは実現できなさそうな表の作成が求められたので、カスタムしてみました。

 

動作確認環境

Xcode 14.3.1
Swift v5.8.1

どのようにしたか

表の元となるのはDictionary型のデータです。
valueとしてさらにDictionary型でデータが格納されているときにはタイトルとして指定した文言をセットし、valueがString型やInt型の時に指定した項目名とvalueを横並びで表示させます。
今回はサンプルとしてこのようなデータを表示させてみたいと思います。

let usersFavorite = [
  [
    "id": "1",
    "name": "ブログ書き太郎",
    "favorite": [
      "frutes": "りんご",
      "number": 3,
      "subject": "国語"
    ]
  ],
  [
    "id": "2",
    "name": "ブログ読み次郎",
    "favorite": [
      "frutes": "みかん",
      "number": 100,
      "subject": "数学"
    ]
  ]
]

そして、作成した結果の完成イメージがこちらになります。

元データにはない『飲み物』の項目がありますが、こちらは後ほど触れます。

UITableView

 

画面レイアウトの準備

まず、StoryBoardで画面レイアウトを決めていきましょう。
ViewControllerにUITableViewを配置します。

ViewControllerとUITableViewを紐づけておくのも忘れないようにしましょう。

UITableViewを配置

@IBOutlet weak var displayFavoriteTableView: UITableView!

 

さらに、今回はカスタムしたCellが必要なので、新しくxibファイルとUITableViewCellファイルをそれぞれ2つ追加します。

タイトル用のCellがこちらです。UILabelを1つ配置し、Cellの背景色を設定しました。

import UIKit

class TopicTableViewCell: UITableViewCell {

    @IBOutlet weak var topicLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

 

value用のCellがこちらです。
項目名とvalueは並べて表示する必要があるため、UILabelを2つ横並びに配置しました。

import UIKit

class DataTableViewCell: UITableViewCell {

    @IBOutlet weak var logicNameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

 

データの準備

次に、ViewController元となるデータを配列に格納していきます。Dictionary型のままUITableVIewCellに表示させようとすると順番が保証されずバラバラで表示されてしまうため、また要件として指定した文言をタイトルや項目名として使用することが指定されているためになります。

またDictionary型からkeyを元にvalueを取り出すと、Optional型として取り出されます。

今回はvalueがnilになった時を想定して、nilの場合はハイフンを表示するようにしました。

改めて、上で示した完成イメージを見てみましょう。

元データにはなかった『飲み物』の項目がハイフンで入っているのがわかりますね。

var labelTextArray : [Any] = []

// 配列に格納するための型定義
struct Item {
    var logicName : String
    var value : String
}

func prepareLabelArray() {
    for i in usersFavorite {
        labelTextArray.append("ユーザー情報")
        labelTextArray.append(Item(logicName: "id", value: "\(i["id"] ?? "-")"))
        labelTextArray.append(Item(logicName: "名前", value: "\(i["name"] ?? "-")"))

        if (i["favorite"] != nil) {
            labelTextArray.append("お気に入り")
            let favoriteInfoData = (i["favorite"] as? [String : Any])!
            labelTextArray.append(Item(logicName: "フルーツ", value: "\(favoriteInfoData["frutes"] ?? "-")"))
            labelTextArray.append(Item(logicName: "数字", value: "\(favoriteInfoData["number"] ?? "-")"))
            labelTextArray.append(Item(logicName: "教科", value: "\(favoriteInfoData["subject"] ?? "-")"))
            labelTextArray.append(Item(logicName: "飲み物", value: "\(favoriteInfoData["drink"] ?? "-")"))
        }
    }
}

余談ですが、順番が保証されたDictionary型を作成したいときは、Appleから提供されているswift-collectionsをインポートし、OrderedDictionaryを使用しましょう。

 

カスタムしたUITableViewCellの紐付け

ViewController上にあるUITableViewに先ほど作成した2つのCellを紐づけていきます。

override func viewDidLoad() {
        super.viewDidLoad()
        displayFavoriteTableView.delegate = self
        displayFavoriteTableView.dataSource = self
        displayFavoriteTableView.separatorStyle = .none  // UITableViewの区切り線をなくすかは要件などによります

        displayFavoriteTableView.register(UINib(nibName: "TopicTableViewCell", bundle: nil), forCellReuseIdentifier: "topic_cell") // タイトル用Cellのxibファイルでidentifierを設定
        displayFavoriteTableView.register(UINib(nibName: "DataTableViewCell", bundle: nil), forCellReuseIdentifier: "data_cell") // value用Cellのxibファイルでidentifierを設定 
}

 

データの表示

それでは、準備が整ったので最後にデータの表示ができるようにUITableView用の設定処理を行います。

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return labelTextArray.count // 行数を定義
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let topicCell = displayFavoriteTableView.dequeueReusableCell(withIdentifier: "topic_cell") as! TopicTableViewCell
    let dataCell = displayFavoriteTableView.dequeueReusableCell(withIdentifier: "data_cell") as! DataTableViewCell

    // 配列に格納されている要素の型によって使用するCellを切り替える
    switch type(of: labelTextArray[indexPath.row]) {
    case is String.Type:
        topicCell.topicLabel.text = labelTextArray[indexPath.row] as? String
        return topicCell

    case is Item.Type:
        dataCell.logicNameLabel.text = (labelTextArray[indexPath.row] as? Item)?.logicName
        dataCell.valueLabel.text = (labelTextArray[indexPath.row] as? Item)?.value
        return dataCell

    default:
        return dataCell
    }
}

これでシミュレータや実機で起動することで完成イメージのような画面が表示されるかと思います。

お疲れ様でした。

 

ViewControllerのコードを改めてまとめて掲載いたします。

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    struct Item {
        var logicName : String
        var value : String
    }

   @IBOutlet weak var displayFavoriteTableView: UITableView!

    var labelTextArray : [Any] = []

    let usersFavorite = [
        [
            "id": "1",
            "name": "ブログ書き太郎",
            "favorite": [
                "frutes": "りんご",
                "number": 3,
                "subject": "国語"
            ]
        ],
        [
            "id": "2",
            "name": "ブログ読み次郎",
            "favorite": [
                "frutes": "みかん",
                "number": 100,
                "subject": "数学"
            ]
        ]
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        displayFavoriteTableView.delegate = self
        displayFavoriteView.dataSource = self
        displayFavoriteTableView.separatorStyle = .none

        displayFavoriteTableView.register(UINib(nibName: "TopicTableViewCell", bundle: nil), forCellReuseIdentifier: "topic_cell")
        displayFavoriteTableView.register(UINib(nibName: "DataTableViewCell", bundle: nil), forCellReuseIdentifier: "data_cell")

        self.prepareLabelArray()
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return labelTextArray.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let topicCell = displayFavoriteTableView.dequeueReusableCell(withIdentifier: "topic_cell") as! TopicTableViewCell
        let dataCell = displayFavoriteTableView.dequeueReusableCell(withIdentifier: "data_cell") as! DataTableViewCell

        switch type(of: labelTextArray[indexPath.row]) {
        case is String.Type:
            topicCell.topicLabel.text = labelTextArray[indexPath.row] as? String
            return topicCell

        case is Item.Type:
            dataCell.logicNameLabel.text = (labelTextArray[indexPath.row] as? Item)?.logicName
            dataCell.valueLabel.text = (labelTextArray[indexPath.row] as? Item)?.value
            return dataCell

        default:
            return dataCell
    }

    func prepareLabelArray() {
        for i in usersFavorite {
            labelTextArray.append("ユーザー情報")
            labelTextArray.append(Item(logicName: "id", value: "\(i["id"] ?? "-")"))
            labelTextArray.append(Item(logicName: "名前", value: "\(i["name"] ?? "-")"))

            if (i["favorite"] != nil) {
                labelTextArray.append("お気に入り")
                let favoriteInfoData = (i["favorite"] as? [String : Any])!
                labelTextArray.append(Item(logicName: "フルーツ", value: "\(favoriteInfoData["frutes"] ?? "-")"))
                labelTextArray.append(Item(logicName: "数字", value: "\(favoriteInfoData["number"] ?? "-")"))
                labelTextArray.append(Item(logicName: "教科", value: "\(favoriteInfoData["subject"] ?? "-")"))
                labelTextArray.append(Item(logicName: "飲み物", value: "\(favoriteInfoData["drink"] ?? "-")"))
            }
        }
    }
}

 

最後に

まとめて振り返るとあまり複雑なことはしていないのですが、実装当時少し悩んだ部分であったため今回記事にしてみました。

これはiOSもAndroidもですが、HTMLのtableタグのような表を作ることをあまり考慮していないように思えます。

スマートフォンでは一度に画面上に表示できる量に制限があるので、項目名をCellに表示して詳細な内容は画面遷移をすることが求められているのだろうと私は考えています。

要件には従いつつ、できるだけネイティブで推奨されている方針に則って行けると良いですね。

 

takinamisのブログ