Auto Layoutに準拠したUICollectionViewCellのサイジング
UICollectioView
のSelf-sizing Cell、とても便利ですよね!
ただ以下のようなケースは、単純にいかなかったりします。
- Vertical Flow Layoutを使ったグリッド表示を行う
- 各セルは画面サイズに合わせ、幅と高さを調整する
例えば画面全体にUICollectionView
を配置しこの中にセルを2列にグリッド表示したい場合、カスタムのUICollectionViewCell
のレイアウトを画面半分の大きさで作れば良さそうです。
想像がつくかと思いますが、仮にiPhone7の大きさに合わせてレイアウトしても、iPhone7 Plusでは無駄な余白ができ、iPhone SEでは2列に表示されません!
これを解決するには、UICollectionViewDelegateFlowLayout
のsizeForItemAtIndexPath()
を実装する必要があります。
(あぁ、せっかくのSelf-sizing Cellが!)
セルの幅の計算はなんとなく想像できます。
高さは?
もし、以下のようなデザイン要件だった場合はどうしますか?
- 画像を表示する
UIImageView
はAspect Ratioを1:1にする - タイトルを表示する
UILabel
の高さは最大2行表示できるよう、40ptとする
ちなみにセルのAspect Ratioを維持するように高さを求めても、期待どおりになりませんよ!
こんなときは、Auto Layoutを使って計算してみましょう!
Auto Layoutを使ったセルサイズの計算
サンプルコードはGitHubからダウンロードできます。
https://github.com/imk2o/UICatalog
デザイン要件を満たすレイアウトを準備しておきます。
次にUICollectionViewCell
のサブクラスを定義し、サイズ計算するメソッドを実装しますが、せっかくなのでprotocol extension
で自由に着脱できるようにしましょう。
import UIKit
protocol PrototypeViewSizing: class {
}
extension PrototypeViewSizing where Self: UICollectionViewCell {
/// 原型ビューに準拠した大きさを求める。
///
/// - Parameters:
/// - flowLayout: フローレイアウト
/// - nColumns: 列数
/// - Returns: 大きさを返す
func propotionalScaledSize(
for flowLayout: UICollectionViewFlowLayout,
numberOfColumns nColumns: Int
) -> CGSize {
// 幅は必ず指定のwidthに合わせ、高さはLayout Constraintに則った値とするサイズを求める
let width = flowLayout.preferredItemWidth(forNumberOfColumns: nColumns)
return self.systemLayoutSizeFitting(
CGSize(width: width, height: 0),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel
)
}
}
private extension UICollectionViewFlowLayout {
/// 列数に対するアイテムの推奨サイズ(幅)を求める。
///
/// - Parameter nColumns: 列数
/// - Returns: 幅を返す
func preferredItemWidth(forNumberOfColumns nColumns: Int) -> CGFloat {
guard nColumns > 0 else {
return 0
}
guard let collectionView = self.collectionView else {
fatalError()
}
let collectionViewWidth = collectionView.bounds.width
let inset = self.sectionInset
let spacing = self.minimumInteritemSpacing
// コレクションビューの幅から、各余白を除いた幅を均等に割る
return (collectionViewWidth - (inset.left + inset.right + spacing * CGFloat(nColumns - 1))) / CGFloat(nColumns)
}
}
あとはこれを実装したいカスタムセルクラスに付与するだけです。
class PropotionalSizingCell: UICollectionViewCell, PrototypeViewSizing {
...
}
最後にUIColletionViewDelegateFlowLayout
を実装します。
class PropotionalSizingCollectionViewController: UIViewController {
var computedCellSize: CGSize?
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(PropotionalSizingCell.nib, forCellWithReuseIdentifier: "Cell")
}
...
}
extension PropotionalSizingCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 一度計算したらキャッシュし、負荷を軽減
// TODO: landscape表示に対応している場合は再計算を行うこと
if let cellSize = self.computedCellSize {
return cellSize
} else {
// PropotionalSizingCell.nibから原型セルを生成し、2列表示に適切なサイズを求める
guard
let prototypeCell = PropotionalSizingCell.nib.instantiate(withOwner: nil, options: nil).first as? PropotionalSizingCell,
let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout
else {
fatalError()
}
let cellSize = prototypeCell.propotionalScaledSize(for: flowLayout, numberOfColumns: 2)
self.computedCellSize = cellSize
return cellSize
}
}
}
private extension PropotionalSizingCell {
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
これで、デザイン要件を満たす表示になりました!
もしもっとスマートで効率の良いやり方をご存知でしたら、コメントいただけると幸いです。
Author And Source
この問題について(Auto Layoutに準拠したUICollectionViewCellのサイジング), 我々は、より多くの情報をここで見つけました https://qiita.com/imk2o/items/f09a39ba61933da282cf著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .