【iOS】カバーフローUIの無限ループを作る

どうも、
「正露丸糖衣A」を「正露丸とYEAH!」
だと本気で思っていた事で有名な東野です。

iOSにて「カバーフロー」と言われるUIデザインがあります。
iPodで曲を選ぶような、あのデザインです。
iphone_coverflow
いつしかあのデザインはiOSではサポートされなくなりましたが、
いまだに人気の高いUIデザインでもあります。

このカバーフローを自力で作った人がいて、
githubに公開されています。
https://github.com/nicklockwood/iCarousel

この中にUIViewを継承したiCarouselというクラスがありますが、
これを使うだけでカバーフローを実装出来ます、ありがたいですね。

しかしこれはカバーフローに登録したアイテム数が上限で、それ以上めくることは出来ません。
今回はiCarouselを少し改良して、
どれだけめくっても終わらない無限ループのカバーフローを作ってみたいと思います。

ListViewやScrollViewを無限ループにしたい時、
スクロールが終わったタイミングで、
Viewの最後にViewの最初を結合して、スクロール位置を戻すという手法をよくとります。

今回やる事も同じ理論です。
例えば10個のアイテムをカバーフローに登録したい時、
内部的には×20の200個のアイテムが登録されているようにして、
その真ん中の100個目にポジションを取ります。
これで開始時点から左右への滑らかなスクロールが可能になります。
スクロール終了時に、真ん中の100個目+オフセット位置にスクロール位置を再配置します。

それではソースを改造していきましょう。

まずreloadDataというメソッドです。
これはカバーフローにアイテムを登録するメソッドです。
この中の

_numberOfItems = [_dataSource numberOfItemsInCarousel:self];

を以下のように書き換えます。

_numberOfItems = [_dataSource numberOfItemsInCarousel:self] * 20;
_scrollOffset = (float)(_numberOfItems / 2);

カバーフロー内部で持つアイテム総数をx20として、
またスクロール開始位置をその真ん中にセットします。

次はloadViewAtIndexというメソッドです。
このメソッドは登録されたアイテムのindex番目のUIViewを返す役割を持っています。
この中の

view = [_dataSource carousel:self viewForItemAtIndex:index reusingView:[self dequeueItemView]];

を以下のように書き換えます

view = [_dataSource carousel:self viewForItemAtIndex:(index % (_numberOfItems / 20)) reusingView:[self dequeueItemView]];

総数がx20になっている事を一旦元に戻してindex番目のUIViewを返すようになります。

最後にstepというメソッドです。
ここでは指を離した後の自動スクロールの動作判定をステップごとに行っています。
この中の

else if (_toggle == 0.0f) {
	[self stopAnimation];
}

を以下のように書き換えます

else if (_toggle == 0.0f) {
	if (!_dragging && _numberOfItems) { 
		int offset =  (int)round(self.scrollOffset) % (_numberOfItems / 20);
 		_scrollOffset = (float)((_numberOfItems / 20) * 10 + offset);
	}
	[self stopAnimation];
}

これはスクロール終了時点で、もともとのアイテム数x10+オフセット番目にスクロール位置を再配置します。

書き換えるのはこの3ヶ所のみで無限ループさせる最低限の動きが出来ました。

アイテム幅やスクロールスピード、使い勝手などを改良する事も出来ますが、
後はお好みでお願いします。

コメントを残す

メールアドレスが公開されることはありません。