【iOS】SideMenuの良さげなライブラリ「SideMenu」を試す

## SideMenu

<a href="https://github.com/jonkykong/SideMenu"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/79a1c046-de72-9242-8ec5-f4c62dabb356.png" width="460px"></a>

シンプルなサイドメニューのライブラリで、
最近もメンテされていて、Cocoapods, Carthage等からのインストールが可能なライブラリです。

↓サンプルを実際に動かしてみた様子 :eyes:
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/cdb44576-cfd6-d02a-1d78-e5aa93f73de0.gif" width="300" />

結構色々カスタマイズができそうな感じでした。 :sparkles: 

###  :computer:環境構築
****
今回は Carthage を使ってライブラリを導入したいと思います。
`Cartfile` に以下を追加し `carthage update --platform iOS` を実行します。

```
github "jonkykong/SideMenu" "master"
```

`Frameworks, Libraries, ..` に `SideMenu.framework` を、Run ScriptのInputFileに
`$(SRCROOT)/Carthage/Build/iOS/SideMenu.framework`を追加してセットアップは完了です。

### :pencil: 実装
****

#### まずは簡単なサンプルで動かしてみます

1. SideMenu用のViewControllerとStoryBoardを作成する
2. SideMenuを表示するViewControllerで以下実装を追加

```swift
    override func viewDidLoad() {
        super.viewDidLoad()

        if let sideMenuViewController = UIStoryboard.initialViewController(withClass: SideMenuViewController.self) {
            let leftMenuNavigationController = SideMenuNavigationController(rootViewController: sideMenuViewController)
            SideMenuManager.default.leftMenuNavigationController = leftMenuNavigationController
        }
        
        if let navigationController = self.navigationController {
            SideMenuManager.default.addPanGestureToPresent(toView: navigationController.navigationBar)
            SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: navigationController.view, forMenu: .left)
        }
    }
```

この時点で動作させたものがこちら ↓
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/ce276006-8ae4-ec6a-1b43-57717ae84b03.gif" width="200" />

設定は特に変更しておらずデフォルトの状態で、分かりやすい様にSideMenuの背景色を赤色にしています。

#### PresentationStyleを色々試してみる

デフォルトで `PresentationStyle` (SideMenuの動作Style) が 複数用意されているので、試して見たいと思います。
実際に試した際に使用したコードは以下になります。

```swift
import UIKit
import RxSwift
import SideMenu

class TopViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        if let sideMenuViewController = UIStoryboard.initialViewController(withClass: SideMenuViewController.self) {
            let leftMenuNavigationController = SideMenuNavigationController(rootViewController: sideMenuViewController)
            leftMenuNavigationController.settings = makeSettings()
            SideMenuManager.default.leftMenuNavigationController = leftMenuNavigationController
        }
        
        if let navigationController = self.navigationController {
            SideMenuManager.default.addPanGestureToPresent(toView: navigationController.navigationBar)
            SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: view, forMenu: .left)
        }
    }
    
    private func makeSettings() -> SideMenuSettings {
        let presentationStyle: SideMenuPresentationStyle = .menuSlideIn // ここを修正して試しました
        presentationStyle.onTopShadowOpacity = 1.0
        var settings = SideMenuSettings()
        settings.presentationStyle = presentationStyle
        settings.statusBarEndAlpha = 0
        return settings
    }
}
```

↓動作させてみて、動作的に違いのあったものを載せてみました。

|Style名|動作|
| --- | --- |
| **menuSideIn** | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/9b4b6f19-6620-abb9-3110-2c89d7d54db0.gif" width="150" /> |
| **menuDissolveIn** | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/f439afdc-4f16-b586-e73f-64a0ff0110e8.gif" width="150" /> |
| **viewSlideOut** | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/b9f74af9-d4f8-9931-6ecd-e96f5f4d68fe.gif" width="150" /> |
| **viewSlideOutMenuOut** | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/a85e17bc-688a-a2b8-cf17-0cea1e9ed3cf.gif" width="150" /> |
| **viewSlideOutMenuZoom** | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/c1129287-8d5e-b0f6-5561-bb5fb3cfea21.gif" width="150" /> |

面白いアニメーションのStyleもありますね :sparkles: 

----
#### SideMenuっぽくTableViewを設置する
完成イメージとしては以下になります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/c9550c1a-cf5e-95c4-fde7-38dd3dad612b.png" width="300" />

1. 作成したSideMenu用のStoryBoardにTableViewを画面一杯に追加する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/0df2a8f7-21d9-8add-a016-5d3d1843e27c.png" width="200" />

2. Cellを別途xibで作成し、Identifierをクラス名と同じに設定する (Cell内にLabelを追加しとく)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/4e6e65cf-213c-92a5-c2c3-90d1d08b3dd8.png" width="300" />

```swift:SideMenuTableViewCell.swift
import UIKit

class SideMenuTableViewCell: UITableViewCell {
    
    @IBOutlet weak var title: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}
```

3. TableViewをViewController側に結びつけ、諸々実装する。

```swift:SideMenuViewController.swift
import UIKit
import SwifterSwift
import RxSwift
import RxCocoa
import RxDataSources

class SideMenuViewController: UIViewController {
    
    private let disposeBag = DisposeBag()
    
    @IBOutlet weak var tableView: UITableView! {
        didSet {
            tableView.register(nibWithCellClass: SideMenuTableViewCell.self, at: SideMenuTableViewCell.self)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.rx.setDelegate(self).disposed(by: self.disposeBag)
        bindDataSources()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        if let indexPath = tableView.indexPathForSelectedRow {
            tableView.deselectRow(at: indexPath, animated: true)
        }
    }
    
    private func bindDataSources() {
        let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, String>>(configureCell: { (_, tableView, indexPath, item) in
            
            let cell = tableView.dequeueReusableCell(withClass: SideMenuTableViewCell.self, for: indexPath)
            cell.title.text = item
            return cell
        })
        dataSource.titleForHeaderInSection = { ds, index in
            return ds.sectionModels[index].model
        }
        
        let sections = [
            SectionModel<String, String>(model: "設定A", items: ["設定1", "設定2", "設定3"]),
            SectionModel<String, String>(model: "設定B", items: ["設定1", "設定2", "設定3"])
        ]

        Observable.just(sections)
            .bind(to: tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }
}

extension SideMenuViewController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
}
```
以上になります。

###  :bomb: バッドノウハウ
****
以下のwarningが出る

```
SideMenu Warning: addScreenEdgePanGesturesToPresent(toView:forMenu:) was called before rightMenuNavigationController was set.
The gesture will not work without a menu. Use addScreenEdgePanGesturesToPresent(toView:forMenu:)) to add gestures for only one menu.
```
Left Menuしか追加していないのに

```swift
SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: navigationController.view)
```
上記を呼び出すと Left / Right 両方のGesturesを設定してしまう為

```swift
SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: navigationController.view, forMenu: .left)
```
`forMenu` 付きのメソッドを呼び出す。

### :link: 参考になったURL
****
- [iOS 13 から導入されるSceneDelegateとは - Yappli Tech Blog](https://tech.yappli.io/entry/scenedelegate)
- [RxSwiftでUITableViewAlertForLayoutOutsideViewHierarchyのWARNINGが出る問題 - Qiita](https://qiita.com/shou8/items/1d3454d176865adb457f)