Swift Playgroundsで学ぶiOSプログラミング

サイズが不ぞろいなセルを表示するプログラム

文●柴田文彦 編集●吉田ヒロ

2018年04月18日 17時00分

カスタムなコレクションビューセルのクラスを定義する

 実は、コレクションビューのセルに任意の内容を表示したい場合、これまでのプログラムで使っているように、セルのcontentViewにaddSubviewするのは、正しい方法とは言えないのです。再利用されたセルは、サブビューとして以前のビュー(この場合はイメージビュー)を持っているので、そこにさらに新たなビューの内容が重なってしまうのです。すべてサイズと位置が同じなら、重なっても気づかないのですが、このようにサイズをダイナミックに変更すると、とたんに不具合が露呈してしまうわけです。

 それでは、セルの中身を設定する正しい方法とは何でしょうか。それは、セル自体をカスタムなクラスとして定義し、そのオブジェクトを使う方法です。この場合は、セルの中身として表示するのはイメージビュー1つなので、最初からプロパティとしてイメージビューを持ったセルのクラスを定義し、そのイメージビューをあらかじめセルのビューに張り付けておきます。

 このようなクラスは、UICollectionViewCellを継承するクラスとして定義できます。ここではプログラムの先頭でImageCellクラスを定義しました。

コレクションビューを適切に利用するには、セルをカスタムなクラスとして定義する必要があります。ここでは、UICollectionViewCellのサブクラスとしてImageCellを定義しています。このクラスのオブジェクトを初期化する際にイメージビューを作成してセルに張り付けています

 このクラスを利用するには、元のプログラムの2箇所を変更する必要があります。

 まず、CollectionViewControllerのviewDidLoadメソッドの中で、再利用可能なセルとしてのIDを設定している部分です。ここは、元のUICollectionViewCell.selfの代わりに、今定義したクラスのImageCell.selfを指定します。

再利用するセルのクラスも、これまでの一般的なUICollectionViewCellから上で定義したImageCellに置き換えます

 もう1箇所はcellForItemAtメソッドの中で、セルに画像をセットする部分です。これまでは、イメージビュー(UIImageView)のオブジェクトごと張り付けていましたが、カスタムなセルには最初からイメージビューが張り付けてあるので、その中身イメージ(UIImage)だけを設定します。また、セルの中身のイメージビューのサイズ(frame)も、毎回セルのサイズ(bounds)に合わせて設定しています。

セルの中身の設定は、これまでのようなイメージビュー(UIImageView)ではなく、単なるイメージ(UIImage)のオブジェクトを作成し、それをカスタムなセルがもともと持っているイメージビューのimageプロパティとしてセットするようにします

 あとは元のプログラムと同じです。動かして見ると、最初は前と同じコレクションビューが表示されます。

カスタムなセルのクラスを定義して使うことで、大きさの異なるセルを再利用しても表示が乱れたり中身が重なってしまうことはなくなりました

 ただし、今度は上下に何回スクロールしても、表示が乱れることはありません。

コレクションビューの周辺のマージンとセルの間隔をカスタマイズする

 これで、コレクションビューのセルのサイズを個別にダイナミックに設定することができるようになりました。ついでに、その他のカスタマイズとして、コレクションビューの表示領域の周辺のマージンと、セル同士の最小の間隔を設定してみましょう。

 前者はinsetForSectionAtというメソッドで設定します。その名のとおり実はセクションごとに設定できるのですが、この例ではセクションは1つだけなので、ビュー全体のマージンと同じことです。このメソッドに対してはUIEdgeInsetsタイプの構造体によって、上、左、下、右のマージンを別々に指定できます。

セルのサイズ以外にも、表示領域の周囲のマージンやセル同士の最小間隔をカスタマイズできます。前者はinsetForSectionAtというメソッドを定義して、UIEdgeInsetsを返すことで、後者はminimumLineSpacingForSectionAtメソッドに対してCGFloatの値を返すことでカスタマイズします

 後者の最小間隔は、minimumLineSpacingForSectionAtのメソッドによって指定します。これもセクションごとに設定可能ですが、ここでもセクションは1つだけです。返す値は単純なCGFloatで、この例では30ポイントを返しています。

 これを実行すると、ビュー周辺からのマージンと、セルの間隔がカスタマイズされた表示となります。

ついでにグレーの背景色の設定も取り払って、カスタマイズ後のコレクションビューを表示してみました。セル間の最小スペースを設定しているため、その条件を満たさない部分は表示がスキップしています

 コレクションビュー自体のサイズが小さいと、設定した条件を満たすことができないセルが出てきてしまいますが、その場合は空欄になります。ここに至っては、もはやセル自体の枠の大きさの確認は不要なので、グレーの背景色設定は外しました。

さらに効率的な動作を追求する

 今回のプログラムを、ちょっと古めの「遅い」iPadで実行すると、コレクションビューをスクロールさせる際に動作がカクカクとぎこちなくなることがあるかもしれません。それには、セルを表示するたびに新しいイメージ(UIImage)オブジェクトを作成していることも影響しているはずです。そこで、動作を最適化するために、そもそも5種類しかない画像のイメージオブジェクトを、最初から作っておくことにしましょう。

 CollectionViewControllerクラスのプロパティとして、5つのイメージオブジェクトを含むimgs配列を定義しておきます。

プログラムの動作を最適化するためにあらかじめ5種類の画像からイメージを作成し、それを配列として保持しておくことにします

 この変更によって、セルに画像を設定する部分のプログラムも簡略化できます。これまでは、indexPathの値に応じて、switch文を使って異なる画像ファイルからイメージオブジェクトを作成していたのに対し、こんどはindexPathの値に応じてimgs配列のインデックスを決めるだけでよくなります。

セルに画像をセットする部分も、配列から取り出した画像を、カスタムなセルのイメージビューのプロパティとしてセットするだけで、かなりプログラムがすっきりしました。もちろん動作もスムーズになるはずです

 この結果の画面は前とまったく同じなので省略しますが、このような変更によってコレクションビューのスクロール動作は、スムーズなものになったはずです。

次回の予定

 今回は、コレクションビューの表示、レイアウトをカスタマイズすることができました。ここまでは、単に複数の画像を並べて表示するという一方通行的な使い方でしたが、もちろんコレクションビューは双方向的な使い方もできます。つまりユーザーの操作を受け取って応答し、アクションを起こすことができるのです。次回は、そのあたりに話を進める予定です。

mobileASCII.jp TOPページへ