【iOS】スクロール可能なメニューバーを持つViewControllerをオープンソースとして公開しました

概要

スクロールできるメニューバーとスワイプで画面を切り替えることができるViewControolerのコンテナを、オープンソースとして公開しました。

We published RMPScrollingMenuBarController by open source.
RMPScrollingMenuBarController has a scrollable menu bar, and multiple view controllers for iOS.

ニュース系アプリでよく使われているUIで、弊社の料理サプリのiOSアプリのトップ画面でも使用しています。
何はともあれこちらのgifアニメをを見てもらったら分かると思います。

料理サプリトップ

自己紹介

本記事がはじめてなので本題の前に少し自己紹介を。
リクルートマーケティングパートナーズのiOSエンジニアの大島雅人と申します。
前職はSIerでBtoB向けiPadアプリの開発をしていましたが、プログラマに特化してキャリアを積んでいきたいと思い、今年1月に転職しました。入社して数日でいきなりブログ書いてますが、このライブラリは僕が作った訳ではなく、yshrktさんが作ったものです。yshrktさんがブログは書きたくないって言ってるwので、僕が代わりに書いています。今流行りのゴーストライターです。

特徴

ビュー構造

グノシー、SmartNews、はてなのPressoなどで使われている最近流行りのUIですが、実はUIKitやオープンソースのライブラリとしては提供されていませんので、このプロジェクトが初じゃないかな?と思います。 UIKit標準では提供されていません。

View構造

このライブラリのポイントは、メニューバー部分はUIScrollViewで作っていますが、コンテンツ部分は複数のUIViewControllerのセットでできているということです。コンテンツ部分もUIScrollViewで作ってしまうと、各画面の処理を分割しにくく、iOS開発にありがちな巨大なViewControllerになってしまいます。各コンテンツをViewControllerのセットとして管理することで、UITabBarControllerのように子画面に分割することができ、既存のUIViewControllerの画面をそのまま生かすこともできます。

コンテンツ部分のスクロールの実現方法

一方で、じゃあコンテンツ部分にUIScrollViewを使わないということは、スクロール処理はどうやるの?という疑問が湧いてくるかと思います。ここがもう一つのポイントで、UIViewControllerInteractiveTransitioningを使って実装しています。遷移前の画面と遷移後の画面にそれぞれ始点と終点を指定してアニメーションを設定しておきます。あとは、ドラッグのジェスチャーであるUIPanGestureRecognizerをaddしておいて、そのイベントを取得してアニメーションの時間(timeOffset)を変更するようにしています。これによってスクロールビューのようなUIを実現しています。
ビューの変更なのに時間をずらすというのはちょっとわかりにくいかもしれませんが、YouTubeなどで動画を見ているときに一時停止状態で再生位置を少しずつずらしているイメージです。(遷移前と遷移後のアニメーションのイメージを図にしておきました。)いやー、すごい。この発想はなかなかできないです。僕は、てっきりスワイプのタッチ位置をとって、その都度viewの位置を計算して少しずつずらしているのかと思っていました。

transition

ライブラリの使い方

初期化

使い方はGitHubにも書いていますが、cocoapods経由でインストールできます

pod RMPMenuBarController

セットアップ方法はオブジェクトを初期化して、UITabBarControllerのようにUIViewControllerの配列をセットするだけです。簡単ですね!

RMPScrollingMenuBarController* menuController = [[RMPScrollingMenuBarController alloc] init];
menuController.delegate = self;
NSArray* viewControllers = @[vc1, vc2, vc3, vc4, vc5];
[menuController setViewControllers:viewControllers];
UINavigationController* naviController;
naviController = [[UINavigationController alloc] initWithRootViewController:menuController];

メニュー名の生成とイベント通知

メニュー名はdelegateメソッドで設定します。初期値として配列を渡すようなアーキテクチャではなく、delegateメソッドで設定することで状況に合わせて変更できる設計にしています。

- (RMPScrollingMenuBarItem*)menuBarController:(RMPScrollingMenuBarController *)menuBarController
                           menuBarItemAtIndex:(NSInteger)index
{
    RMPScrollingMenuBarItem* item = [[RMPScrollingMenuBarItem alloc] init];
    item.title = [NSString stringWithFormat:@"Title %02ld", (long)(index+1)];
    return item;
}

選択中のviewControllerやindexなどはプロパティとして取得できます。

/** Selected view controller.
 */
@property (nonatomic, weak)UIViewController* selectedViewController;
/** Index of selected view controller.
 */
@property (nonatomic, assign)NSInteger       selectedIndex;

メニューの選択やスワイプでの画面の切り替わりの通知を受け取ることができます。メニュー経由でもスワイプでも同じです。

// 1. 画面を切り替えて表示しようとしている画面
- (void)menuBarController:(RMPScrollingMenuBarController *)menuBarController
 willSelectViewController:(UIViewController *)viewController
{
    NSLog(@"will select %@", viewController);
}
// 2. 切り替わったあと表示されている画面の通知
- (void)menuBarController:(RMPScrollingMenuBarController *)menuBarController
  didSelectViewController:(UIViewController *)viewController
{
    NSLog(@"did select %@", viewController);
}
// 3. 切り替えようとしたけどやっぱり元の画面に戻ったときの通知
- (void)menuBarController:(RMPScrollingMenuBarController *)menuBarController
  didCancelViewController:(UIViewController *)viewController
{
    NSLog(@"did cancel %@", viewController);
}

使い方はそれほど難しくないと思いますが、詳細はGitHubにExampleプロジェクトがあるのでそれで確認していただければと思います。

ライセンス

MIT

最後に

cap-torishabu

このライブラリを使って新しいUIに修正した料理サプリをversion 3.0.0として公開しています。個人的にはいつか行ってみたいなと思っていた賛否両論の笠原さんのレシピ動画が載っててこりゃすごいな1)もちろん他の方もすごい有名人ばかりですがと思いました!鶏のしゃぶしゃぶとか超うまそう。

コントリビュートもお待ちしていますので、お気軽にプルリクなどして頂ければと思います!

脚注

脚注
1 もちろん他の方もすごい有名人ばかりですが
関連職種の採用情報
詳しくはこちら