【iOS実装アイデア】Viewコンポーネントの設計を考える — UICollectionViewCell編

こんにちは iOSエンジニアの muukiiです🤠

今回はView(UI)コンポーネントの開発のアイデアのひとつを紹介します。

私の経験に基づいた事例として、Viewコンポーネントのユースケースとして次のようなものがありました。

  • 単体で使用される (Viewにそのまま貼り付け)
  • UICollectionViewまたはUITableViewで複数表示される (Cellとして表示)
  • タップ可能
  • 配置する場所による見た目の変化は無し

このようなケースにたまに遭遇することがあります。

この場合Viewコンポーネントはどのように設計し、実装すべきでしょうか。

コンポーネントが持つ機能や目的は同じだとするとコードの共通化は狙えそうです。

しかし、UICollectionViewまたはUITableViewが表示先だとUICollectionViewCellまたはUITableViewCellを継承したコンポーネントにする必要があります。

また、UICollectionViewCellを継承するとUITableViewには表示できなくなりますし、UIViewに貼り付けることも困難になります。

出来れば、使う場所に依存しない形でViewコンポーネントを用意したいものです。

まずは、UIViewサブクラスとしてViewコンポーネントを作り、単体で生成可能にする

UIViewのサブクラスだと安心しますね。initを呼べばいつでも生成可能です。

任意のViewコンポーネントを貼り付けられるUICollectionViewCellを作る

対象をUICollectionViewに表示することを例として、

ViewコンポーネントはUICollectionViewCellとして作る必要があるように見えますが、実はそうでもなく、中身が空のUICollectionViewCellを作り、そこにUIViewベースのコンポーネントを貼り付ければ表示は可能となります。

普段UIImageViewやUILabelを貼り付けているのと全く同じことと言えます。

コードで表現すると次のように実装することが出来ます。

これだけのことではあるのですが、MyViewContainerCell があまりにもただのコンテナ過ぎて、直接UICollectionViewCellサブクラスに実装することが多いと思います。

しかし、ただのコンテナ でもとても大きな価値があります。

この存在により中身であるMyViewがどこにでも表示できるコンポーネントでいられるようになるからです。

あえてデメリットを挙げるとすると、UICollectionViewCellに直接実装するよりもUIViewが一枚増えてしまうことです。
 ただ、これが問題になることはほとんどないと言えるでしょう。

”ただのコンテナ” を少し一般化することを考えてみる

Viewコンポーネントを包むContainerCellを都度作るのは少し大変です。

Swiftの言語機能を活用して一般化することが出来ないかを考えてみます。

WrapperCellというUICollectionViewCellを用意しました。

WrapperCellは指定されたViewコンポーネントを自身と同じサイズで貼り付けて表示するだけの「ただのコンテナ or ラッパー」です。

WrapperCellはBackingViewMakerを持つ型をMakerとして受け付けます。

BackingViewMakerはWrapperCellが持つ中身のViewを生成する機能を持つことを表すprotocolです。

BackingViewMakerはどんなViewを生成するかassociatedTypeで指定可能ですが、そのViewはReusableViewTypeを実装していなければなりません。

ReusableViewTypeはWrapperCellに埋め込まれるViewが持つ必要があり、これはreuseされるタイミングを通知してもらうために必要なprotocolです。

チュートリアルとして、UIViewサブクラスとしてViewコンポーネントを実装します。

定義したMyViewをWrapperCellを通してCollectionViewに表示できるように登録します。

Cellをdequeueします。

WrapperCellはupdateメソッドを持っています。

updateメソッドを呼び出すことで、WrapperCellが持つ中身のViewにアクセスすることが可能となります。 (普通にpropertyアクセスしても全然問題ないです)

WrapperCellつかうとプロジェクト内におけるUICollectionViewCellサブクラスはWrapperCellのみになる

WrapperCellを使うことでどんなViewもreuseを考慮しながらUICollectionViewに表示できるようになりました。

こうなると、プロジェクト内に存在するUICollectionViewCellのサブクラスはWrapperCellだけで済むようになります。

さらにCellとして表示したいコンポーネントはUIViewサブクラスで開発可能になるので、どこにでも表示可能になります。

デザイン実装のために どこかに単体で貼り付けておき、 Build & Runのサイクルを高めるのにも役立ちそうです。

余談 : このアイデアはTextureから考えました

ASCollectionNodeに表示することができるNodeはASCellNodeですが、

ASCellNodeが生成するのはUIViewであり、ASCollectionNodeは空っぽのContainerCellに貼り付けているだけ。 (Viewが一枚増える程度ではパフォーマンスにはほとんど影響は与えないのだろう。)

という実装からの発想でした。

おわりに

コンテナを作るという設計の仕方はコードは多少増えますが、柔軟性を生みます。

今回の例では「ただのコンテナ」でしたが、装飾(オーバーレイとか)を表示する機能を持たせることも可能です。

中身とは分離しているため、中身のコードに影響を与えることなく見た目に装飾を加えることが可能になるのです。