理想的なサイドメニューを追い求めてみた。

#### まずは完成Verの動画をご覧下さい。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428088/44edf9b6-24ac-3edf-8065-0647f73d2e37.gif)
# はじめに

サイドメニュー、ドロワーメニュー、ハンバーガーメニューなど、色々な呼称があるこの左からニョキッと出てきてくれるカッコイイ画面遷移方法。たくさんの優秀なライブラリが公開されてはいるのですが、本当は　もうちょっと早く動いて欲しかったり、もうちょっといじって自分だけのカスタムサイドメニューにしたいなと思っていたので、これを機に、ライブラリに頼らず、自前で自分だけのサイドメニューを作ってみました。

Githubにソースコードを貼って置きました。

https://github.com/masatakasumida/IdealSideMenu

### 製作にあたって
一から自分で考えて作れた訳では無く、沢山の優秀な記事を参考に、いいとこ取りで自分なりのサイドメニューを作りました。本当にありがとうございました!!
説明もわかりやすかったので、是非ご覧になって下さい。
参考記事

https://dev.classmethod.jp/articles/uipresentationcontroller-drawer-menu/

https://shiba1014.medium.com/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%81%AA%E9%81%B7%E7%A7%BB%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B-f5398711ac49

https://www.youtube.com/watch?v=cI3Bzmq4EgY&t=240s&pp=ugMICgJqYRABGAE%3D

### 機能
・「SideMenu」をタップすると画面遷移
・画面端からスワイプで画面遷移
・遷移後、画面の暗い部分をタップすると画面が閉じる
・画面を閉じる時はスワイプの速さを感知して閉じるか開くかを判断する。

### 本当は実現したかった挙動
画面端からのスワイプで、開度が半分以下の時に、

```swift:
interactionController.cancel()
```
を実行し、画面を閉じたかったのですが、画面が閉じられた瞬間に、画面がなぜか瞬間移動してしまう挙動を解決できなかったので、
```swift:
interactionController.finish()
```
を
```swift:
interactionController.completionSpeed = 2.5
```
で素早く画面遷移させる事でとりあえずの対処しました。

### 追記(2022/5/27)-画面遷移時の開度に応じて画面を閉じる、開くを自動で選択する機能を追加しました。

```swift:
interactionController.cancel()
```
この時の挙動としては
```swift:
 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return CustomAnimatedTransitioning(isPresenting: true)
    }
```
が呼ばれている状態だったので、結果的にこのメソッドのアニメーション処理完了後に、
```swift:
if toView.frame.origin.x == 0 {
                transitionContext.completeTransition(completed)
            }else {
                if transitionContext.transitionWasCancelled {
                    toView.removeFromSuperview()
                }
```
を追記しました。

アニメーション後に画面遷移が完了していない時に(toView.frame.origin.x != 0)画面を消しました。

他にもinteractionController.cancel()時にfunc animateDissmissalTransitionに分岐させる方法など色々考えてみましたが、どれも上手くいかず、上記の方法になりました。

他に綺麗なコードの書き方があれば教えてほしいです。🙇‍♂️

### 追記(2022/6/2)画面遷移後、画面を閉じる際に、画面外からのスワイプでも画面を閉じる機能を追加しました。

PresentationControllerにUIPanGestureRecognizerを追加

```swift:PresentationController
class PresentationController: UIPresentationController {
    
    let blurEffectView: UIVisualEffectView!
    var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
    //追加
    var panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer()
    
    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        let blurEffect = UIBlurEffect(style: .dark)
        blurEffectView = UIVisualEffectView(effect: blurEffect)
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
        //追加
        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(dragDismissController))
        blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.blurEffectView.isUserInteractionEnabled = true
        self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
        self.blurEffectView.addGestureRecognizer(panGestureRecognizer)
    }
```

メソッドdragDismissControllerを追加
```swift:PresentationController
@objc func dragDismissController(sender: UIPanGestureRecognizer) {
        let progress = abs(sender.translation(in: blurEffectView).x / blurEffectView.bounds.size.width)
        
        let translation = sender.translation(in: blurEffectView)
        
        guard translation.x <= 0 else { return }
        presentedView?.frame.origin = CGPoint(x: translation.x, y: 0)
        
        if sender.state == .ended {
            let dragVelocity = sender.velocity(in: blurEffectView)
            if dragVelocity.x <= -500 {
                self.presentedViewController.dismiss(animated: true, completion: nil)
            }else if progress > 0.55 {
                self.presentedViewController.dismiss(animated: true, completion: nil)
            }else {
                UIView.animate(withDuration: 0.3) {
                    self.presentedView?.frame.origin = CGPoint(x: 0, y: 0)
                }
            }
        }
    }
```

以上で個人的には理想的な画面遷移が出来るようになりました!!

#### 最終的な機能
・画面外からのスワイプで画面遷移
・「SideMenu」ボタンから画面遷移
・遷移後、スワイプで画面を閉じれる
・画面外からでもスワイプで画面を閉じれる、タップでも閉じれる
・それぞれの画面遷移時、開度に応じて画面を開く、閉じるを判断して最適な遷移が出来る
・スワイプの移動量が大きい場合、判断して一瞬で画面遷移する