[iOS]リスト表示→グリッド表示の切り替えをアニメーション付きで行う方法

2016.07.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは!
モバイルアプリサービス部の田中孝明です。

今回はリスト表示→グリッド表示の切り替えをアニメーション付きで行うTIPSをサンプルプログラム付きで書き記します。
商品一覧を閲覧する機能を作る際、商品の写真を強調して見せたいモード商品名とサマリーを見せたいモードの切り替えを行いたいとの要望がありました。
その際の実装例として考案したものをまとめてみました。

list grid

サンプルプログラム

以下に公開しています。

ToggleCollectionSampler

実装例

便宜上、商品の写真だけ見せたいモードをグリッド表示、商品名とサマリーを見せたいモードをリスト表示と呼称します。
上記のモードをアニメーション付きで切り替えるためにはUICollectionViewを利用します。

UICollectionViewUICollectionViewFlowLayoutを表示したいモードに合わせて切り替えることで、UITableViewのようにも利用することができます。

下記はリスト表示のUICollectionViewFlowLayoutです。
こうすると、UITableViewのような見た目にすることができます。

let layout = UICollectionViewFlowLayout()

layout.itemSize = CGSize(width: rect.size.width, height: 60)
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

return layout

下記はグリッド表示のUICollectionViewFlowLayoutです。

let layout = UICollectionViewFlowLayout()

layout.itemSize = CGSize(width: (rect.size.width - 30) / 2, height: (rect.size.width - 30) / 2)
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

return layout

あとはUICollectionViewCellを定義します。
ポイントとしてはリスト表示、グリッド表示共に同じUICollectionViewCellを利用します。
Autolayoutの制約(NSLayoutConstraint)をIBOutletで紐付けておくことで、表示変更時に制約を変更できるようにしておきます。

class ToggleCollectionCell: UICollectionViewCell {

    private var savedItem: Item?

    @IBOutlet weak var idLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var thumbnailImageView: UIImageView!
    @IBOutlet weak var bottomView: UIView!

    @IBOutlet weak var nameLabelCenterXLayoutConstraint: NSLayoutConstraint!
    @IBOutlet weak var nameLabelCenterYLayoutConstraint: NSLayoutConstraint!

    func configureWithItem(item: Item, cellType: CellType) {
        savedItem = item
        updateConstraintsWithCellType(cellType)
    }

    func updateConstraintsWithCellType(cellType: CellType) {

        if let item = savedItem {
            idLabel?.text = item.id
            nameLabel?.text = item.name
        }

        switch cellType {
        case .List:
            backgroundColor = UIColor.whiteColor()
            idLabel.textColor = UIColor.redColor()
            nameLabel.textColor = UIColor.blackColor()
            nameLabelCenterXLayoutConstraint?.constant = -100
            nameLabelCenterYLayoutConstraint?.constant = 0
            bottomView.hidden = false
        case .Grid:
            backgroundColor = UIColor.darkGrayColor()
            idLabel.textColor = UIColor.blueColor()
            nameLabel.textColor = UIColor.brownColor()
            nameLabelCenterXLayoutConstraint?.constant = 0
            nameLabelCenterYLayoutConstraint?.constant = 50
            bottomView.hidden = true
        }
    }
}

list-grid

各表示のレイアウトによってはかなりコードが煩雑になるかもですが。。。

あとは上記の設定を表示モード切り替え処理でUIViewanimateWithDurationを利用すればAutolayoutの制約に従ってUICollectionViewCellがアニメーションしながら表示が切り替わります。

@IBAction func didTapToggleButtonItem(sender: AnyObject) {

    switch cellType {
    case .List:
        cellType = .Grid
    case .Grid:
        cellType = .List
    }

    UIView.animateWithDuration(0.5, animations: { [weak self] in
        guard let `self` = self else { return }

        self.collectionView?.collectionViewLayout = self.cellType.layoutFromSuperviewRect(self.collectionView!.frame)

        self.collectionView?.visibleCells().forEach { cell in
            guard let _cell = cell as? ToggleCollectionCell else { return }

            _cell.updateConstraintsWithCellType(self.cellType)
        }

    }, completion: { [weak self] _ in
        guard let `self` = self else { return }

        self.toggleButtonItem.title = self.cellType.toggleButtonItemTitle
    })
}

最後に

今回のネタに関しては利用シーンが限定されているとは思います。
AutolayoutUIViewanimateWithDurationの組み合わせを使えばある程度のアニメーションを作成することが可能であることが今回のTIPSになります。