[iOS] ニュース系アプリのユーザインタフェースの実装

# iOS のニュース系アプリ

日常的に iPhone で利用しているニュース系アプリの多くが類似したユーザインタフェースを採用しているのを見て、
汎用的なユーザインタフェースのクラスを作ってみたくなった。
以下、「ニュースパス」「グノシー」「スマートニュース」「ハッカドール」アプリのスクリーンショットを例示する。

**ニュースパス** [（App Store）](https://itunes.apple.com/jp/app/nyusupasu-kantan-cao-zuode/id1106788059?mt=8 "NewsPass")
![ニュースパス画像](https://qiita-image-store.s3.amazonaws.com/0/62583/67d5fc1f-d822-2d7a-9c49-eb71ddda8ce5.png "ニュースパス")

**グノシー** [（App Store）](https://itunes.apple.com/jp/app/gunoshi-entamenyusu-supotsunyusumo/id590384791?mt=8 "Gunosy")
![グノシー画像](https://qiita-image-store.s3.amazonaws.com/0/62583/6f700ead-d614-b537-b4c5-ad30cc46d237.png "グノシー")

**スマートニュース** [（App Store）](https://itunes.apple.com/jp/app/sumatonyusu-quan-waidemonyusugasakusaku/id579581125?mt=8 "SmartNews")
![スマートニュース画像](https://qiita-image-store.s3.amazonaws.com/0/62583/8c7267ac-84a3-2011-d8e4-87f304f65e3f.png "スマートニュース")

**ハッカドール** [（App Store）](https://itunes.apple.com/jp/app/hakkadoru-junnishinkurosurunyusuapuri/id888231424?mt=8 "Hackadoll")
![ハッカドール](https://qiita-image-store.s3.amazonaws.com/0/62583/cdddb29e-733c-371d-695f-41dd8ea99630.png "ハッカドール")


今回は、同じような感じのユーザインタフェースを一つのクラスで切り替えられる仕組みを考えてみた。

# PageMenuController クラスの設計と実装

完成版は github に [PageMenuController](https://github.com/magickworx/PageMenuController "PageMenuController") として公開中。
すぐに動かしてみたい人は、上記 URL をクリックしてダウンロードするなり、clone するなりして、Xcode でビルドして、iOS Simulator で実行してね。

## メニュー画面の設計

いずれのニュース系アプリに共通するのが、画面上部の横スクロールするタブ形式のメニュー画面である。
今回は、スタイル指定でこのメニュー部分を切り替え可能な仕組みを設計する。
とりあえず、先に紹介したニュース系アプリと同じ見た目にするスタイルを決める（以下参照）。

```PageMenuController.h
typedef NS_ENUM(NSInteger, PMKPageMenuControllerStyle) {
  PMKPageMenuControllerStylePlain,      // like NewsPass
  PMKPageMenuControllerStyleTab,        // like Gunosy
  PMKPageMenuControllerStyleSmartTab,   // like SmartNews
  PMKPageMenuControllerStyleHackaTab    // like Hackadoll
};
```

+ メニュー画面は、一番簡単な UIScrollView に UILabel を追加する方法で実装する。
+ メニューのタイトルや背景色は、UILabel への設定で実現する。
+ メニューのタップによるコンテンツ切り替えは、UILabel 上での UITapGestureRecognizer を処理する。
+ メニュー毎に切り替わるコンテンツ部分は、UIPageViewController を利用する。
+ コンテンツ画面上での左右へのスワイプ操作での画面切り替えは、UIPageViewController の機能をそのまま利用する。

Initializer は以下の仕様にする。

```PageMenuController.h
-(instancetype)initWithControllers:(NSArray<UIViewController *> *)controllers
                         menuStyle:(PMKPageMenuControllerStyle)menuStyle
                      topBarHeight:(CGFloat)topBarHeight;
```

- __controllers__ 配列は、コンテンツ部分の UIViewController サブクラスである。
- __menuStyle__ は、上述したメニュースタイルのいずれかを指定する。
- __topBarHeight__ は、statusBar や navigationBar の高さを指定する。

詳細な実装に関しては、[PageMenuController](https://github.com/magickworx/PageMenuController "PageMenuController") の **PMKPageMenuController.m** ファイルを参照して欲しい。

## 各メニュー画面のスクリーンショットとスタイルの指定方法

PageMenuController クラスを利用したい UIViewController サブクラスで、次のコードを記述する。

```objectivec
@property (nonatomic,strong) PMKPageMenuController * pageMenuController;
```
コンテンツ管理用の配列なども準備しておく。以下のようなコードを loadView に記述する。
より具体的な記述例は、[PageMenuController](https://github.com/magickworx/PageMenuController "PageMenuController") 内の __RootViewController.m__ ファイルを参照してね。

```objectivec
// UIViewController のサブクラスを管理する配列
NSMutableArray * controllers = [NSMutableArray new];

// 以下のような感じで必要な UIViewController を追加する
UIViewController * vc = [UIViewController new];
vc.title = @"Page Title"; // このタイトルがメニューに表示されるよ
[controllers addObject:vc];

// ステータスバーの高さを求める
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
```
以下、メニュー画面のスクリーンショットと指定したスタイル名を列挙する。

**「ニュースパス」っぽいメニュー画面（PMKPageMenuControllerStylePlain を指定）**

![PMKPageMenuControllerStylePlain](https://qiita-image-store.s3.amazonaws.com/0/62583/f9c05e37-e4d3-5962-a687-4b1e8f05b7c9.png "「NewsPass」っぽいメニュー")

```objectivec
self.pageMenuController = [[PMKPageMenuController alloc]
                            initWithControllers:controllers
                            menuStyle:PMKPageMenuControllerStylePlain
                            topBarHeight:statusBarHeight];
```

**「グノシー」っぽいメニュー画面（PMKPageMenuControllerStyleTab を指定）**

![PMKPageMenuControllerStyleTab](https://qiita-image-store.s3.amazonaws.com/0/62583/d08a348f-601b-760a-44ac-9198449b78cb.png "「Gunosy」っぽいメニュー")

```objectivec
self.pageMenuController = [[PMKPageMenuController alloc]
                            initWithControllers:controllers
                            menuStyle:PMKPageMenuControllerStyleTab
                            topBarHeight:statusBarHeight];
```

**「スマートニュース」っぽいメニュー画面（PMKPageMenuControllerStyleSmartTab を指定）**

![PMKPageMenuControllerStyleSmartTab](https://qiita-image-store.s3.amazonaws.com/0/62583/69fa909e-7c16-4b64-cd1b-fa7c83e042fd.png "「SmartNews」っぽいメニュー")

```objectivec
self.pageMenuController = [[PMKPageMenuController alloc]
                            initWithControllers:controllers
                            menuStyle:PMKPageMenuControllerStyleSmartTab
                            topBarHeight:statusBarHeight];
```

**「ハッカドール」っぽいメニュー画面（PMKPageMenuControllerStyleHackaTab を指定）**

![PMKPageMenuControllerStyleHackaTab](https://qiita-image-store.s3.amazonaws.com/0/62583/df0ac40a-028f-fb9e-fdad-7ac189471845.png "「Hackadoll」っぽいメニュー")

```objectivec
self.pageMenuController = [[PMKPageMenuController alloc]
                            initWithControllers:controllers
                            menuStyle:PMKPageMenuControllerStyleHackaTab
                            topBarHeight:statusBarHeight];
```

## メニュー画面のカスタマイズ

__PMKPageMenuControllerStyleTab__ と __PMKPageMenuControllerStyleSmartTab__ スタイルの背景色を変更する場合は、UIColor オブジェクトを格納した NSArray を Initializer に引数として渡すことで実現できる。

```PMKPageMenuController.h
-(instancetype)initWithControllers:(NSArray<UIViewController *> *)controllers
                         menuStyle:(PMKPageMenuControllerStyle)menuStyle
                        menuColors:(NSArray<UIColor *> *)menuColors
                      topBarHeight:(CGFloat)topBarHeight;
```

また、各メニュー項目をカスタマイズしたい場合は、以下の Delegate が利用可能である。

```PMKPageMenuController.h
// メニューの準備が完了した後に一度だけ呼び出される
-(void)pageMenuController:(PMKPageMenuController *)pageMenuController
      didPrepareMenuItems:(NSArray<PMKMenuItem *> *)menuItems;
// メニュー項目がタップされた際に呼び出される
-(void)pageMenuController:(PMKPageMenuController *)pageMenuController
        didSelectMenuItem:(PMKMenuItem *)menuItem
              atMenuIndex:(NSUInteger)index;
```

Delegate で引き渡される __PMKMenuItem__ は、次のように定義してある。
ただし、_badgeValue_ と _borderColor_ は、__PMKPageControllerStyleHackaTab__ の時だけ有効である。

```PMKPageMenuController.h
@interface PMKMenuItem : NSObject
@property (nonatomic,copy) NSString *   title;  // set automatically
@property (nonatomic,assign) NSInteger  tag;    // default: 0
@property (nonatomic,copy) NSString *   badgeValue; // default: nil
@property (nonatomic,strong) UIColor *  titleColor; // set automatically
@property (nonatomic,strong) UIColor *  backgroundColor; // set automatically
@property (nonatomic,strong) UIColor *  borderColor; // set automatically
@property (nonatomic,getter=isEnabled) BOOL     enabled; // default: YES
@property (nonatomic,readonly,getter=isSelected) BOOL   selected; // default: NO
@property (nonatomic,readonly) PMKPageMenuControllerStyle       menuStyle;
@end
```

# ダウンロード

[Xcode Project 一式](https://github.com/magickworx/PageMenuController "PMKPageMenuController クラス") は、github から入手できます。