はじめに
こんにちは!Life is Tech ! #2 Advent Calendar 2020の22日目を担当しますふみっちです。
この記事は〜その1 概要編〜の続きとなっていて、Intrinsic Content Size
に関する具体的な実装を解説を行います!
プロジェクト全体はこちらからダウンロードできるので是非手元で動かしてみてください😎
Intrinsic Content Size
を用いると動的なコンテンツの変化によるサイズ調整を自動で実装できるようになるという話を〜その1 概要編〜に書かせてもらいましたが、おそらく動的なサイズ調整が必要になるケースとしてはUITableView
やUICollectionView
を使用するが多いと思います。
そこで今回はUITableView
のCellをIntrinsic Content Size
を用いてセルごとに可変なものにしていきます!
UITableViewCell × Intrinsic Content Size
TableViewやCollectionViewには一つ一つのセルの高さを可変にするという仕組みが備わっており、Self Sizing
と呼ばれています。
このSelf Sizing
は先ほど説明したIntrinsic Content Size
を用いることで手動で計算をしなくても実現することができます。
Self Sizingの具体例
今回はSelf Sizing
を使用して画像の大きさに合わせて動的な高さを持つUITableViewCellを作成する例を紹介します。
また、セルのボタンをタップするとコンテンツの表示・非表示を変更できる方法も紹介します。
Apple純正の「リマインダー」アプリでみるようなセルの表示・非表示もAppleの場合はセルの数自体を変えているように見えますが、リマインダーアプリに近いことができるようになると思います。
リマインダー |
---|
先ほどのリンクのSample2のプロジェクトに対応する内容です。
Demo |
---|
このように可変サイズを持つUIViewはIntrinsic Content Size
をうまく使うことで簡単に実現できます!
実装ポイント
Cellの内部はIntrinsic Content Size
を持つUIViewのみを配置する
Self Sizingによって自動でセルの高さを決定する場合はIntrinsic Content Size
が有効である必要があります。なのでただのUIViewやUIScrollViewをセルの内部に配置すると正しくセルがリサイズされない場合があります。
セルを可変にする際はセルに配置するUIViewに注意 |
---|
表示中のCellの高さを変更したい場合は一度セルをリロードする
Intrinsic Content Size
を用いて手軽にCellごとの高さを動的に変更する際に注意点が一つ必要です。
それは一度表示したあとにCellの高さを変更したい場合はセルを再度更新する必要があるということです。(他にも方法はありそうですがこの方法が一番簡単だったので紹介します。)
ただ、UITableView
が持つreloadData()
は全てのセルを更新してしまうので、同じくUITableView
が持つreloadRows(at:with:)
というメソッドを用いて高さの変更が必要なセルのみを更新します。
このとき、ボタンがタップされたことはセル側が検知するため、ViewController側にデリゲートを利用して処理を移譲する必要があります。
デリゲートを定義してViewControllerに処理を渡す
以下のようにセルからViewControllerに処理を移譲するためにプロトコルを作成してあげます。
protocol TableViewCellDelegate: AnyObject {
func didTapChangeVisibleButton(cell: TableViewCell)
}
このプロトコルにはdidTapChangeVisibleButton
というメソッドを定義していますが、引数にセル自身を渡してあげられるようなメソッドであれば他の名前でも大丈夫です!
ボタンタップ時にデリゲートメソッドを呼ぶ
次に上のTableViewCellDelegate
を使用してセルでボタンがタップされたことを受け取ったら、ViewController(デリゲート)側に処理を伝えます。
import UIKit
class TableViewCell: UITableViewCell {
@IBOutlet weak var randomImageView: UIImageView!
// デリゲートをプロパティとして参照する
weak var delegate: TableViewCellDelegate?
// ボタンがタップされたことをdelegate側に伝える
@IBAction func tapChangeVisibleButton() {
// 先ほど定義したメソッドを呼んでデリゲート側で処理をしてもらう
delegate?.didTapChangeVisibleButton(cell: self)
}
}
ViewController(デリゲート)側でセルを更新する
次にViewController
が先ほど定義したTableViewCellDelegate
に準拠し、セルの更新処理を行います。
import UIKit
extension ViewController: TableViewCellDelegate {
func didTapChangeVisibleButton(cell: TableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
data[indexPath.row].toggle()
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
}
先ほど紹介したtableView.reloadRows(at:with:)
を呼ぶためにセルのIndexPath
を取得する必要がありますが、引数にセル自身を渡しているのでtableView.indexPath(for: cell)
という具合に取得することができます!
// 引数に指定したセルのIndexPathが取得できる
let indexPath: IndexPath? = tableView.indexPath(for: cell)
セルに関するデータモデル
セル一つ一つに対して画像を表示するのか・しないのかに関してはセル側ではなくViewController側で管理する必要があります。
今回は「セルに画像を表示しますか?」「はい、いいえ」という知識を持っていれば良いのでvar data: [Bool] = []
というようにデータを定義します。
var data: [Bool] = [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
]
最初は画像を非表示の状態から始めたいのでfalse
としています。
UITableViewDataSource
ViewController側で実装されている以下の二つのメソッドの中身を解説していきます。
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
このメソッドはセルの個数を決めるメソッドです。
配列data
の要素数と同じにすればデータの数だけセルが表示されるようになります。(セルの数を増やしたい場合は配列の要素を増やしましょう!)
tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
このメソッドはIndexPathというUITableViewにおけるセルの所在地を元に表示するセルを決定するメソッドです。
ここのメソッドの内部では以下のコードがミソです。
let isVisible = data[indexPath.row]
if isVisible {
cell.randomImageView.image = imageArray[indexPath.row % 4]
} else {
cell.randomImageView.image = nil
}
上記の最初の行では、data
変数のセルに該当する要素を取得しています。
もしセルは画像を表示するべき(isVisible
がtrue
)であればセルに画像を表示します。
一方セルは画像を表示するべきではない(isVisible
がfalse
)であればセルも画像はnil
になります。
UIImageViewのIntrinsic Content Size
はimage
プロパティの値によって変わるため、image
をnilにすると自動的にUIImageViewの高さも0になり、画像が代入されるとその画像に適する高さにリサイズされます。
このようにして自分で高さの調整をせずとも表示するデータに応じてセルが自動的にサイズを調整してくれるわけです!
完成イメージ |
---|
最後に
この記事では〜その1 概要編〜で説明したIntrinsic Content Size
を交えた実装例を紹介しました。
Intrinsic Content Size
はマイナーな内容かもしれないですが、実は普段から意識しなくても使っているということもあると思うのでこの機会に興味を持ってもらえると嬉しいです!
最後まで読んでいただきありがとうございました‼️