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

「写真」にタッチして拡大や入れ替えを可能にするプログラム

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

2018年04月25日 17時00分

選択して拡大したセルを画面中央に表示

 上で挙げたような不満は、1つのメソッドにちょっと手を加えるだけで比較的簡単に解消できます。そのメソッドとは、ユーザーがセルをタップして選択した際に呼ばれるdidSelectItemAtです。

選択されて拡大表示された画像をタップすると、元の大きさに戻るようにするためdidSelectItemAtメソッドに手を加えます。ついでに、選択された画像がなるべく画面の中央にくるようにスクロールさせます

 この中で、まず最初に新たにタップされたセルが記憶している「選択されたセル」と同じであるかを確認します。もしそうなら、それはすでに選択されているので、選択を解除するためselectedCellにあえてnilを代入します。一致しない場合は新たなセルの選択なので、これまでと同じように選択されたセルのインデックスパスをselectedCellに記憶します。

 また最後にscrollToItemメソッドを実行し、タップされたセルの縦方向の中央が画面の中央に一致するようにコレクションビューをスクロールさせています。

 このプログラムの実行結果の見た目は前とほとんど変わりません。しかし、実際に操作してみればすぐに違いに気づくでしょう。

プログラムを動かしてみると、今度は拡大表示した画像が自動的に中央に表示されるようになりました。また拡大した画像をタップすると元の大きさに戻り、画像が1つも選択されていない初期状態に戻ります

 まずタップして選択した画像は、自動的に画面の中央に表示されます。ただし選択した画像がコレクションビューの端のものだった場合、スクロールしきれずに完全には中央にならないこともあります。また、選択した状態の画像をタップすると元の大きさに戻り、すべての画像が同じ大きさで表示される初期状態に戻ります。

画像をドラッグして順番を入れ替えられるように

 コレクションビューでも、テーブルビューと同じようにセルの順番を入れ替えることができます。しかも、ユーザーのドラッグ操作というジェスチャーを検出する機能も内蔵しているため、たった1つのメソッドを実装するだけで順番の入れ替えを実現できるのです。そのメソッドとはmoveItemAtです。

コレクションビューの中の画像をドラッグして順番を入れ替えられるようにするには、moveItemAtのメソッドを実装するだけでいいのです。ここでは、ドラッグ操作に合わせて画像の配列の中身の順番も入れ替えるようにしています

 このメソッドには、移動前のセルのインデックスパスと移動後の位置のインデックスパスが渡されてきます。そこで、このプログラムの場合にはそれに合わせて画像(UIImage)を保持している配列の要素の順番を入れ替えればいいのです。もう少し具体的に言うと、元(sourceIndexPath)の位置にあった要素を一時的に記憶してからその位置の要素を削除します。その後、記憶した要素を目的(destinationIndexPath)の位置に挿入します。これは、コレクションビュー上でユーザーがセルをドラッグして移動する際の動作を同様であることに気付くでしょう。

 このメソッドを実装するだけで、ほかには何の変更を加えなくてもコレクションビュー上のセルはドラッグして移動可能になります。

プログラムを動かして画像をドラッグしてみると、任意の位置に移動できるようになったことに気付くでしょう。もちろん、ドラッグ前の画像があった位置には他の画像が入り、ドラッグ先にはドラッグした画像が入るスペースが用意されます

 ただし、いろいろな状況で試してみるとこのままではバグが露呈してしまうことにも気付きます。選択して拡大したセルがある状態では、そのセルでもほかのセルでもドラッグすると発生します。

ここまでのプログラムのままでは、選択されて拡大表示された画像がある場合、それに絡むように画像をドラッグすると不具合が生じます。複数の画像が拡大表示のままになったり、画像が重なって表示されることもあります

 これは、選択されて拡大表示しているセルをインデックスパスで記憶しているため、ドラッグしてセルの順番が入れ替わると拡大表示しているはずのセルのインデックスがずれてしまうからです。コレクションビュー内のセルの順番を入れ替えたら、それに合わせて選択されているセルのインデックスパスも更新してやらなければならないのです。

選択されているセルとドラッグされているセルの位置関係を考慮してインデックス修正

 このような問題を解消するには、面倒でもいくつかの場合に分けて、それぞれ選択されているセルのインデックスを適切に修正する必要があります。この処理も、moveItemAtメソッドの中で実行します。

選択された画像がある状態でセルをドラッグする際には、選択されたセルとドラッグするセルとの位置関係を考慮して選択されたセルのインデックスを適切に修正してやる必要があります

 まずは、大きく2つの場合に分けます。選択されているセルがあるかないかです。これはselectedCellの値がnilかどうかで判断できます。何も選択されていない場合には、特にインデックス修正の必要はありません。選択されているセルがある場合にはさらに4つの場合に分けて処理します。プログラムの形は3つの場合に見えるかもしれませんが、どのifにも該当しない場合がありそれが4つめとなります。

 1つめは、選択された拡大表示しているセルそのものがドラッグされた場合です。そのときは選択されたセルとsourceIndexPathが一致します。この場合は移動後の選択されたセルのインデックスパスの値をdestinationIndexPathにセットすればいいのです。

 2つめと3つめは、移動前と移動後のインデックスパスが、選択されたセルのインデックスパスを挟んでいる場合です。つまり、選択されて拡大されたセルを飛び越えて移動する場合です。

 そのうち2つめは、選択されたセルより前から、選択されたセルの後に移動する場合です。そのときは、選択されたセルのインデックスを1つ小さくします。

 3つめは選択されたセルの後ろから前に移動する場合です。これは選択されたセルのインデックスを1つ大きくしてやればいいのです。

 以上のif〜else文に該当しない4つめの場合は選択されたセルの前の方だけで、または後ろの方だけで移動する場合です。この場合も、選択されたセルは移動しないのでインデックスパスの修正は必要ありません。

 特にプログラムを起動した状態は示しませんが、選択されたセルに関わる移動操作の際に以前のプログラムに見られたような不具合は解消されているはずです。

次回の予定

 今回まで、コレクションビュー使って写真を表示する例を扱ってきました。コレクションビューは、テーブルビューと同じように、もっと色々なアプリに応用できるはずですが、それはまたの機会に譲って、次回からはまたまったく新しい世界に踏み込みましょう。このところ何かと話題の「機械学習」を利用可能にするiOSのフレームワーク、CoreMLを使ってみることにします。Swift Playgroundsと機械学習縁遠いように思われるかもしれませんが、CoreMLを使えば可能になります。

mobileASCII.jp TOPページへ