30
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AutoLayout+StackViewで自動可変行テーブルビューを実装する

30
Last updated at Posted at 2019-04-11

従来、UITableViewCellの内容に応じて行の高さを算出して可変にする実装はかなりめんどくさかった。
AutoLayoutとStackViewで自動的に可変になるように実装してみた。
AutoLayoutはSnapKitを使った。

  • Xcode 10.1
  • Swift 4.2

scroll.gif

サンプルコードは以下に配置
https://github.com/yumemi-ajike/AutoFlexibleRowHeight

UITableViewCellの内部レイアウト

パターン

UITableViewCell のサブクラスとして TableViewCell を追加し、
イメージが縦に配置される VerticalTableViewCell と左横に配置される HorizontalTableViewCell をそれぞれ TableViewCell を共通の親としてサブクラス化した。

イメージの縦配置 イメージの横配置 テキストのみ1行 テキストのみ複数行 テキスト1つのみ
vertical_cell.png horizontal_cell.png vertical_singletext_cell.png vertical_text_cell.png vertical_textonly_cell.png

組み合わせはもっとたくさんあるけどサンプルコードをビルドしたら確認可能なので割愛する。

イメージの縦配置

VerticalTableViewCell クラスで実装している。

ビュー構造

UITableViewCell.contentView に以下のビュー構造で追加していて、
それぞれの UIImageViewUILabelisHidden を変更することでレイアウト内容を変えている。

  • UIStackView
    • UIImageView
    • UILabel
    • UILabel
    • UILabel
  • UIView(下部separator)

表示順

addArrangedSubview した順番に表示される。

TableViewCell.swift
final class VerticalTableViewCell: TableViewCell {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(stackView)
        stackView.addArrangedSubview(thumbnailImageView)
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(dateLabel)
        stackView.addArrangedSubview(descriptionLabel)
        
        stackView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(insets)
        }
        thumbnailImageView.snp.makeConstraints { (make) in
            make.width.equalTo(stackView.snp.width)
            // 16 : 9 and hidden priority
            make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue)
        }
    }
    ...
}

stackView.axis = .vertical で縦方向に整列するようにし、 stackView.distribution = .equalSpacing で項目間のマージンを均一にしている。
セル内側のマージンは以下で定義して stackViewedgesinset する形でセットしている。

TableViewCell.swift
let insets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)

イメージサイズ

ImageViewの表示サイズの制約は16:9で表示するようにし、
非表示時の対応としてpriorityを .defaultLow にセットしている。
これがないとAutoLayoutの警告が出るので注意。

TableViewCell.swift
        thumbnailImageView.snp.makeConstraints { (make) in
            make.width.equalTo(stackView.snp.width)
            // 16 : 9 and hidden priority
            make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue)
        }

イメージの横配置

HorizontalTableViewCell クラスで実装している。

ビュー構造

UITableViewCell.contentView に以下のビュー構造で追加。

  • UIStackView
    • UIView
      • UIImageView
    • UIStackView
      • UILabel
      • UILabel
      • UILabel
  • UIView(下部separator)

イメージの右横にテキスト行を配置する必要があるため、 UIStackView の入れ子構造とすることで実現している。
また UIImageView がひしゃげてしまったり伸びてしまったりすることを避けるため、 imageBaseView を追加することで回避している。

TableViewCell.swift
final class HorizontalTableViewCell: TableViewCell {
    private let imageBaseView = UIView()
    ...
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        let horizontalStackView = UIStackView()
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        horizontalStackView.axis = .horizontal
        horizontalStackView.alignment = .top
        horizontalStackView.distribution = .fill
        horizontalStackView.spacing = 8
        contentView.addSubview(horizontalStackView)
        
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(descriptionLabel)
        stackView.addArrangedSubview(dateLabel)
        
        horizontalStackView.addArrangedSubview(imageBaseView)
        imageBaseView.addSubview(thumbnailImageView)
        horizontalStackView.addArrangedSubview(stackView)
        
        horizontalStackView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(insets)
        }
        ...
    }
}

親のStackViewは horizontalStackView.axis = .horizontal で横方向に整列するようにし、
horizontalStackView.alignment = .top で上揃えになるようにしている。

イメージサイズ

ImageViewの表示サイズは固定で64 x 64になるように制約を設けている。

TableViewCell.swift
        imageBaseView.snp.makeConstraints { (make) in
            make.width.equalTo(64)
            make.height.greaterThanOrEqualTo(imageBaseView.snp.width).priority(UILayoutPriority.defaultLow.rawValue)
        }
        thumbnailImageView.snp.makeConstraints { (make) in
            make.size.equalTo(64)
            make.center.equalToSuperview()
        }

文字や画像のセット

各ビューに文字や画像などの情報を入れ、表示されないビューの isHidden を変更して表示を切り替えている。

TableViewCell.swift
    func configure(with item: Item) {
        thumbnailImageView.kf.setImage(with: item.imageUrl)
        isThumbnailHidden = item.imageUrl == nil
        titleLabel.text = item.title
        titleLabel.isHidden = item.title == nil
        dateLabel.text = item.date
        dateLabel.isHidden = item.date == nil
        descriptionLabel.text = item.description
        descriptionLabel.isHidden = item.description == nil
    }

動作確認

TableViewController.swiftTableViewCell.Item の配列を作ってパターンを網羅するようにしている。

TableViewController.swift
    private let items = [TableViewCell.Item(title: "Short title",
                                            date: nil,
                                            description: nil,
                                            imageUrl: nil),
                         TableViewCell.Item(title: "Short title",
                                            date: "April 7th, 2019",
                                            description: nil,
                                            imageUrl: nil),
                         ...
                         省略
                         ...
                         TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.",
                                            date: "April 7th, 2019",
                                            description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.",
                                            imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image")),
                         TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.",
                                            date: "April 7th, 2019",
                                            description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.",
                                            imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image"))]

感想

かなりシンプルになったし、コード量が激減するので嬉しい!
行高計算のめんどくささからも解放されて喜ばしいけど、AutoLayoutの制約エラーを解決していくのがメイン作業になってこれはこれで慣れが必要。。。
UIStackViewを入れ子構造にしたときにiOS10以前で発生する制約の不整合とかもあるのでその辺の解決にちょっと時間かかった。

30
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?